第13章部分习题答案

思考题13-1。其五个小问abcde答案如下:

a:

  • 当插入一个关键词时,从根到新插入结点x路径上的所有结点都需要被改变,因为这个新插入结点x的地址值需要赋值给一个结点的孩子指针,那么这个结点需要被新建,同样,也需要孩子结点指向这个新节点,以此类推,一直到根。

  • 当删除一个结点时,如果这个要被删除的结点z最多只有一个孩子,那么这个要被删除结点z的所有祖先均需要被改变。否则的话找到这个要被删除结点z的后继结点succNode,那么这个后继结点succNode的所有祖先均需被改变。

b:

  • 持久树结点的定义如下:
template<class Type>
struct node{
                Type element;
                node* right;
                node* left;
                node(const Type& e=Type(),node* r=0,node* l=0):element(e),right(r),left(l){}
        };
  • 插入采用的是递归法,insert(val,node*)这个函数返回一个新建结点的指针,然后再新建一个结点,将其合适的赋值,再返回这个结点。代码如下:
template<class Type>
typename persistentTree<Type>::node* persistentTree<Type>::insert(const Type& val,node* rootNode)
{
        if(rootNode==0)
                return new node(val,0,0);

        node* currentNode=rootNode;
        node* newNode=0;

        if(val<currentNode->element){
                newNode=insert(val,currentNode->left);
                return new node(currentNode->element,currentNode->right,newNode);
        }
        else if(currentNode->element<val){
                newNode=insert(val,currentNode->right);
                return new node(currentNode->element,newNode,currentNode->left);
        }
        else
                throw runtime_error("the element has existed in the tree!");
}
  • 删除采用的方法和插入法类似,都是递归,但相比如插入,删除要略微麻烦些。当我们找到要被删除的结点z时,一个单独的函数removeNode(node*)会去处理这个结点z。要分一下三种情况讨论:

    1. 要被删除的结点z左孩子为空,这时候removeNode(node*)函数会直接返回这个结点z的右孩子的指针;
    2. 要被删除的结点z的右孩子为空,这时候removeNode(node*)函数会直接返回这个结点z的左孩子的指针;
    3. 要被删除的结点z的左右孩子均不为空,我们需要找到它的后继结点succNode,但这时候又可以分为两种情况:

      • 结点z的右孩子的左结点为空,那么其后继结点succNode为其右孩子,那么removeNode(node*)会返回一个新建结点newSuccNode的指针,其key为succNode的key,右孩子为原succNode的右孩子,左孩子为要被删除结点z的左孩子,示意图如下:

      • 要被删除结点z的右孩子的左结点x1不为空,那么我们需要沿着z的右孩子的左子树去寻找z的后继结点succNode并且从x1到succNode这一路径上所有的结点都要被新建。那么removeNode(node*)函数依然要返回一个新建的后继结点newSuccNode的指针,其key为后继结点succNode的key,右孩子为新建的结点z右孩子x1,不是原来的结点z右孩子,左孩子为结点z的左孩子。示意图如下:

代码如下:

//类似于插入代码
template<class Type>
typename persistentTree<Type>::node* persistentTree<Type>::remove(const Type& val,node* rootNode)
{
                if(rootNode==0)
                        throw runtime_error("the element is not existent in the tree.");

                node* currentNode=rootNode;
                node* newNode=0;

                if(val<currentNode->element){
                        newNode=remove(val,currentNode->left);
                        return new node(currentNode->element,currentNode->right,newNode);
                }
                else if(currentNode->element<val){
                        newNode=remove(val,currentNode->right);
                        return new node(currentNode->element,newNode,currentNode->left);
                }
                else
                        return removeNode(currentNode);//处理要被删除的结点z
}

//处理要被删除的结点z:
template<class Type>
typename persistentTree<Type>::node* persistentTree<Type>::removeNode(node* currentNode)
{
        if(currentNode->left==0)
                 return currentNode->right;//情况1
        else if(currentNode->right==0)
                 return currentNode->left;//情况2

        else{ //情况3
                node* newSuccNode=removeNodeWithDoubleChild(currentNode->right);//the successor of currentNode
                newSuccNode->left=currentNode->left;

                return newSuccNode;
        }
}

template<class Type>
typename persistentTree<Type>::node* persistentTree<Type>::removeNodeWithDoubleChild(node* currentNode)
{
        if(currentNode->left==0) //情况3的第一种情况
                return new node(currentNode->element,currentNode->right,0);

        //情况3的第二种情况
        node* succ=0;
        node* tmp=removeNodeWithDoubleChild(currentNode,succ);
        return new node(succ->element,tmp,0);
}

//处理情况3的第二种情况
template<class Type>
typename persistentTree<Type>::node* persistentTree<Type>::removeNodeWithDoubleChild(node* currentNode,node*& succ)
{
        if(currentNode->left==0){
                succ=currentNode;
                return currentNode->right;
        }
        else{
                node* newNode=removeNodeWithDoubleChild(currentNode->left,succ);
                return new node(currentNode->element,currentNode->right,newNode);
        }
}

c:

  • 插入算法在从根到新建结点路径上的每个结点上所花的时间为常量,因为路径长度最多为h,所以所花时间为O(h);
  • 因为空间需求与新分配的结点数成正比,树高为h,所以分配的结点数最多为h,所以空间需求为O(h)。

d:

如果每个结点有父节点属性,那么原树种所有的结点均需被复制,因为根会被复制新建,那么它的两个孩子结点的父节点需要指向它,那么这两个孩子结点需要被新建,同理这两个孩子结点的孩子结点也需要被新建,以此类推,原树中所有的结点均需被复制。所以插入时间需求和空间需求为 Ω(n)

e:

从问题a和c,我们知道插入或删除一个元素所需时间为O(h)(h为树的高),红黑树的高为O(lgn),所以利用红黑树可以保证时间和空间需求为O(lgn)。但是相比如在普通二叉搜索树中的插入和删除,在红黑树中,我们还需要施加结点旋转和改变颜色这额外的操作来恢复红黑树性质。现在来说明这些额外操作最多会增加O(lgn)的空间需求和时间需求。

  • 插入: 编写和问题(b)中类似的插入代码,但要用栈来保存父节点,因为问题(d)表明结点不能有父结点属性,由于红黑树的高为O(lgn),所以栈最多保存O(lgn)个结点,这时候会增加时间和空间需求O(lgn)。插入结束后调用RB-INSERT-FIXUP函数恢复红黑树性质,保存一系列父节点的栈要被传递到该函数中。在该函数代码中,一个目前结点的父节点和祖父节点的指针值需要被知道,这可以直接从栈中获取,所需时间为O(1)。然后如果其叔结点的颜色为红色,这时候需要把父节点和叔结点的颜色变为红色,祖父节点的颜色变为黑色,祖父结点点变成目前结点。由于父结点和祖父结点出现在从根到新建结点的路径上,所以它们在插入代码中已经被新建了,那么这时候只需要新建叔结点即可。叔结点为红色这种情况最多出现O(lgn)次,所以时间需求和空间需求为O(lgn)。一旦叔结点为黑色,则开始旋转,最多做两次旋转,由于要被旋转的结点都位于从根到新插入结点的路径上,所以它们在插入时已经被新建了,这时候没有必要新建,所以没有空间需求。由于旋转操作所花费的时间为常数量,所以所需时间为O(1)。

  • 删除:删除情况的分析几乎和插入的分析一样,栈的新建为增加O(lgn)空间和时间需求。新建叔结点最多增加O(lgn)空间和时间需求。旋转最多实施三次,但是与插入旋转有点不同的是,插入旋转不需要新建结点,但删除时,旋转需要创建常数个结点。旋转操作为常数量时间操作,所以增加的时间需求为O(1)。

综上所述,红黑树可以保证每次插入和删除的最坏情况运行时间和空间为O(lgn)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值