对Treap的讲解,我们以问题作为导引,逐步解析Treap。看完题干之后即可开始手写Treap,题干所给出的信息足够写出一棵Treap,并且不可参考他人代码。
a. 数学归纳法:
优先级最小的位于根节点。
1. 当树为空时,插入一个节点必定为根节点,优先级最小
2. 假设高度为n - 1的Treap是唯一的
3. 对高度为n的Treap,优先级最小的节点为根节点,并且由于二叉搜索树的性质,根节点确定,左右子树的节点集合便确定。因为假设2,高度为n - 1的Treap是唯一的,所以高度为n的Treap是唯一的。
所以得证。
b. Treap为每个节点随机分配优先级,这形式上等价于随机构建二叉搜索树,我的上一篇博客详细的证明了随机构建二叉搜索树,期望高度及平均节点深度都为Θ(log n),所以Treap的期望高度为Θ(log n),查找一个值所花的时间为Θ(log n)。
c. 首先构建一个节点,并随机分配优先级,执行一次普通二叉搜索树的插入过程,当插入完成后,再进行优先级的修正,单旋转能够将节点上移一层,这样只有当前节点优先级比父节点小,我们就执行一次旋转,循环这个过程,即可完成修正过程。具体算法的Java代码如下:
private void insert(TreapNode z) { TreapNode y = NULLNODE; TreapNode x = root; while (x != NULLNODE) { y = x; if (z.element < x.element) x = x.left; else x = x.right; } z.parent = y; if (y == NULLNODE) root = z; else if (z.element < y.element) y.left = z; else y.right = z; z.left = NULLNODE; z.right = NULLNODE; z.priority = random.nextLong(); insertFixup(z); size++; } private void insertFixup(TreapNode z) { while (z != root && z.priority < z.parent.priority) { if (z == z.parent.left) rightRotate(z.parent); else leftRotate(z.parent); } }
d. 有b题可知,Treap的期望高度为Θ(log n) ,由于每次旋转都为常数时间,所以可以直接得出结论插入的期望运行时间为Θ(log n)
e. 数学归纳法:
1. 当插入一个新的节点时,不执行旋转,旋转次数为0,此时为新节点为叶节点,C + D = 0,成立。
2. 假设进行k - 1次旋转后C + D = k - 1
3. 当进行第k次旋转之后,假如进行右旋,则D加一,进行左旋,则C加一(可画图理解),所以可得C + D = k,成立,证明完毕。
f. Xik = 1即y在x的左子树的右脊柱中。由于y在x左子树中,所以y.key < x.key,由于x为y的祖先节点,所以y.priority > x.priority。由于y.key < z.key < x.key,所以z节点必定在x左子树的右脊柱中,且位于y节点的右子树中,所以y.priority < z.priority,证明完毕。
g. 由题可知,个体域选取为x及x的左子树的右脊柱到y节点。题中给出关键码取1...n,所以这其中有k - i + 1个节点,所以优先级排列数有(k - i + 1)!,因为Xik = 1,所以x和y的相对优先级已经确定,所以剩下的优先级排列数为(k - i - 1)!,所以可以得出:
h.
i. 将Treap左右子树进行交换,并将其重新分配关键码,此时x的k' = n - k + 1,此时左子树的右脊柱就等价于原来左子树的左脊柱,因为对于前一种情形我们已经证明过了,所以可得:
j. (最后两个为减号,打错了懒得重新编辑公式)
Addition Problem k. 分析Treap-delete是如何工作的,并给出其伪代码。
一个思路为施行普通二叉搜索树的删除操作,只不过当删除节点有两个孩子时将小的子节点旋转当前节点,这样逐步将删除节点下移至只有一个孩子或叶子节点,即可直接删除而不破坏堆的性质。Java代码如下:
private void delete(TreapNode node, int x) { if (node != NULLNODE) { if (x < node.element) delete(node.left, x); else if (x > node.element) delete(node.right, x); else { if (node.left == NULLNODE) { transplant(node, node.right); size--; } else if (node.right == NULLNODE) { transplant(node, node.left); size--; } else { if (node.left.priority < node.right.priority) rightRotate(node); else leftRotate(node); delete(node, x); } } } }
第二个思路为删除节点有两个孩子的时候把关键码变为右子树最小的节点的关键码,再将右子树最小的节点进行删除。可能这效率更高,但是有两个孩子时的删除过程中相当于修改了一个节点的优先级,这可能导致进行大量删除操作后Treap变得极不平衡,具体策略根据具体要求选取。
对于Treap的分析基本结束,我们可以看到,Treap等价于一棵随机构建二叉搜索树,也就代表他的所有操作复杂度为Θ(log n),并且插入需要旋转的此书不超过两次。看到这,你已经可以做到不看任何代码的情况下写出一棵完整的Treap了。但是很多人会说Treap如何分裂合并等等一些其他的操作,那么请独自完成下面这到红黑树的合并问题,并思考红黑树如何分裂,一句话,红黑树你能弄明白,Treap那不是信手捏来吗。
最后给出我用Java写的Treap源码
package jckeep.treap; import java.util.Random; class TreapNode { public int element; public long priority; public TreapNode left; public TreapNode right; public TreapNode parent; public TreapNode(int element, long priority, TreapNode left, TreapNode right, TreapNode parent) { this.element = element; this.priority = priority; this.left = left; this.right = right; this.parent = parent; } public TreapNode(int element, long priority) { this(element, priority, null, null, null); } } class Treap { private static TreapNode NULLNODE = new TreapNode(Integer.MAX_VALUE, Long.MAX_VALUE); private Random random = new Random(); private TreapNode root; private int size; public Treap() { root = NULLNODE; size = 0; NULLNODE.left = NULLNODE; NULLNODE.right = NULLNODE; NULLNODE.parent = NULLNODE; } public boolean isTreap() { return isTreap(root); } private boolean isTreap(TreapNode x) { if (x == NULLNODE) return true; else return (x.priority < x.left.priority && x.priority < x.right.priority); } public TreapNode search(int key) { return search(root, key); } public boolean inEmpty() { return size == 0; } public int size() { return size; } public void insert(int key) { TreapNode x = new TreapNode(key, random.nextLong(), NULLNODE, NULLNODE, NULLNODE); insert(x); } public void delete(int key) { delete(root, key); } private void transplant(TreapNode u, TreapNode v) { if (u.parent == NULLNODE) root = v; else if (u.parent.left == u) u.parent.left = v; else u.parent.right = v; v.parent = u.parent; } private void leftRotate(TreapNode x) { TreapNode y = x.right; x.right = y.left; if (y.left != NULLNODE) y.left.parent = x; y.parent = x.parent; if (x.parent == NULLNODE) root = y; else if (x.parent.left == x) x.parent.left = y; else x.parent.right = y; x.parent = y; y.left = x; } private void rightRotate(TreapNode x) { TreapNode y = x.left; x.left = y.right; if (y.right != NULLNODE) y.right.parent = x; y.parent = x.parent; if (x.parent == NULLNODE) root = y; else if (x.parent.left == x) x.parent.left = y; else x.parent.right = y; x.parent = y; y.right = x; } private TreapNode search(TreapNode x, int key) { if (x == NULLNODE) return null; else if (key < x.element) return search(x.left, key); else if (key > x.element) return search(x.right, key); else return x; } private void insert(TreapNode z) { TreapNode y = NULLNODE; TreapNode x = root; while (x != NULLNODE) { y = x; if (z.element < x.element) x = x.left; else x = x.right; } z.parent = y; if (y == NULLNODE) root = z; else if (z.element < y.element) y.left = z; else y.right = z; z.left = NULLNODE; z.right = NULLNODE; z.priority = random.nextLong(); insertFixup(z); size++; } private void insertFixup(TreapNode z) { while (z != root && z.priority < z.parent.priority) { if (z == z.parent.left) rightRotate(z.parent); else leftRotate(z.parent); } } private void delete(TreapNode node, int x) { if (node != NULLNODE) { if (x < node.element) delete(node.left, x); else if (x > node.element) delete(node.right, x); else { if (node.left == NULLNODE) { transplant(node, node.right); size--; } else if (node.right == NULLNODE) { transplant(node, node.left); size--; } else { if (node.left.priority < node.right.priority) rightRotate(node); else leftRotate(node); delete(node, x); } } } } }Treap
下面是我的Treap测试程序
package jckeep.treap; import jckeep.treap.TreapNode; import jckeep.treap.Treap; import java.util.*; import jckeep.sort.Sort; public class TestTreap { public static void main(String[] args) { Scanner input = new Scanner(System.in); Random random = new Random(); Treap a = new Treap(); int n = random.nextInt(1000); int[] b = new int[n]; for (int i = 0; i < n; ++i) { b[i] = random.nextInt(5000); a.insert(b[i]); } Sort.qsort(b, 0, n - 1); for (int i = 0; i < n; i++) { TreapNode tmp = a.search(b[i]); if (tmp != null) { System.out.print("Element: " + tmp.element + "\t Size: " + a.size() + "\tIsTreap: \033[42m" + a.isTreap() + "\033[0m\n"); a.delete(b[i]); } } } }
多次测试的结果: