树堆结构

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

设计原因:

二叉查找树是为了让查找的平均效率保持在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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值