实现二叉搜索树时对类的疑惑
Doubts on classes when implementing binary search trees
我正在研究一些包含实现伪代码的笔记上的通用二叉搜索树 (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
}
此时我有点疑惑,我知道上面只是一个伪代码,但我想知道是否有办法重用已经为 Node
提供的操作 insert
.使用模板专业化只是意味着为 AVLNode
编写不同的专业化 insert<AVLNode>
,所以这不是我所指的。
我认为一种方法是将 AVLNode
定义为 Node
的子 class,然后使用类似
的东西
struct AVLNode : Node {
//implementation
}
void avlInsert(int k, AVLNode* R){
Node *root=R;
insert(k,root);
AVLNode* N=find(x,R);
rebalance(N);
}
但我不太确定这是否可行,我不知道如何管理指向 parent
和子项的指针(即它们必须是 Node
内部的指针 Node
到 AVLNode
里面 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_ptr
或 shared_ptr
这样的智能指针
我正在研究一些包含实现伪代码的笔记上的通用二叉搜索树 (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
}
此时我有点疑惑,我知道上面只是一个伪代码,但我想知道是否有办法重用已经为 Node
提供的操作 insert
.使用模板专业化只是意味着为 AVLNode
编写不同的专业化 insert<AVLNode>
,所以这不是我所指的。
我认为一种方法是将 AVLNode
定义为 Node
的子 class,然后使用类似
struct AVLNode : Node {
//implementation
}
void avlInsert(int k, AVLNode* R){
Node *root=R;
insert(k,root);
AVLNode* N=find(x,R);
rebalance(N);
}
但我不太确定这是否可行,我不知道如何管理指向 parent
和子项的指针(即它们必须是 Node
内部的指针 Node
到 AVLNode
里面 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_ptr
或 shared_ptr