我是靠谱客的博主 独特纸飞机,最近开发中收集的这篇文章主要介绍通俗易懂的讲解堆排序(含Gif图),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

堆的定义

堆排序是一种树形结构选择排序方法,其特点是:在排序过程中,将序列视为一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的关系,在当前无序区间中选择关键字最大(或最小)的元素。堆的定义如下:

n 个关键字序列 L[1,2,3...n] 称为堆,当且仅当该序列满足:

① L(i) <= L(2i) 且 L(i) <= L(2i+1)         ② L(i) >= L(2i) 且 L(i) >= L(2i+1)                   (1 <= i <= n/2)

满足第一种情况的堆称为小根堆(或小顶堆),满足第二种情况的堆称为大根堆(大顶堆)。在大根堆中,最大的元素存放在根结点中。对其任一非根结点,它的值小于等于其父亲结点的值。小根堆的定义刚好相反,根结点是最小元素。建立一个堆有两种方法,很多书籍把这两种方法称为向上调整和向下调整:

向下调整是让调整的结点与其孩子结点进行比较
向上调整是让调整的结点与其父亲结点进行比较

 

向上调整

向上调整需要从根结点开始建堆(以小根堆为例),基本思路:每添加一个元素,将该元素与其父结点进行比较,若新增结点小于父结点,则交换两个结点的值,然后继续与上一级的父结点进行比较。停止向上调整的条件是该结点大于父结点的值。现有序列 list [ ] = {87, 45, 78, 32, 17, 65, 53, 9} 分别用向上调整和向下调整进行建堆,看看这两种方法有什么区别。

向上调整建堆的过程:

添加元素 9 的向上调整 gif:

向上调整代码:

void AdjustUp(int a[],int size)
{                                //size是数组目前的大小
    int temp,flag=0;
    if(size==1)
        return;                  //此时是堆顶,不需要上调
    while(size!=1 && flag==0)    
    {
        if(a[size] < a[size/2])  //与父结点进行对比
        {
            temp = a[size];
            a[size] = a[size/2];
            a[size/2] = temp;
        }
        else
        {
            flag = 1;
        }
        size = size/2;           //更新编号i为它父结点的编号。
    }
}

void AdjustUp1(int a[], int k)
{//参数k为向上调整的结点,也是堆的元素个数
	if(k == 1)
		return;
	a[0] = a[k];
	int i = k/2;
	while(i > 0 && a[i] > a[0])  //若结点值小于父结点,则将父结点向下调
	{
		a[k] = a[i];              //父节点向下调
		k = i;
		i = k/2;                  //继续向上比较
	}
	a[k] = a[0];
}

 

向下调整

向下调整建堆是一个反复筛查的过程。n 个结点的完全二叉树,最后一个结点是第⎿n/2个结点的孩子(完全二叉树的性质)。对第n/2个结点为根的子树筛查(对于小根堆,若结点的关键字大于左右孩子中关键字较小者,则交换),使该子树成为堆。之后向前依次对各结点(n/2-1 ~ 1)为根的子树进行筛选,看该结点值是否小于其左右子结点的值,若不小于,则将左右子结点中的较小值与之交换,交换后可能会破坏下一级的堆,于是继续采取上述方法构造下一级的堆,直到以该结点为根的子树构成堆为止。反复利用上述调整方法建堆,直到根结点。根据原始数据建立的树形结构如下:

根据向下调整的思路,从第 4 个结点开始进行筛查建立小根堆:

向下调调整思路:将该结点与其孩子结点进行比较,选取其中最小进行交换,若该节点本身就是最小的结点,则不需要进行交换操作。如上图,需要将 9 和 32 进行交换。然后对第 3 个结点进行调整,该过程较为简单,所以省略。接下来展示第 2 个结点的调整情况。

