算法学习之排序算法:堆排序

        要了解堆排序,首先要了解堆的概念,因为本文主要研究堆排序的算法,此处对数据结构堆只是给出概念:n个元素的序列{k1,k2,...kn},当且仅当满足如下关系时,称之为堆。

k[i] <= k[2i]且k[i] <= k[2i+1] (或k[i] >= k[2i]且k[i] >= k[2i+1]

比如:序列96、83、27、38、11、09(或12、36、24、85、47、30、53、91)都是堆。

       如果将堆对应的一维数组看成是一个二叉树,则堆的含义表明:完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。将上面的一维数组形式的堆改写成二叉树形式的堆如下:

                               96                                                                   12

                             /       \                                                              /       \

                          83        27                                                       36        24

                        /     \     /                                                          /     \      /    \

                      38   11  9                                                       85    47   30   53

                                                                                           /

                                                                                         91

       由上面给出的一维数组的堆和二叉树堆例子可以看出,若序列{k1,k2,...kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。


       堆排序的过程:若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又简称一个堆,则得到n个元素中的次小值。依次反复执行,使能得到一个有序序列。


实现堆排序需要解决两个问题:

1、由一个无序序列建成一个堆。

2、在输出堆顶元素之后,调整剩余元素成为一个新的堆。


       下面以序列{49、38、65、97、76、13、27、49}为例,分析如何进行建初始堆以及输出堆顶元素后如何调整建新堆。


        从一个无序序列建堆的过程就是一个反复“筛选”的过程。若将此序列看成是一个完全二叉树,则最后一个非终端结点是第[n/2]个元素,由此“筛选”只需从第[n/2]个元素开始。以构造“最小值堆”为例:


                         49

                      /        \                                        图a:因为n/2向下取整是4,第4个元素的值是97,

                   38         65                                    在最小值堆中非终端结点的值应该小于它

                 /      \      /    \                                    的左右孩子的值,此处97>49,因此将97与49交换。

              97     76  13    27                                 得到图b。

             /

          49 

                     (a)


                       

                         49

                      /        \                                        图b:按照顺序筛选第3个元素65,

                   38         65                                              因为65>13,所以将65与13交换,得到图c。

                 /      \      /   \                                    

              49     76  13    27                                 

             /

          97 

                        (b)


                         49

                      /        \                                        图c:按照顺序筛选第2个元素38,

                   38        13                                              因为38不大于其左右孩子,则筛选序列不变,得到图d。

                 /      \      /    \                                    

              49     76  65    27                                 

             /

          97 

                        (c)


                         49

                      /        \                                        图d:按照顺序筛选第1个元素49,

                   38        13                                              因为49>13,所以交换之,

                 /      \      /    \                                             又因为交换后的元素49仍然大于27,

              49     76  65    27                                         继续与27进行交换,得到图e,即为构造的堆。

             /

          97 

                        (d)


                         13

                      /        \                                        图e:构造的堆。

                   38         27                                             

                 /      \      /    \                                            

              49     76  65    49                                        

             /

          97 

                        (e)



构造出新堆之后,就可以选取堆顶的元素作为最小值,然后重新调整输出堆顶元素之后的剩余元素成为一个新的堆。从而取出次小值。仍然以上面构造出的堆为例:


                         13

                      /        \                                        图a:取出构造的堆顶元素13作为排序后

                   38         27                                             有序序列的第一个元素:{13}。

                 /      \      /    \                                            输出堆顶元素13之后,以堆中最后一个元素97替代之。

              49     76  65    49                                        得到图b。

             /

          97 

                       (a)


                         97

                      /        \                                        图b:将97置于根结点之后,它的左右子树(图中的绿色与红色标记)

                   38         27                                             仍然为堆。仅需要自上之下调整,使整个数为堆即可。首先以堆

                 /      \      /    \                                            顶元素和其左右子树根结点的值比较,由于右子树根结点的值

              49     76  65    49                                        小于左子树根结点的值且小于根结点的值,则将27和97交换之。

                                                                                  得到图c。

          13 

                        (b)


                         27

                      /        \                                        图c:由于97替代了27之后破坏了右子树的“堆”,则需要进行和上述

                   38         97                                             相同的调整,直至叶子结点,得到图d。

                 /      \      /    \                                            

              49     76  65    49                                        

                                                                                  

          13 

                       (c)


                         27

                      /        \                                        图d:此时的二叉树是一个堆,堆顶为n-1个元素中的最小值。取出

                   38         49                                             该n-1个元素中的最小值(n个元素中的次小值)。

                 /      \      /    \                                            重复前面的步骤,依次得到最小的值。从而完成堆排序。

              49     76  65    97                                        

                                                                                  

          13   27

                       (d)


注意:堆排序方法对记录数较少的文件并不值得提倡,但对n较大的文件还是很有效的。堆排序在最坏的情况下,其              时间复杂度为O(nlogn)。相对于快速排序来说,这是堆排序的最大优点。另外,堆排序仅需一个记录大小供交            换用的辅助存储空间。


示例代码(C语言描述):

/*********************************************************************
Author:李冰 date:2014-9-19
Email:libing1209@126.com
*********************************************************************/
typedef int HeapType;

#define LT(a, b) ((a) < (b))

//交换数据
void Swap(HeapType *a, HeapType *b)
{
	HeapType tmp = *a;
	*a = *b;
	*b = tmp;
}

//已知H[s,m]中记录的关键字H[s]之外均满足堆的定义,
//本函数调整H[s]的关键字,使H[s…m]成为一个大顶堆
void HeapAdjust(HeapType *H, int s, int m)
{
	HeapType rc = H[s];

	for(int j = 2 * s; j < m; j *= 2){  //沿关键字较大的孩子结点向下筛选
		if(j < m && LT(H[j], H[j + 1]))   //j为关键字较大的记录的下标
			++j;	
		if(!LT(rc, H[j]))           //rc应插入在位置s上
			break;

		H[s] = H[j];

		s = j;
	}

	H[s] = rc;                   //插入

}

void HeapSort(HeapType H[], int length)
{
	for(int i = length / 2; i > 0; i--)
		HeapAdjust(H, i - 1, length);   //把H[0…length-1]建成大顶堆


	for(int i = length; i > 1; i--){
		Swap(&H[0], &H[i - 1]);	//将堆顶记录和当前未经排序子序列H[0…i-1]中
                               //最后一个记录相互交换
		HeapAdjust(H, 0, i - 2);     //将H[0…i-2]重新调整为大顶堆
	}
}

测试代码如下:

int main(int argc, char **argv)
{
	HeapType H[] = {49,38,65,97,76,13,27,49};
	int length = sizeof(H) / sizeof(H[0]);

	HeapSort(H, length);
	for(int i = 0; i < length; i++){
		printf("%d ", H[i]);	
	}
	putchar('\n');

	return 0;
}

输出结果为:

13 27 38 49 49 65 76 97 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实验目的: 通过本实验,实现对堆排序算法的理解,掌握堆排序算法的原理以及实现方法,并掌握Java语言的编程技巧。 实验内容: 堆排序是一种树形选择排序方法,是对直接选择排序算法的有效改进。堆排序的基本思想是:将待排序的序列构造成一个大根堆或小根堆,堆顶元素为最大值或最小值,后将堆顶元素与最后一个元素交换,后把剩余的元素重新构造成堆。如此反复执行,直到排序完成。由于堆排序算法的时间复杂度为O(nlogn),因此在大数据量的情况下,堆排序是高效的。 堆排序算法的实现主要有两个步骤:构建初始堆和堆排序。其中,构建初始堆是将待排序的序列构建成一个大根堆或小根堆的过程,堆排序是不断将堆顶元素与最后一个元素交换,并重新调整堆的过程。 以下是Java语言实现堆排序的代码: ```java public class HeapSort { public static void heapSort(int[] arr) { int n = arr.length; // 构建初始堆 for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i); // 堆排序 for (int i = n - 1; i >= 0; i--) { // 将堆顶元素与最后一个元素交换 int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; // 调整堆 heapify(arr, i, 0); } } // 调整堆 public static void heapify(int[] arr, int n, int i) { int largest = i; int left = 2 * i + 1; int right = 2 * i + 2; // 找到左子节点和右子节点中的最大值 if (left < n && arr[left] > arr[largest]) largest = left; if (right < n && arr[right] > arr[largest]) largest = right; // 如果最大值不是根节点,则交换根节点和最大值,并递归调整子堆 if (largest != i) { int temp = arr[i]; arr[i] = arr[largest]; arr[largest] = temp; heapify(arr, n, largest); } } public static void main(String[] args) { int[] arr = { 64, 34, 25, 12, 22, 11, 90 }; heapSort(arr); System.out.println("排序后的数组:"); for (int i = 0; i < arr.length; i++) System.out.print(arr[i] + " "); } } ``` 实验结果: 经过测试,以上代码能够正确地对数列进行堆排序,得到正确的排序结果。 实验总结: 通过本次实验,我学习堆排序算法的原理和实现方法,并通过Java语言编写了相应的代码。堆排序算法具有高效的时间复杂度,在大数据量的情况下具有明显的优势。这次实验让我对Java语言的编程技巧有了更深刻的理解,也对算法的实现有了更深的认识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值