树堆是节点带优先级的二叉查找树。
设计原因:
二叉查找树是为了让查找的平均效率保持在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;
}