实现二进制搜索树时对类的怀疑

问题描述

我正在研究一些包含实现伪代码的注释上的通用二进制搜索树(BST)和AVL树(AVL)。我对它们的一些实施细节感到困惑。

BST基于下面的struct Node

struct Node{
  int key;
  Node* parent;
  Node* left;
  Node* right;

  //constructors
}

//methods

AVL版本基本相同,还有更多字段用于平衡树(为清楚起见,我将其称为AVLNode,但注释上没有这种区别):

struct AVLNode{
  int key;
  int height;
  int size;
  AVLNode* parent;
  AVLNode* leftchild;
  AVLNode* rightchild;

  //constructors
}

//methods

两棵树之间的许多操作都是相同的,我可以轻松地使用模板以便在两棵树上重用它们。但是,请考虑操作insert,该操作将插入一个新节点。 BST的代码类似于

//Insert node with key k in tree with root R
void insert(const int& k,Node* root){
  Node* N=find(k,root);         //finds where to insert the node
  if (N->key>k)
    N->leftchild=new Node(k,N);  //inserts as a left child
  else
    N->rightchild=new Node(k,N); //inserts as a right child
}

现在,关键是AVL树的insert操作基本相同。注释中显示代码如下:

void avlInsert(int k,AVLNode* R){
  insert(k,R);          //same operations as for Nodes,shown above
  AVLNode* N=find(x,R); //find node inserted (generic operation for BST)
  rebalance(N);         //perform balancing operations specific to AVL trees 
}

在这一点上,我有点困惑,我知道上面只是一个代码,但是我想知道是否有一种方法可以重用已经为insert提供的操作Node。使用模板专业化只是意味着为insert<AVLNode>一个不同的专业化AVLNode,所以这不是我要指的。

我认为一种方法是将AVLNode定义为Node的子类,然后使用类似的

struct AVLNode : Node {
  //implementation
}

void avlInsert(int k,AVLNode* R){
  Node *root=R;
  insert(k,root);
  AVLNode* N=find(x,R);
  rebalance(N);
}

但是我不太确定这是否可行,而且我不知道如何管理指向{{1}的parent和子元素的指针(即,它们必须是指向Node的指针}并移至Node内的AVLNode

有办法避免重写相同的代码吗?

解决方法

您可以在此处使用CRTP。这将允许您在基类中创建左右节点和父节点。例如,考虑以下内容:

template<typename T>
struct BaseNode{
  int key;
  T* parent;
  T* left;
  T* right;
};

struct AVLNode : public BaseNode<AVLNode>{
  int height;
  int size;
  AVLNode(const int&k,AVLNode*root){};
  AVLNode(){};
};
struct Node : public BaseNode<Node>{
    Node(const int&k,Node*root){};
    Node(){};
};

template<typename T>
T* find(const int& k,T* root){return root;};

template<typename T>
void insert(const int& k,T* root){
  T* N=find(k,root);         //finds where to insert the node
  if (N->key>k)
    N->left=new T(k,N);  //inserts as a left child
  else
    N->right=new T(k,N); //inserts as a right child
}

void test(){
    AVLNode avl_root;
    Node node_root;
    insert(42,&avl_root);
    insert(42,&node_root);
}

不利之处在于,编译器将生成不必要的代码。因为它为每种类型创建一个新的插入函数。对于您来说,这可能不是问题,但值得考虑。有关生成的代码,请参见godbolt

顺便说一句。 请,请请不要使用原始指针以及新建和删除指针。您将获得大量的内存泄漏,尤其是当指针由于其父级被删除而“丢失”时。考虑使用unique_ptrshared_ptr

这样的智能指针