如何实现插入的AVL树没有父指针
How to implement insertion for AVL tree without parent pointer?
我看到了一些关于AVL的rebalance()
函数实现的文章。
在每次插入之后,我们应该检查插入节点的祖先节点是否平衡。
所以我想,为了检查祖先的平衡,我需要知道插入节点的父节点。
但是,我想知道有没有其他方法可以做到这一点,而不必使用父指针?
例如,节点结构:
struct Node {
int data;
struct Node *lchild, *rchild; //*parent;
};
如果我没记错的话:
你要做的是将balance因子作为int类型存储在节点本身中,它可以是:
- -1:节点的左子树比右子树高一级(left-heavy)
- 0节点均衡;或
- 1右子树较高(右重)。
insert(Node subtree)函数返回一个布尔值,如果插入使子树的高度增加,则返回true。在从递归insert()调用返回时更新balance因子并重新平衡树。
这可能最好用几个例子来解释:
如果当前节点处于平衡因子-1,则插入到右子树中,并且insert(rchild)返回true,则:
- 将当前节点的平衡因子更新为0 -插入前左子树更高,右子树的高度增加,所以它们现在是相同的高度;和
- 返回false -较浅的树的高度增加,所以当前节点的高度保持不变
如果你插入到子树,并且insert(…)返回false:
- 当前节点的平衡因子不变 -子树高度不变,平衡因子不变
- 返回false -子树高度没有改变,所以当前节点高度也没有改变
如果当前节点的平衡因子0,则插入到左子树中,并且insert(lchild)返回true:
- 平衡因子变为-1 -插入前子树高度相同,插入使左侧的子树更高
- 返回true
(类似地,如果插入到右子树,平衡因子将变为1)
如果当前节点的平衡因子为-1,则插入到左子树中,并且insert(lchild)返回true:
平衡因子将变为-2,这意味着您必须通过进行适当的旋转来重新平衡节点。我承认,对于这四个旋转中的每一个将对balance因子产生什么影响以及insert(current)将返回什么,我是一个空白,希望前面的示例能够充分解释跟踪节点平衡的方法。
在遍历树时可以维护当前节点的堆栈
stack<Node*> nodeStack;
当你遍历到一个新节点时,将它添加到堆栈中,然后你就有了你的祖先。当处理完一个节点后,将它从堆栈中弹出。
**编辑
对对齐注释的细化:
struct Node {
int data;
struct Node *children, *parent
};
创建子元素时,这样做:
node.children = new Node[2]; or node.children = malloc(sizeof(Node) * 2);
node.children[0].parent = node;
node.children[1].parent = node;
使用双指针(或c++中对指针的引用)应该完全消除对父指针的需求。
typedef struct Node {
int value;
int height;
struct Node *left;
struct Node *right;
} Node;
int height(Node *node) {
return (node == NULL) ? -1 : node->height;
}
void insert(Node * & node, int value) {
if (node == NULL) {
node = new Node();
node->value = value;
} else if (value < node->value) {
insert(node->left, value);
if (height(node->left) - height(node->right) == 2) {
if (value < note->left->value) {
singleRotateWithLeftChild(node);
} else {
doubleRotateWithLeftChild(node);
}
}
} else if (value > node->value) {
// Symmetric case
}
node->height = 1 + max(height(node->left), height(node->right));
}
由于这个问题没有完整的实现,我决定添加一个。这可以通过使用返回当前节点的递归insert
来实现。下面是代码:
typedef struct node
{
int val;
struct node* left;
struct node* right;
int ht;
} node;
int height(node* current) {
return current == nullptr ? -1 : current->ht;
}
int balanceFactor(node* root) {
int leftHeight = height(root->left);
int rightHeight = height(root->right);
return leftHeight - rightHeight;
}
int calcHeight(node* n) {
int leftHeight = height(n->left);
int rightHeight = height(n->right);
return max(leftHeight, rightHeight) + 1;
}
node* insert(node * root,int val) {
/**
First, recusively insert the item into the tree
*/
if (root == nullptr) {
root = new node();
root->val = val;
} else if (root->val < val) {
root->right = insert(root->right, val);
//the height can increase only because of the right node
root->ht = std::max(root->ht, root->right->ht + 1);
} else {
root->left = insert(root->left, val);
//the height can increase only because of the left node
root->ht = std::max(root->ht, root->left->ht + 1);
}
//after insertion on this depth is complete check if rebalancing is required
// the right subtree must be rebalanced
if (balanceFactor(root) == -2) {
node* r = root->right;
node* rl = r->left;
// it's a right right case
if (balanceFactor(r) == -1) {
r->left = root;
root->right = rl;
root->ht = calcHeight(root);
r->ht = calcHeight(r);
//return new root
return r;
} else { // it's a right left case
node* rlr = rl->right;
node* rll = rl->left;
rl->left = root;
root->right = rll;
rl->right = r;
r->left = rlr;
root->ht = calcHeight(root);
r->ht = calcHeight(r);
rl->ht = calcHeight(rl);
return rl;
}
} else if (balanceFactor(root) == 2) {
node* l = root->left;
node* lr = l->right;
// it's a left left case
if (balanceFactor(l) == 1) {
l->right = root;
root->left = lr;
root->ht = calcHeight(root);
l->ht = calcHeight(l);
//return new root
return l;
} else { // it's a left right case
node* lrl = lr->left;
node* lrr = lr->right;
lr->right = root;
lr->left = l;
root->left = lrr;
l->right = lrl;
root->ht = calcHeight(root);
l->ht = calcHeight(l);
lr->ht = calcHeight(lr);
return lr;
}
}
return root;
}
我的编码方式是,当您在树中搜索要删除的元素时,临时更改遍历(左或右)的子链接,使其成为遍历节点堆栈中的链接(实际上是一个临时父指针)。然后从这个堆栈中弹出每个节点,恢复子指针,并重新平衡。
有关c++编码,请参阅https://github.com/wkaras/C-plus-plus-intrusive-container-templates/blob/master/avl_tree.h中的remove成员函数(目前在第882行)。
有关C编码,请参见http://wkaras.webs.com/gen_c/cavl_impl_h.txt中由宏调用L__(remove)生成的函数。
我不认为父指针对插入有任何用处。
如果你想删除一个由节点指针而不是唯一键标识的节点,那么我认为使用父指针可能会更快。
- 1d 智能指针不适用于语法 (*)++
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 为什么使用 "this" 指针调用派生成员函数?
- 函数向量_指针有不同的原型,我可以构建一个吗
- 使用指针从C++中的数组中获取最大值
- 助记符和指向成员语法的指针
- 嵌入方指针压缩已禁用
- 数组的指针从不分段故障
- C++ 指针的内存地址和指向数组的内存地址如何相同?
- 何时在引用或唯一指针上使用移动语义
- QMetaObject invokeMethod的基于函数指针的语法
- 如何从 std::atomic 中提取指针 T<T>?
- 如何在 C# 中映射双 C 结构指针?
- C++将浮点指针值舍入为小数位数
- 为什么++(*p)更改指针值
- 调整大小后指向元素值的指针unordered_map有效?
- 正在将指针转换为范围
- 为什么我们要为avl树实现返回一个指向节点的指针,而不是void函数
- C++指针、引用和 AVL 树
- 如何实现插入的AVL树没有父指针