12.1-2:
- 在二叉搜索树中,一个结点的关键字要大于等于其左孩子的关键字,小于等于其右孩子的关键字。但在最小堆中,结点的关键字只要小于等于其左右孩子的关键字即可。
- 不可以。因为在最小堆中,它只告诉你结点的关键字要小于其左右孩子的关键字,但并没有告诉你左右孩子结点的关键字谁大,因此无法像二叉查找树那样,按序输出元素。再说,如果最小堆能够在的时间 O(n) 的时间内按序输出一个元素,又由于最小堆的插入只需要 O(n) 的时间,所以我们就有了一个用于排序的 O(n) 时间算法,这与比较排序最坏时间为 Ω(n lgn) 矛盾。
12.2-5:
- 有一个结点为A,假设它的后继结点为B,则B在A的右子树中,如果该后继结点还有左孩子C,那么C存储的关键子就要比B的小,那么排序输出后C就在A和B之间,那么B就不可能是A的后继结点,所以与假设矛盾,所以B没有左孩子。同理,由于对称性,结点A的前驱没有右孩子。
12.2-7:
先调用TREE-MINIMUM找到最小元素,再调用n-1次TREE-SUCCESSOR,恰好可以实现排序输出,因为该算法先找到最小元素,在根据SUCCESSOR的定义找到最小元素的后继值,以此类推,可以实现从小到大的排序输出。
该算法的运行时间为 Θ(n) 。因为树的n-1每条边最多遍历两次。下面给出该证明。
我们考虑树中的任意一条边,该边的两个节点标记为 u 和v ,并假设 v 是u 的孩子结点。我们假定 v 是
u 的左孩子- 在输出
u
的关键词之前,我们一定要输出以
v 为根的 u 的左子树,这就确保了一个从u 到 v 的向下的遍历。 - 在我们输出
u 的以 v 为根的左子树的最大值后,这就意味着以v 为根的左子树已经输出完了,接下来为调用SUCCESSOR函数。由SUCCESSOR编写的函数可知,这时候它会从 v 到u 向上遍历,这时候会输出 u 结点的关键词。这时候因为u的所有左子树以及u被全部输出了,那么边(u,v)就再也不会被访问了,所以这时候边(u,v)被访问了两次。
- 在输出
u
的关键词之前,我们一定要输出以
我们假设v是u的右孩子:
- 在u被打印出来后,SUCCESSOR会被调用,找到u的右子树的最小节点,这时候会有一个从u到v的向下遍历。
- 在我们打印出以v为根的u的右子树最大值后,这就意味着以v为根的右子树已经全部被输出完了,接下来会调用SUCCESSOR函数,那么这时候会从v到u向上遍历。这时候由于u以及u的右子树已经全部被输出了,所以边(u,v)就再也不会被访问了,所以这时候边(u,v)被访问了两次。
综上,树的n-1每条边最多被访问了两次,所以该算法的运行时间为
12.3-3:
最坏运行时间为 Θ(n2) 。 这时候插入的n个结点全部在一条链上, 运行时间 T=1+2+3+...+n−1=Θ(n2)
最好运行时间为 Θ(n lgn) 。这时候树为完全平衡树,高度为 lgn 。插入时间:
T=lg1+lg2+lg3+...+lg(n−1)=lg(1∗2∗...∗(n−1))=Θ(n−1)lg(n−1)=Θnlgn
12-2:
为了排序,我们首先将一系列字符串插入到基数树中,然后再使用前序遍历法,我们既可以对这些字符串按字典序输出。
前序遍历法之所以能够将存储在基数树中的字符串按序输出,是因为根据题中所给的信息,结点A的值小于两个孩子的结点值,同时右孩子的结点值要大于左孩子的结点值。我们知道前序遍历法是先输出结点A,再输出结点A的左孩子,最后再输出结点A的右孩子。所以通过前序遍历法就对这些字符串进行了排序输出。
该排序输出算法的运行时间为 Θ(n) 的理由如下:
插入需要花费 Θ(n) 的时间。因为在基数树种插入一个字符串所花费的时间正比于该字符串的长度,所有字符串的长度和为n,所有插入所花费的时间为 Θ(n) 。
前序遍历花费 Θ(n) 时间。因为对前序遍历的运行时间分析与对中序遍历的运行时间分析一样,它所花费的时间正比于树中结点的数目。因为存储一个长度为i的字符串的路径对应于一个根结点和i个结点所包含的路径,所以树中结点的数目最多是根节点加上字符串的长度和n。之所以是最多是因为树中一个单节点可以被许多字符串路径共用。
所以从上面可以看出,先插入再前序遍历这个算法能够在 Θ(n) 的运行时间内对基数树种存储的字符串按字典序进行排序。
基数树中结点结构如下:
struct node{
bool occupied;
node* left;
node* right;
node(bool occp=false,node* l=0,node* r=0):occupied(occp),left(l),right(r){}
};
插入代码如下:
bool radixTree::insert(const string& val)
{
node* insertionNode=root;//在基数树类的构造函数中,我们需要先创立一个结点(root= new node())
int index=0;
while(index!=val.size()-1){
if(val[index]=='0'){
if(insertionNode->left==0)
insertionNode->left=new node();
insertionNode=insertionNode->left;
}
else {
if(insertionNode->right==0)
insertionNode->right=new node();
insertionNode=insertionNode->right;
}
index++;
}
if(val[index]=='0'){
if(insertionNode->left==0)
insertionNode->left=new node(true);
else if(insertionNode->left->occupied==false)
insertionNode->left->occupied=true;
else
return false;
}
else{
if(insertionNode->right==0)
insertionNode->right=new node(true);
else if(insertionNode->right->occupied==false)
insertionNode->right->occupied=true;
else
return false;
}
return true;
}
前序遍历代码如下:
void radixTree:sortedPrint()
{ sortedPrint(root,string());}
void radixTree::sortedPrint(node* rootNode,string printValue)
{
if(rootNode!=0){
string leftValue=printValue+"0";
string rightValue=printValue+"1";
// cout<<printValue<<endl;
if(rootNode->occupied==true)
cout<<printValue<<" ";
if(rootNode->left!=0)
sortedPrint(rootNode->left,leftValue);
if(rootNode->right!=0)
sortedPrint(rootNode->right,rightValue);
}
}
12-3:
由题意知:
P(T)=∑x∈Td(x,T)⇒P(T)n=∑x∈Td(x,T)nTL 为T的根的左子树,在左子树 TL 中的任何一个结点到左子树 TL 根路径长比到树T的根的路经长小于1,所以此时对于在左子树 TL 中的任何一个结点来说, d(x,TL)=d(x,T)−1 。同理,对于任何一个在右子树 TR 中的结点来说, d(x,TR)=d(x,T)−1 。在右子树和左子树中一共有n-1个结点,所以 P(T)=P(TL)+P(TR)+n−1 。
对于一组输入元素来说,假设第i个元素为第i小的元素,如果抽取到该元素为树的根,则在 TL 中有i-1个元素,在 TR 中有n-i个元素,因为是随机抽取的,所以抽取到第i小的元素的概率为 1n 。所以二叉搜索树的平均深度为
P(n)=1n∑i=1n(P(i−1)+P(n−i)+n−1) =1n∑i=0n−1(P(i)+P(n−i−1)+n−1)- P(n)=1n∑i=0n−1(P(i)+P(n−i−1)+n−1)=1n∑i=0n−1P(i)+1n∑i=0n−1(P(n−i−1))+n−1=1n∑i=0n−1P(i)+1n∑i=0n−1P(i)+n−1=2n∑i=0n−1P(i)+n−1
因为 P(0)=0,所以P(n)=2n∑n−1k=1P(k)+Θ(n) 。
- P(n)=2n∑k=1n−1P(k)+Θn令Θ(n)=cn则 P(n)=2n∑k=1n−1P(k)+cn⇒nP(n)=2∑k=1n−1P(k)+cn2 (1)⇒(n−1)P(n−1)=2∑k=1n−2P(k)+c(n−1)2 (2)(1)−(2)⇒nP(n)−(n−1)P(n−1)=2P(n−1)+2cn−c (3)因为常数c对(3)式无关紧要,所以nP(n)−(n−1)P(n−1)=2P(n−1)+2cn⇒nP(n)=(n+1)P(n−1)+2cn (4)(4)式两边除以n(n+1),可得P(n)n+1=P(n−1)n+2cn+1P(n−1)n=P(n−2)n−1+2cnP(n−2)n−1=P(n−3)n−2+2cn−1...P(2)3=P(1)2+2c3将上面n−1个公式相加,可得:P(n)n+1=P(1)2+2c∑i=3n−11i因为∑i=3n−1≈loge(n+1)+γ−32(γ=0.577)所以P(n)n+1=O(logn)所以P(n)=O(nlogn)
我们知道当一个元素x被选为子树T的根时,在x之后被插入到T的元素全部要与x元素做比较。同样,对于快速排序来说,一旦元素y被选为子数组中的枢纽元,在子数组中的所有其它元素均要与y做比较。所以为了满足题意,我们可以把插入到二分查找树非叶节点元素的顺序作为快速排序枢纽元选择的顺序。