数据结构:堆的构建与排序思想(C语言)

1.什么是堆?

定义:

堆通常是可以看作一棵树的数组对象,堆是非线性数据结构,相当于一维数组。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

在这里插入图片描述

下面我们将会学习:

  • 建初堆:如何将一个无序序列建成一个堆?
  • 调整堆:去掉堆顶元素,在堆顶元素改变之后,如何调整剩余元素成为一个新的堆?

因为建初堆要用到调整堆的操作,所以下面先讨论调整堆的实现。

2.调整堆

先看一个例子,如图a是一个堆,将堆顶元素97和堆中最后一个元素38交换后,如图b所示。由于此时除根节点外,其余节点均满足堆的性质,由此仅需自上至下进行一条路径上的结点调整即可。首先以堆顶元素38和其左、右子树根结点的值进行比较,由于左子树根节点的值76大于右子树根节点的值65,则将堆顶元素38与76交换;由于38替换了76之后,破坏了左子树的“堆”,则需进行和上述相同的调整直到叶子节点,那么此时左子树就满足了堆的性质,调整后的状态如图c所示。重复上述过程,将堆顶元素76和堆中最后一个元素27交换,此时右子树是被破坏的“堆”,则向上面一样进行调整,最后得到新堆。

在这里插入图片描述
上述过程就像过筛子一样,把较小的关键字逐层筛选下去,而较大的关键字逐层选上来。因此,称此为“筛选法”。

假设r[s+1] ~ r[m]已经是堆的情况下,按“筛选法”将r[s] ~ r[m]调整为以r[s]为根的堆,算法实现如下:

算法步骤:

从r[2s]和r[2s+1]中选出关键字较大者,假设r[2s]的关键字较大,比较r[s]和r[2s]的关键字。

  1. 若r[s].key>=r[2s].key,说明以r[s]为根的字数已经是堆,不必做任何调整。
  2. 若r[s].key<r[2s].key,交换r[s]和r[2s]。交换后,以r[2s+1]为根的子树仍为堆,如果以r[2s]为根的子树不是堆,则重复上述过程,将以r[2s]为根的子树调整为堆,直至进行到叶子结点为止。

伪代码:

      void HeapAdjust(Sqlist &L,int s,int m)
      {
    
    //假设r[s+1] ~ r[m]已经是堆,将r[s] ~ r[m]调整为以r[s]为根的大根堆
        rc= L.r[s];									 //记录堆顶
        for(j=2*s;j<=m;j*=2){
    
       					//找出左、右子树的根谁更大,则向其方向下筛选;因为较小方的的堆没有被破坏,不用处理
          if(j<m&&(L.r[j].key<L.r[j+1].key))  ++j;  //j为key较大的记录的下标,如果满足if条件,说明右子树的根将和堆顶元素交换,否则是左子树的根与堆顶交换
              if(rc.key>=L.r[j].key)  break;		//rc应该插入再位置s上
              L.r[s]=L.r[j];
              s=j;
        }
        L.r[s]=rc;									//将调整前的堆顶元素插入s位置上
      }

3.建初堆

要将一个无序序列调整为堆,就必须将其所对应的完全二叉树中以每一个结点为根的子树都调整为堆。显然只有一个节点的树必然是堆,而在完全二叉树中,所有序号大于[n/2]的结点都是叶子,因此以这些结点为根的子树均已是堆。这样,只需利用筛选法,从最后一个分支结点[n/2]开始,依次将序号为[n/2]、[n/2]-1,…、1的结点作为根的子树都调整为堆即可。

算法步骤:

对于无序的r[1] ~ r[n],从i=n/2开始,反复调用筛选法HeapAdust(L,i,n),依次将以r[i],r[i-1],…,r[1]为根的子树调整为堆。

伪代码:

void CreateHeap(Sqlist &L)
{
    
    	//把无序序列L.r[1] ~ L.r[n]建成大根堆
	n = L.length;
	for(i=n/2;i>0;--i){
    
    
		HeapAdjust(L,i,n);
	}
} 

4. 堆排序

思想:利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得当前无序的序列中选择关键字最大(或最小)的记录变得简单。下面讨论用大队根进行排序,堆排序的步骤如下:

  1. 按堆的定义将待排序序列r[1…n]调整为大根堆(这个过程称为建初堆),交换r[1]和r[n],则r[n]为关键字最大的记录。
  2. 将r[1] ~ r[n-1]重新调整为,接着交换r[1]和r[n-1],则r[n-1]为关键字次大的记录。
  3. 循环n-1次,直到交换了r[1]和r[2]为止,得到了一个非递减的有序序列r[1] ~ r[n]。

同样,可以通过构建小根堆得到一个非递增的有序序列。

堆排序:

void HeapSort(SqList &L)
{
    
    
	CreateHeap(L);				//把无序序列L.r[1] ~ L.r[L.length]建成大根堆
	for(i=L.length;i>1;--i){
    
    
		x=L.r[1];				//将堆顶记录和当前未经排序子序列L.r[1] ~ L.r[i]中最后一个记录进行互换
		L.r[1]=L.r[i]
		L.r[i]=x;
		HeapAjust(L,1,i-1);		//将L.r[1] ~ L.r[i-1]重新调整为大根堆
	}

}

出自C语言数据结构第二版

猜你喜欢

转载自blog.csdn.net/weixin_43334673/article/details/109687253