平衡二叉树(AVL)结点删除操作
2014-02-24 14:48
323 查看
结点的删除向来是二叉树的操作中的难点,平衡二叉树中结点删除相对更是复杂,由于删除结点操作以后还要保持其平衡的特征,
所以给我们删除的操作带来了一些小麻烦!
之前的文章我写过一个关于AVL树的结点插入的操作,当时我在二叉树结点的定义中加入了一个height的参数,这个参数表示以该
结点为根的子树的深度。在本文中我将这个参数删掉,利用一个方法Depth(),来求该结点的深度!如果读者有兴趣,自己把这个参数的操作
加进去,难度不会很大,逻辑对了就OK。 虽然因此效率低了不少,不过难度降低了不少!
好依然首先把结点结构定义打出来:
删除结点的操作依然需要分几种情况来分析:
1、假如要删除的结点是一片叶子,即叶子结点:
那么直接删除这个结点,然后从它的父节点开始判断结点被删除以后是否依然满足平衡的特性,利用递归一直判断到根结点为止,
利用递归很容易解决。
2、非叶子结点的删除:
非叶子结点的删除稍微麻烦,要分2种情况,这两种情况又各自有一个对称的情况。
a)我以某结点T的左子树是删除结点操作之后最深的一个不满足平衡特性的结点,而它的左子树的深度大于右子树的深度。即(depth(l) - depth(r) == 2)
这说明是被删除的结点是T的左子树的右子树上。这是我们就利用右双旋转将其平衡(关于旋转的方法在之前文章总有描述,这里我只把旋转的方法写出来不做过多解释)。
b)我以某结点T的左子树是删除结点操作之后最深的一个不满足平衡特性的结点,而它的右子树的深度大于左子树的深度。即(depth(r) - depth(l) == 2)
被删除的结点是T的左子树的右子树上。这是我们就利用右单旋转将其平衡。
这两个方法各自有一个对称的情况,利用左旋转解决。
我先把旋转的方法写出来:
这几个旋转才做和插入时的旋转操作一模一样。
然后就是整个删除操作的方法:
因为删除一个节点可能导致对其祖先结点的旋转,因此可以事先将要节点的祖先节点都保存下来。也就是从根结点到T1节点的路径。调整的时候是从T1的父节点开始往山(可能持续到根节点),因此其路径适合保存在栈中。这样可以依次从栈中弹出节点,判断是否需要调整。
需要注意的是,如果T1不是叶节点,需要查找其左子树的最右节点或者右子树的最左节点T2,这时的调整是从T2的父节点开始的,因此还需要记录T1到T2的路径,也就是说,最终得到的路径是从根结点到T2的。
调整某个节点的时候,记此节点为Tc,假设其父节点为Tp。需要将Tp的左(或者右)子树指针指向调整后得到的新Tc。
因为删除一个节点可能导致对其祖先结点的旋转,因此可以事先将要节点的祖先节点都保存下来。也就是从根结点到T1节点的路径。调整的时候是从T1的父节点开始往山(可能持续到根节点),因此其路径适合保存在栈中。这样可以依次从栈中弹出节点,判断是否需要调整。
需要注意的是,如果T1不是叶节点,需要查找其左子树的最右节点或者右子树的最左节点T2,这时的调整是从T2的父节点开始的,因此还需要记录T1到T2的路径,也就是说,最终得到的路径是从根结点到T2的。
调整某个节点的时候,记此节点为Tc,假设其父节点为Tp。需要将Tp的左(或者右)子树指针指向调整后得到的新Tc。
学起来很蛋疼但是学完以后完全理解旋转以后就很容易啦,祝大家愉快!
所以给我们删除的操作带来了一些小麻烦!
之前的文章我写过一个关于AVL树的结点插入的操作,当时我在二叉树结点的定义中加入了一个height的参数,这个参数表示以该
结点为根的子树的深度。在本文中我将这个参数删掉,利用一个方法Depth(),来求该结点的深度!如果读者有兴趣,自己把这个参数的操作
加进去,难度不会很大,逻辑对了就OK。 虽然因此效率低了不少,不过难度降低了不少!
好依然首先把结点结构定义打出来:
typedef int ElemType; typedef struct Node { ElemType data; struct Node * lchild; struct Node * rchild; }avlNode,*avlTree;
删除结点的操作依然需要分几种情况来分析:
1、假如要删除的结点是一片叶子,即叶子结点:
那么直接删除这个结点,然后从它的父节点开始判断结点被删除以后是否依然满足平衡的特性,利用递归一直判断到根结点为止,
利用递归很容易解决。
2、非叶子结点的删除:
非叶子结点的删除稍微麻烦,要分2种情况,这两种情况又各自有一个对称的情况。
a)我以某结点T的左子树是删除结点操作之后最深的一个不满足平衡特性的结点,而它的左子树的深度大于右子树的深度。即(depth(l) - depth(r) == 2)
这说明是被删除的结点是T的左子树的右子树上。这是我们就利用右双旋转将其平衡(关于旋转的方法在之前文章总有描述,这里我只把旋转的方法写出来不做过多解释)。
b)我以某结点T的左子树是删除结点操作之后最深的一个不满足平衡特性的结点,而它的右子树的深度大于左子树的深度。即(depth(r) - depth(l) == 2)
被删除的结点是T的左子树的右子树上。这是我们就利用右单旋转将其平衡。
这两个方法各自有一个对称的情况,利用左旋转解决。
我先把旋转的方法写出来:
avlTree SingleRotateWithLeft(avlTree k2) { avlTree k1; k1 = k2->lchild; k2->lchild = k1->rchild; k1->rchild = k2; return k1; } avlTree SingleRotateWithRight(avlTree k2) { avlTree k1; k1 = k2->rchild; k2->rchild = k1->lchild; k1->lchild = k2; return k1; } avlTree DoubleRotateWithLeft(avlTree k1) { k1->lchild = SingleRotateWithRight(k1->lchild); return SingleRotateWithLeft(k1); } avlTree DoubleRotateWithRight(avlTree k1) { k1->rchild = SingleRotateWithLeft(k1->rchild); return SingleRotateWithRight(k1); }
这几个旋转才做和插入时的旋转操作一模一样。
然后就是整个删除操作的方法:
因为删除一个节点可能导致对其祖先结点的旋转,因此可以事先将要节点的祖先节点都保存下来。也就是从根结点到T1节点的路径。调整的时候是从T1的父节点开始往山(可能持续到根节点),因此其路径适合保存在栈中。这样可以依次从栈中弹出节点,判断是否需要调整。
需要注意的是,如果T1不是叶节点,需要查找其左子树的最右节点或者右子树的最左节点T2,这时的调整是从T2的父节点开始的,因此还需要记录T1到T2的路径,也就是说,最终得到的路径是从根结点到T2的。
调整某个节点的时候,记此节点为Tc,假设其父节点为Tp。需要将Tp的左(或者右)子树指针指向调整后得到的新Tc。
因为删除一个节点可能导致对其祖先结点的旋转,因此可以事先将要节点的祖先节点都保存下来。也就是从根结点到T1节点的路径。调整的时候是从T1的父节点开始往山(可能持续到根节点),因此其路径适合保存在栈中。这样可以依次从栈中弹出节点,判断是否需要调整。
需要注意的是,如果T1不是叶节点,需要查找其左子树的最右节点或者右子树的最左节点T2,这时的调整是从T2的父节点开始的,因此还需要记录T1到T2的路径,也就是说,最终得到的路径是从根结点到T2的。
调整某个节点的时候,记此节点为Tc,假设其父节点为Tp。需要将Tp的左(或者右)子树指针指向调整后得到的新Tc。
avlTree Delete(avlTree T,ElemType e) { if(T == NULL) { return NULL; } else if(e < T->data) { T->lchild = Delete(T->lchild,e); if(Depth(T->rchild) - Depth(T->lchild) == 2) { if (Depth(T->rchild->rchild) < Depth(T->rchild->lchild)) { T = DoubleRotateWithRight(T); } else { T = SingleRotateWithRight(T); } } } else if(e > T->data) { T->rchild = Delete(T->rchild,e); if(Depth(T->lchild) - Depth(T->rchild) == 2) { if(Depth(T->lchild->lchild) < Depth(T->lchild->rchild)) { T = DoubleRotateWithLeft(T); } else { T = SingleRotateWithLeft(T); } } } else if(T->lchild == NULL && T->rchild == NULL) { free(T); return NULL; } else { avlTree tmp = FindMin(T->rchild); T->data = tmp->data; T->rchild = Delete(T->rchild,tmp->data); } return T; }
学起来很蛋疼但是学完以后完全理解旋转以后就很容易啦,祝大家愉快!
相关文章推荐
- 平衡二叉树AVL的基本操作之删除
- Geeks - AVL Tree Deletion 平衡二叉树 删除操作
- 对链表进行创建、结点的删除和插入操作
- 关于线性表的链式存储(链表)的创建,头插法,尾插法,删除链表结点的操作
- 链表操作(删除链表中的一个结点)
- 平衡二叉树平衡二叉树(AVL)--查找、删除、插入(Java实现)
- 日常联系:一个C++程序完成链表的增加结点、删除结点、打印、查询、逆序等操作
- 单链表的建立(头部延长、尾部延长)、插入操作、删除操作(无头结点的删除、有头结点的删除)
- 平衡二叉树(AVL)的创建、插入、删除
- C语言实现单链表(带头结点)的基本操作(创建,头插法,尾插法,删除结点,打印链表)
- 循环结点的前驱删除操作
- 笔试题:创建一个单链表,结点包含学生的学号,姓名,性别,年龄信息.写几个程序,实现按学生学号插入,查询,删除等操作.
- AVL--平衡二叉树的一些操作,杂记
- AVL---平衡二叉树的基本操作
- 平衡二叉树(AVL)实现(3)-删除
- 链表(二)——单向链表的基本操作(创建、删除、打印、结点个数统计)
- 二叉排序树的创建(结点的插入,删除等操作)
- 程序员面试宝典_链表基本操作,建立,求长,删除和插入特定值的结点
- HDU 1473 并查集 删除结点操作 建立虚拟根结点
- C语言实现带头结点的链表的创建、查找、插入、删除操作