数据结构--堆

数据结构--堆,可能我们只在《数据结构》这本书上接触过,实际应用中很少用到(至少我觉得是这样)。那么这到底是一种怎样的结构?引入它有什么好处?先来分析这两个问题

生活中,我们常可见到各种土堆,石堆等等,类似地,“堆”这个名词可以想到是顶端窄,下端越来越宽的金字塔结构。形象化的一种表示方式就是二叉树,当然它是一种特殊的二叉树,除最下层外都是满二叉树,并且最下层是从左到右依次排列没有空间隔(层序遍历不会遇到空节点

堆可以用一维数组表示,从而大大简化空间成本,例如

注意到堆结构的特殊化,如果规定第一个元素从下标1开始,那么容易得到:

root = 1

value(i) = x[i]

leftchild(i) = 2*i

rightchild(i) = 2*i+1

parent(i) = i/2

null(i) = (i<1) or (i>n)

关键是一个节点编号i和它的左右子节点(2i,2i+1)的关系,有了这一点,就能很方便按照“堆”逻辑,对数组进行操作

接下来是“堆”的另一种重要属性,节点的值大于或等于父节点的值(称为小顶堆,类似的有大顶堆)。如果元素[1...n]满足,就称为满足性质heap(1,n)

第一个问题:假设有已经具备性质heap(1,n-1)的数组,要向其插入第n个元素并调整,使得最后满足关系heap(1,n),该怎么做?

利用堆的两个属性,我们可以将这个新的元素n与它的父节点比较,如果它比较小就互换位置以“上浮”,否则认为这就是它合适的位置,结束(交换操作始终保持着堆的性质,所以siftup操作完后整个数组即满足性质),这一实现类似后面的“优先级队列”insert

同样,设第一个元素1需要调整,已满足heap(2,n),类似地可以用子节点比较后“下沉”的方式,siftdown

第二个问题:需要取出堆中的最小元素(这个好办),并使剩下的(2...n)保持heap性质,怎么办呢?

这个问题直观上来看可能会让人犯难,移除第一个元素后,第二个元素作为根,按这样的逻辑后续数据全部得调整……

堆的性质很简单,它并不要求数据是有序的(往往会想成是这样),而只需要满足节点值比子节点值小,基于此,可以将最后一个节点n交换到1的位置,然后用“下沉”的方式维持heap(1,n-1)性质即可完成,这一操作不会破坏原本堆结构的其它部分,简单方便。详见后面的extract

对堆的操作只需要这两个接口,便可以实现排序,优先级队列等算法问题:

//使用一维数组表示堆,以1开始,n个元素
int heaparray[MAXSIZE],n=0;

void insert(int num)
{
  int i,p;
  heaparray(++n) = num;
  for(i=n;i>1&&heaparray[p=i/2]>heaparray[i];i=p)
    swap(p,i);
}
int extract()
{
  int i,c;
  int min = heaparray[1];
  heaparray[1] = heaparray[n--];
  for(i=1;(c=2*i)<=n;i=c)
  {
    if(c+1<=n && heaparray[c+1]<heaparray[c])
      c++;
    if(heaparray[i]<=heaparray[c])
      break;
    swap(c,i);
  }
  return min;
}


代码中的int可以换成抽象类型T,以支持更多的数据类型

用堆结构实现优先级队列的特点:相比有序序列和无序序列的插入取出元素的不同复杂度(O(1),O(n)),两种操作的时间复杂度都是O(logn),使得平均操作复杂度最好

有了堆数据结构,堆排序也就应运而生啦:

n=0;
void pqsort(int x[])
{
  int i;
  for(i=0;i<n;i++)
    insert(x[i]);
  for(i=0;i<n;i++)
    x[i] = extract();
}

这样的堆排序简单易懂,但是需要额外的数据空间来存储堆数据结构

《编程珠玑》作者给出了一种精巧的算法,直接利用原数组直接操作的方式,不妨一看:

for i = [2,n]
  siftup(i)
for (i= n;i>=2;i--)
  swap(1,i)
  siftdown(i-1)


其中siftup,siftdown即对数组两端元素进行调整堆操作的函数。注意到不同,这里做的是“大顶堆”调整,x[1]始终是最大的元素

第一个for循环将整个数组的每个数进行调整使其满足(大顶)堆的性质

第二个for循环依次将堆的第一个元素(最大),与位置i交换,使得排序过程中恰好是这样的一个情况:[1..i]为堆,[i...n]为已排序的序列,循环过程中随着i的减小,堆部分越来越小,排序序列越来越大直至到整个数组

----总结

堆排序的确用的不多,因为我们往往不关注性能,只关注正确性。使用良好的数据结构以及正确的边界处理,正是衡量程序员编码质量好坏的标准

就我们目前写代码而言,在提高程序健壮性的同时,需要多积累经验,学习一些比较巧妙的处理方式(这些方式往往需要很强的循环体边界处理能力)

掌握基本的数据结构,很重要~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值