对第 2 个结点进行调整时,需要交换 9 和 45。但是交换后发现 32 比 45 要小,所以还需要进行一次交换操作。该过程告诉我们,在交换父子结点后,子结点的堆属性可能被破坏,所以要对子结点进行调整(直至子结点为叶子结点为止)。以下是第 2 个结点最终的调整情况:

向下调整的最终形态:

向下调整 gif:

向下调整代码:

void heap_ajust_min(int *b, int i, int size)  //a为数组,size为堆的大小
{
    int lchild = 2*i;        //i的左孩子节点序号 
    int rchild = 2*i +1;     //i的右孩子节点序号 
    int min = i;             //记录根和左右子树中最小的数的下标
	int temp;
    if(i <= size/2)          //调整不需要从叶结点开始
    {
        if(lchild<=size && b[lchild]<b[min])
        {
            min = lchild;
        }    
        if(rchild<=size && b[rchild]<b[min])
        {
            min = rchild;
        }                    //两个if语句寻找三个结点中最小的数
        if(min != i)         //如果min不等于i,说明最小的值在左右子树中
        {
            temp = b[i];     //交换a[i]和a[min]的值
            b[i] = b[min];
            b[min] = temp;
            heap_ajust_min(b, min, size); //被交换的子树可能不满足堆的定义,需要对被交换的子树重新调整
        }
    }        
}
 

void build_heap_min(int *b, int size)      //建立小根堆 
{
    int i;
    for(i = size/2; i >= 1; i--)           //非叶子节点最大序号值为size/2,从这个结点开始调整
    {                                      //注意for中的循环条件(i = size/2; i >= 1; i--)
        heap_ajust_min(b, i, size);         
    }    
}

向下调整的时间与树的高度有关,为 O(h)。建堆过程中每次向下调整时,大部分结点的高度都比较小。我们很容易会发现向上调整和向下调整的最终结果有所不同,但都满足了小根堆的性质。向上调整和向下调整都能够完成建立堆的工作。区别在于,使用向上调整建堆需要从第一个元素开始,过程相当于每次插入一个元素,然后再进行向上调整的工作。向下调整充分的利用了完全二叉树的性质,从n/2⏌结点到根结点逐一筛查。它们的具体操作如下:

for(k = 1; k < size ; ++k)     //向上调整建堆
{
    AdjustUp(b, k);
}

for(i = size/2; i >= 1; i--)   //向下调整建堆      
{
    heap_ajust_min(b, i, size); 
}    

 

堆排序

应用堆进行排序的思路很简单:首先将存放在 L[1,2,3...n] 中的 n 个元素建成初始堆,由于堆本身的特点,堆顶元素就是最值。输出堆顶元素后,通常将堆底元素送入堆顶,此时根结点不满足堆的性质,经过向下调整后使其恢复堆的性质,再输出堆顶元素。重复操作,直至最后一个元素为止。

堆排序代码:

void heap_sort_min(int *a, int size)
{
    int i;
	int temp;
    for(i = size; i >= 1; i--)
    {
        temp = a[1];
        a[1] = a[i];
        a[i] = temp;               //交换堆顶和最后一个元素
        heap_ajust_min(a, 1, i-1); //再一次调整堆顶节点成为小顶堆
    }
} 

利用上述小根堆代码完成排序,会发现序列是倒序的,这是因为排序过程是从后往前不断调整小根堆的结果。如果想要获得升序序列,需要使用大根堆进行排序。

堆支持删除和插入操作,删除元素时,需要将最后一个元素放到堆顶,然后使用向下调整,使堆保持堆的特性。对堆进行插入操作时,先将新结点放在堆的末端,再对这个新结点进行向上调整操作。注意:插入元素时(小根堆为例),只需要调用一次向上调整函数便能恢复堆的属性。因为在插入之前,堆本身是完整的。此时若插入元素小于父结点的值,那么只需要交换两个结点而不需要考虑别的情况。这是因为之前满足堆性质,而插入了一个更小的值,交换后堆属性不变。具体可以看添加 9 的时候的 gif。可以根据需求,将向上调整和向下调整搭配着使用。例如:小根堆删除元素后使用向下调整恢复堆的性质。

完整代码:

#include<stdio.h>
#include<math.h>

void heap_ajust_min(int *b, int i, int size)  /*a为堆存储数组,size为堆的大小*/
{
    int lchild = 2*i;       //i的左孩子节点序号 
    int rchild = 2*i +1;     //i的右孩子节点序号 
    int min = i; /*存放三个顶点中最大的数的下标*/
	int temp;
    if(i <= size/2)          //假设i是叶节点就不用进行调整 
    {
        if(lchild<=size && b[lchild]<b[min])
        {
            min = lchild;
        }    
        if(rchild<=size && b[rchild]<b[min])
        {
            min = rchild;
        }
        if(min != i)
        {
            temp = b[i]; /*交换a[i]和a[max]的值*/
			b[i] = b[min];
			b[min] = temp;
            heap_ajust_min(b, min, size); /*被交换的位置曾经是大根堆,如今可能不是大根堆
			                            所以须要又一次调整使其成为大根堆结构*/ 
        }
    }        
}

void build_bheap_min(int *b, int size) //建立小堆
{
    int i;
    for(i=size/2; i >= 1; i--)      //非叶节点最大序号值为size/2
    {
        heap_ajust_min(b, i, size); 
    }    
}

void heap_sort_min(int *a, int size)
{
    int i;
	int temp;
    for(i = size; i >= 1; i--)
    {
        temp = a[1];
		a[1] = a[i];
		a[i] = temp;               //交换堆顶和最后一个元素
        heap_ajust_min(a, 1, i-1); //再一次调整堆顶节点成为小顶堆
    }
} 

void AdjustUp(int a[],int size)
{
    int temp,flag=0;
    int k;
    if(size==1)
        return;//此时是堆顶,不需要上调
    while(size!=1 && flag==0)
    {
        if(a[size] < a[size/2])
        {
            temp = a[size];
            a[size] = a[size/2];
            a[size/2] = temp;
        }
        else
        {
            flag = 1;
        }
        size = size/2;//更新编号i为它父结点的编号。
    }
}

void AdjustUp1(int a[], int k)
{//参数k为向上调整的结点,也是堆的元素个数
	if(k == 1)
		return;
	a[0] = a[k];
	int i = k/2;
	while(i > 0 && a[i] > a[0])  //若结点值小于父结点,则将父结点向下调
	{
		a[k] = a[i];              //父节点向下调
		k = i;
		i = k/2;                  //继续向上比较
	}
	a[k] = a[0];
}
 
int main(int argc, char *argv[])
{   
	int i,j,k;
	int count=1;
	int a[]={0, 87, 45, 78, 32, 17, 65, 53, 9};
	int b[]={0, 87, 45, 78, 32, 17, 65, 53, 9};
	int size = sizeof(a)/sizeof(int) -1;
	build_bheap_min(a, size);
	printf("向下调整建立小顶堆:n"); 
    count=1;
	for(i=0;i<=4;i++)
	{
		for(j=0;j<pow(2,i);j++)
		{
			if(count<=size)
			{
				printf("%d ",a[count++]);
			}else{
				break;
			}
		}
		printf("n");
	}


	for(k = 1; k < 9 ; ++k)
	{
		AdjustUp(b, k);
	}

	printf("向上调整小顶堆:n"); 
    count=1;
	for(i=0;i<=4;i++)
	{
		for(j=0;j<pow(2,i);j++)
		{
			if(count<=size)
			{
				printf("%d ",b[count++]);
			}else{
				break;
			}
		}
		printf("n");
	}
    return 0;
}

 

最后

以上就是独特纸飞机为你收集整理的通俗易懂的讲解堆排序(含Gif图)的全部内容,希望文章能够帮你解决通俗易懂的讲解堆排序(含Gif图)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部