(a) ①根据堆性质以每个结点为根的子树的最小优先级都是在子树的根上。②根据二叉搜索树性质,以每个节点的根的子树的左子树<右子树。
根据这两点根和左右子树都是被唯一确定,因此能够确定整棵树。
(b)Treap树是二叉查找树的一种,而二叉查找树期望高度为O(lgn),所以treap期望高度为O(lgn).
(c)代码如下:
//13-4 Treap树 #include <iostream> #include <time.h> using namespace std; #define LEN sizeof(struct Treap) #define n 100//树中元素个数。 struct Treap { char key; int priority; struct Treap*lchild; struct Treap*rchild; struct Treap*parent; }; struct Treap*root=NULL; int RAND(int a[],int i)//随机选择N个互不相同的数。 { int k=rand()%n+1; for (int j=0;j<i;j++) { if (a[j]==k) { k=rand()%n+1; j=-1; } } return k; } struct Treap*LEFT_ROTATE(struct Treap*x) {//左旋转:分三个步骤①②③来叙述旋转代码的。 struct Treap*y=x->rchild;//设置y结点。 x->rchild=y->lchild;//本行代码以及下面的if结构表达的是“y的左孩子成为x的右孩子”。① if(y->lchild!=NULL) { y->lchild->parent=x; } y->parent=x->parent;//本行代码以及下面的if-else结构表达的过程是“y成为该子树新的根”。② if(x->parent==NULL) { root=y; } else if(x==x->parent->lchild) { x->parent->lchild=y; } else x->parent->rchild=y; y->lchild=x;//本行代码以及下面一行都表达了“x成为y的左孩子”。③ x->parent=y; return y; } struct Treap* RIGHT_ROTATE(struct Treap*x) {//右旋转:分三个步骤①②③来叙述旋转代码的。 struct Treap*y=x->lchild;//设置y结点。 x->lchild=y->rchild;//本行代码以及下面的if结构表达的是“y的左孩子成为x的右孩子”。① if(y->rchild!=NULL) { y->rchild->parent=x; } y->parent=x->parent;//本行代码以及下面的if-else结构表达的过程是“y成为该子树新的根”。② if(x->parent==NULL) { root=y; } else if(x==x->parent->rchild) { x->parent->rchild=y; } else x->parent->lchild=y; y->rchild=x;//本行代码以及下面一行都表达了“x成为y的左孩子”。③ x->parent=y; // 结点的位置变了, 要更新结点的高度值 return y; } //非递归的插入函数 void ITERATIVE_TREE_INSERT(struct Treap*&root,struct Treap*z) { struct Treap*y=NULL; struct Treap*x=root; while (x) { y=x; if (z->key<x->key) x=x->lchild; else x=x->rchild; } z->parent=y; if (y==NULL) { root=z; } else if(z->key<y->key) { y->lchild=z; } else y->rchild=z; z->lchild=z->rchild=NULL; struct Treap*p=y; while (z->parent!=NULL&&z->priority<z->parent->priority) {//和AVL树类似,从新插入结点往上查询满足条件的结点做旋转,直到满足Treap树。 if (z->parent->lchild==z)//z是左孩子 { p=RIGHT_ROTATE(p); } else//z是右孩子 { p=LEFT_ROTATE(p); } p=p->parent; } } //中序遍历 void InOderTraverse(struct Treap *p) { if (p) { InOderTraverse(p->lchild); cout<<p->key<<" "<<p->priority<<endl; InOderTraverse(p->rchild); } } void main() { srand( (unsigned)time( NULL ) ); int i=0; char B[10]={'G','B','H','A','E','K','I','C','D','F'}; int a[10]={0}; while (i!=10) { struct Treap*z=new struct Treap[LEN]; z->key=B[i]; z->priority=RAND(a,i); a[i++]=z->priority; ITERATIVE_TREE_INSERT(root,z); } InOderTraverse(root); }
(d)正如题目中所说的。Treap_insert先执行一个查找然后做一些列旋转。查找时是从树根一直向下移动到叶子结点的一条简单路径上。而旋转则是从新插入结点往上移动到根结点,所以两者都要进行O(h)次迭代或者递归操作,其中h=O(lgn)(根据b部分),所以Treap_insert操作需要O(lgn)期望时间。
(e)根据Treap_insert函数的最后旋转循环知:若新结点z是其父的左孩子(x是z的右子树),则进行右旋转,右旋转是以左脊柱为轴进行的,旋转次数为D。若z是其父的右孩子(x是z的左子树),则以右脊柱为轴做左旋转。旋转次数C注意这里的z和题目中的x不一样。总旋转次数=C+D。
(f)若y是x左子树的右脊柱:
如图可知:根据堆的性质,y的优先级大于x优先级。根据二叉查找树性质,key[y]<key[x],而对于z,y的优先级小于z的优先级。其他情况都不满足y是x左子树的右脊柱的前提。
(g) 从关键字k到i有k-i+1个数据,也就是有(k-i+1)!种二叉查找树可能的结构。其中满足y是x左子树的右脊柱的结构有(k-i+1-2)!种,其中k和k的左孩子是位置固定不变的。那么k到i中满足f的概率就是g所给的答案。数学简化过程略。
(h)此处用到了期望值的定义。i有1到k-1个取值可能,所以对于这k-1种可能中的每种可能都有发生f部分的情况。这些情况做个期望∑就得到了期望值E(C).
(i)根据对称性,若y是x右子树的左脊柱,那么g的概率分析可以拿来直接用,而根据h. i的取值范围变成了1到n-k,有n-k种取值,分析过程和h类似。
(j)旋转次数=E(C)+E(D)=2-1/k-1/(n-k+1)<2,所以期望次数小于2次。旋转次数为常数次。