《算法导论》笔记 第12章 12.3 插入和删除
【笔记】
插入:从根结点开始,沿树下降。指针x跟踪这条路径,而y始终指向x的父结点。根据key[z]与key[x]的比较结果,决定向左向右转。直到x成为NIL为止。这个NIL所占位置及我们想插入z的地方,y即为z的父结点。
删除:以指向z的指针为参数,考虑三种情况。若z没有子女,则修改其父结点p[z],是NIL为其子女;如果结点z只有一个子女,则可以通过在其子结点与父结点之间建立一条链来删除z。如果结点z有两个子女,先删除z的后继y(它没有左子女),再用y的内容来替代z的内容。
void treeInsert(NODE *z) { NODE *y = NULL; NODE *x = root; while (x != NULL) { y = x; if (z->key < x->key) x = x->l; else x = x->r; } z->p = y; if (y == NULL) root = z; else { if (z->key < y->key) y->l = z; else y->r = z; } } NODE* treeDelete(NODE* z) { NODE *y, *x; if (z->l == NULL || z->r ==NULL) y = z; else y = treeSuccessor(z); if (y->l != NULL) x = y->l; else x = y->r; if (x != NULL) x->p = y->p; if (y->p == NULL) root = x; else { if (y == y->p->l) y->p->l = x; else y->p->r = x; } if (y != z) { z->key = y->key; } return y; }
对于高度为h的二叉查找树,动态集合操作INSERT和DELETE的运行时间为O(h)。
【练习】
12.3-1 给出过程TREE-INSERT的一个递归版本。
void insert(NODE *rt, NODE *z,NODE *pa = NULL) { if (rt == NULL) { z->p = pa; z->l = z->r = NULL; if (pa == NULL) root = z; else { if (z->key < pa->key) pa->l = z; else pa->r = z; } return; } if (z->key < rt->key) insert(rt->l,z,rt); else insert(rt->r,z,rt); }
12.3-2 假设我们通过反复插入不同的关键字的做法来构造一棵二叉查找树。论证:为在树中查找一个关键字,所检查的结点数等于插入该关键字时所检查的结点数+1。
插入时的比较与查找时的比较几乎完全相同,唯一区别为查找时要多检查插入时的结点。
12.3-3 可以这样来对n个数进行排序:先构造一棵包含这些数的二叉查找树(重复应用TREE-INSERT来逐个地插入这些数),然后按中序遍历来输出这些数。这个排序算法的最坏情况和最好情况运行时间怎样?
最坏情况,二叉树退化成链,则构造O(n^2),遍历O(n),运行时间O(n^2)。
最好情况,二叉树平衡,构造O(nlogn),遍历O(n),运行时间O(nlogn)。
12.3-4 假设另有一种数据结构中包含指向二叉查找树中某结点y的指针,并假设用过程TREE-DELETE来删除y的前驱z。这样做会出现哪些问题?如何改写TREE-DELETE来解决这些问题?
有可能指向y的指针已被删除,而y的值已被复制到z。
将y指向父结点子结点的指针改为与z相同,并改变z的父结点指向z的指针与子结点指向父亲的指针。
12.3-5 删除操作是可以交换的吗?说明为什么是的,或给出一个反例。
12.3-6 当TREE-DELETE中的结点z有两个子结点时,可以将其前驱拼接掉。有些人提出了一种公平的策略,即为前驱和后继结点赋予相同的优先级,从而可以得到更好的经验性能。那么,应该如何修改TREE-DELETE来实现这样一种公平的策略?