树堆结构

来源:互联网 发布:武汉人工智能创业公司 编辑:程序博客网 时间:2024/06/05 23:58

树堆是节点带优先级的二叉查找树。

设计原因:

二叉查找树是为了让查找的平均效率保持在O(lgn)。节点带优先级的原因是为了让增删节点后的树的高度大致保持在ln(n),避免查找的效率退化成O(n)


树堆有两个特性

(1) 二叉查找树特性

1)如果v是u的左节点,则v的key小于u的key

2)如果v是u的右节点,则v的key大于u的key

(2)最小(大)堆特性

如果v是u的孩子,则v的priority大于 u的 priority


如下图:



树堆的构造:

假设插入关联关键字的结点x1,x2,...,xn到一棵树堆内。结果的树堆是将这些结点以它们的优先级(随机选取)的顺序插入一棵正常的二叉查找树形成的,亦即priority[xi] < priority[xj]表示xi在xj之前被插入。
在算法导论的12.4节中,其证明了随机构造的二叉查找树的期望高度为O(lgn),因而树堆的期望高度亦是O(lgn)。

由于旋转是O(1)的,最多进行h次(h是树的高度),插入的复杂度是O(h)的,在期望情况下h=O(log n),所以它的期望复杂度是O(log n)。

树堆插入操作:
1.按照二叉树的插入方法,将节点插入到树中
2.根据堆的性质(这里是最小堆)和优先级的大小调整节点位置:

1)先根据符节点选择插入的方向

2)根据优先级选择是否翻转节点

树堆删除操作:
1)找到键相等的结点
2)若该节点为叶子节点,则删除;
3)若该节点只有一个子节点,则将其叶子节点赋值给它;
4)若该节点有两个子节点,则进行相应的旋转,直到该节点出现2)或3)的情况,然后删除。

旋转情况包括:右旋转和左旋转

如图:


插入和删除需要翻转节点,每次翻转存在4个步骤,所以一次操作复杂度估算是4lnN。插入和删除操作较多的情况下并不划算,只有在查询较多的情况下才适用。

总的来说,树堆的实际应用价值有待思考。因为插入和删除节点的复杂度增加了,是为了保持查询的复杂度为O(lnN)。但二分查询是基于优先级的,而优先级又是随机的,这样查询的基准就成了一个问题。

树堆的构造、遍历、删除节点操作代码:

#include <stdio.h>#include <stdlib.h>#include <time.h>struct node_t{  node_t* left;//左节点  node_t* right;//右节点  int priority;//优先级  int key;//关键字};struct treap_t{ node_t* root;};//右旋转本节点void rotate_right(node_t* &node){ node_t* x = node->left;//记下左节点 node->left = x->right;//左节点的右节点作为本节点的左节点 x->right = node;//把本节点作为左节点的右节点 node = x;//把左节点作为本节点}//左旋转本节点void rotate_left(node_t* &node){ node_t* x = node->right; node->right = x->left; x->left = node; node = x;}//插入操作(父节点为空则建立父节点;比父节点小则建立左节点,否则右节点)void treap_insert(node_t* &root, int key, int priority){ //根节点为NULL,则创建结点作为根结点 if (!root) {   root = (node_t*)malloc(sizeof(struct node_t));   root->left = NULL;   root->right = NULL;   root->priority = priority;   root->key = key; } else if (key < root->key)//向左插入结点 {   treap_insert(root->left, key, priority);   if (root->left->priority < root->priority)// 如果本节点优先级大于左节点优先级则向右旋转(左节点则会转上来)     rotate_right(root); } else//向右插入结点 {   treap_insert(root->right, key, priority);   if (root->right->priority < root->priority)// 如果本节点优先级大于右节点优先级则向左旋转(右节点则会转上来)     rotate_left(root); }}//删除指定键的节点void treap_delete(node_t* &root, int key){ if (!root) {   if (key < root->key)     treap_delete(root->left, key);   else if (key > root->key)     treap_delete(root->right, key);   else//键相等的   {    if (!root->left && !root->right)//没有子节点的就直接释放    {    free(root);    root = NULL;    return;    }         if (root->left == NULL)//没有左节点       root = root->right;     else if (root->right == NULL)//没有右节点       root = root->left;     else     {       if (root->left->priority < root->right->priority)//判断左右节点的优先级,转向有衔接较大的那边       {        //先旋转节点,然后再删除节点         rotate_right(root);//右旋转本节点         treap_delete(root->right, key); //root->right 是旋转后的原来的节点       }       else       {         rotate_left(root);//左旋转本节点         treap_delete(root->left, key);//root->left 是旋转后的原来的节点       }     }   } }}//中序遍历void mid_order_traverse(node_t* root){if (root != NULL){  mid_order_traverse(root->left);  printf("%d\t", root->key);  mid_order_traverse(root->right);}}//计算树的高度int depth(node_t* node){  if(node == NULL)      return -1;  int l = depth(node->left);//左边的子树的高度  int r = depth(node->right);//右边的子树的高度  return (l < r)?(r+1):(l+1);// 返回本层的高度(最低层是0层)}int main(){treap_t* treap = (treap_t*)malloc(sizeof(struct treap_t));treap->root = NULL;int i = 0;srand(time(0));for (i = 0; i < 100; i++)  treap_insert(treap->root, i, rand());mid_order_traverse(treap->root);printf("\n高度:%d\n", depth(treap->root));printf("删除操作\n");for (i = 23; i < 59; i++)  treap_delete(treap->root, i);mid_order_traverse(treap->root);printf("\n树堆高度:%d\n", depth(treap->root));return 0;}





0 0