《算法导论》12.3节习题
Posted meixiaogua
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《算法导论》12.3节习题相关的知识,希望对你有一定的参考价值。
- 12.3-1 二叉搜索树insert操作的递归版本
void insert1(Node* pRoot, Node* pAdd)
{
bool bLeft = pAdd->key < pRoot->key;
Node* pNextRoot = bLeft ? pRoot->left : pRoot->right;
if(pNextRoot)
insert1(pNextRoot, pAdd);
else
{
pAdd->parent = pRoot;
if(bLeft)
pRoot->left = pAdd;
else
pRoot->right = pAdd;
}
}
12.3-2 insert过程途经n个节点后遇到了空节点,便将待插入的元素安放上去了,接下来的search过程先路过同样的n个节点,最后与第n+1个节点比较发现相同,于是search完毕。
12.3-3 由12.3-2可得,insert过程和search过程一样,是O(h)-time的,h代表树的高度。
设一共有n个节点,构建一棵树需要调用n次insert,这个过程耗时O(nh),后续的中序遍历耗时是O(n)。所以排序过程的时间复杂度是由构建过程决定的。
最坏的情况集合按照正序或者倒序排列,则h = n,总时间是O(n^2)。
最好情况是集合按照层次遍历顺序排列,最后构建成一棵完全二叉树,h = lg(n) ,总时间为O(n*lg(n))。
12.3-4 从同一棵二叉搜索树上先删除x再删除y,和先删除y再删除x,最终得到的树一定是一样的吗?
不一定,举个反例:
//现在有一棵树,上面有1,2,3,4四个节点:
2
/ 1 4
/
3
//如果 先删除1 再删除2,结果是:
2 2 4
/ /
1 4 --> 4 --> 3
/ /
3 3
//如果 先删除2 再删除1,结果是:
2 3 3
/ / 1 4 --> 1 4 --> 4
/
3
//得到的结果是不一样的。
下面说说,我是怎样想到这个反例的。
要删除x,根据删除的规则,如果x没有孩子,直接删除;如果x只有一个孩子,就用唯一的孩子顶替x的位置;如果x有两个孩子,就将x的后继节点s顶替x的位置。
从这个规则中可以发现,x有几个孩子会影响到x的接班人人选。如果删除的顺序可以影响到删除x时候x的孩子个数,就会影响到最终树的形状。
那么,y在x的什么位置上,删除y会对x孩子个数造成影响呢?y本身就是x的一个孩子,而且y没有孩子。下面分左孩子和右孩子讨论。因为x只有一个孩子y的情况太简单,也不能成为反例,不做讨论,下面对x有两个孩子的情况进行分析。称以x为根的树为X树。
如果y是x的左孩子,先删y,删除之后,x的孩子个数变成1,此时删除x,X树被其右子树取代,X树的根变为x的右孩子。反过来,先删除x,此时x有两个孩子,X树的根变为x的后继,只要其后继不是它的右孩子本身,那么结果就是不一样的。这也就是上面给出的反例。
如果y是x的右子树,先删y,再删x,X树被x左子树取代,先删x,y顶替x的位置,再删y,X树仍然被x的左子树取代,结果是一样的,不能作为反例。
还有一种可能,删除x,x的接班人是自己的后继s,原以s为根的树S会发生改变,从S树上节点能否找出一个y作为反例,我暂时没有想清楚。
12.3-5 二叉搜索树的每个节点保存“后继”,“左孩子”,“右孩子”三个属性,在O(h)时间内实现insert delete search。
为什么要把“父亲”属性替换成“后继”属性呢?相比保存“父亲”,保存“后继”属性的优势在于查找后继节点时间O(1),排序虽然都是O(n)但是常数项较小。执行这两种操作时,性能相当于单向链表。而执行插入、删除、查找操作时,性能相当于二叉树。
我们先来总结一下这些操作需要读写哪些属性。
insert操作需要修改的有:父亲节点的孩子属性,前驱节点和新插入节点的后继属性
delete操作需要修改的有:父亲节点的孩子属性,前驱节点的后继属性
search操作需要读取的有:节点的孩子属性
根据上面的总结可以发现,这道题的关键点是在O(h)时间内找到父亲节点和前驱节点。
查找父亲节点的做法是从Root向下逐级查找;如果当前节点没有左孩子,那么查找前驱节点的做法也是从Root向下查找,可以和父亲节点的查找工作合并起来。如果当前节点有左孩子,那么前驱节点是左孩子的最大节点。
#include <iostream>
#include <cassert>
using namespace std;
struct Node
{
int key;
Node* succ;
Node* left;
Node* right;
Node(int k):key(k),succ(nullptr),left(nullptr),right(nullptr){}
};
Node* minimum(Node* pRoot);
Node* parent_pred(Node* pRoot, int key, Node*& pPred);//为新插入节点,找父亲的同时从父亲中找前驱
void insert(Node* pRoot, int key)
{
Node* pNew = new Node(key);
Node* pPred;
Node* pParent = parent_pred(pRoot, key, pPred);
Node* pHead = minimum(pRoot);
//upate parent‘s child
if(key < pParent->key)
pParent->left = pNew;
else
pParent->right = pNew;
//update pPred‘s succ and pNew‘s succ
if(pPred)
{
pNew->succ = pPred->succ;
pPred->succ = pNew;
}
else
{
pNew->succ = pHead;//注意:这个头结点一定要在插入之前获取
}
}
Node* pred(Node* pNode); //从左子树上找前驱
Node* parent(Node* pRoot, Node* pNode); //找父亲
Node* parent_pred(Node* pRoot, Node* pNode, Node*& pPred); //为已有节点,找父亲的同时从父亲中找前驱
void Delete(Node*& pRoot, Node* pDelete)
{
Node* pDeleteReplace = nullptr;
if(!pDelete->left)
{
pDeleteReplace = pDelete->right;//no child //only right
}
else
{
pDeleteReplace = pDelete->left; //only left
if(pDelete->right) //both
{
Node* pSucc = pDelete->succ;
if(pSucc != pDelete->right)
{
Node* pSuccParent = parent(pRoot, pSucc);
if(pSucc->key < pSuccParent->key)
pSuccParent->left = pSucc->right;
else
pSuccParent->right = pSucc->right;
pSucc->right = pDelete->right;
}
pSucc->left = pDelete->left;
pDeleteReplace = pSucc;
}
}
//update parent‘s child, pred‘s succ
Node* pPred;
Node* pDeleteParent = parent_pred(pRoot, pDelete, pPred);
bool bLeft;
if(pDeleteParent)
bLeft = pDeleteParent->left == pDelete;
if(pDeleteParent)
{
if(bLeft)
pDeleteParent->left = pDeleteReplace;
else
pDeleteParent->right = pDeleteReplace;
}
else
{
pRoot = pDeleteReplace;
}
if(pPred)
{
pPred->succ = pDelete->succ;
}
}
Node* search(Node* pRoot, int key)
{
Node* pCurrent = pRoot;
int keyCurrent;
while(pCurrent)
{
keyCurrent = pCurrent->key;
if(key == keyCurrent)
break;
if(key < keyCurrent)
pCurrent = pCurrent->left;
else
pCurrent = pCurrent->right;
}
return pCurrent;
}
Node* minimum(Node* pRoot)
{
Node* pMin = pRoot;
while(pMin->left)
{
pMin = pMin->left;
}
return pMin;
}
Node* parent_pred(Node* pRoot, int key, Node*& pPred)
{
Node* pCurrent = pRoot;
Node* pParent = nullptr;
pPred = nullptr;
while(pCurrent)
{
pParent = pCurrent;
if(key < pCurrent->key)
{
pCurrent = pCurrent->left;
}
else
{
pCurrent = pCurrent->right;
pPred = pParent;
}
}
return pParent;
}
Node* parent(Node* pRoot, Node* pNode)
{
Node* pCurrent = pRoot;
Node* pParent = nullptr;
while(pNode != pCurrent)
{
assert(pCurrent);
pParent = pCurrent;
if(pNode->key < pCurrent->key)
pCurrent = pCurrent->left;
else
pCurrent = pCurrent->right;
}
return pParent;
}
Node* parent_pred(Node* pRoot, Node* pNode, Node*& pPred)
{
Node* pCurrent = pRoot;
Node* pParent = nullptr;
pPred = nullptr;
while(pNode != pCurrent)
{
assert(pCurrent);
pParent = pCurrent;
if(pNode->key < pCurrent->key)
pCurrent = pCurrent->left;
else
{
pCurrent = pCurrent->right;
pPred = pParent;
}
}
return pParent;
}
Node* pred(Node* pNode)
{
Node* pPred = pNode->left;
while(pPred->right)
pPred = pPred->right;
return pPred;
}
void walk(Node* pRoot)
{
Node* pCurrent = minimum(pRoot);
while(pCurrent)
{
cout << pCurrent->key << " ";
pCurrent = pCurrent->succ;
}
cout << endl;
}
void test()
{
//build
Node* pRoot = new Node(4);
insert(pRoot, 2);
insert(pRoot, 5);
insert(pRoot, 1);
insert(pRoot, 3);
insert(pRoot, 7);
insert(pRoot, 6);
insert(pRoot, 8);
walk(pRoot);
//search
cout << search(pRoot, 3)->key << endl;
cout << search(pRoot, 6)->key << endl;
cout << search(pRoot, 4)->key << endl;
//delete
Delete(pRoot, pRoot->right);//delete 5
Delete(pRoot, pRoot->left); //delete 2
Delete(pRoot, pRoot->left); //delete 3
Delete(pRoot, pRoot);// delete 4
Delete(pRoot, pRoot->left);//delete 1
walk(pRoot);
//destroy
Node* pCurrent = minimum(pRoot);
while(pCurrent)
{
delete pCurrent;
pCurrent = pCurrent->succ;
}
pCurrent = nullptr;
}
/*output
1 2 3 4 5 6 7 8
3
6
4
6 7 8
*/
以上是关于《算法导论》12.3节习题的主要内容,如果未能解决你的问题,请参考以下文章