堆排序实现

1、堆排序算法描写叙述:

(1)定义

n个keyword序列Kl,K2,…,Kn称为(Heap)。当且仅当该序列满足例如以下性质(简称为堆性质):

1)ki<=k(2i)且ki<=k(2i+1)(1≤i≤ n/2)。当然。这是小根堆。大根堆则换成>=号。//k(i)相当于二叉树的非叶子结点,K(2i)则是左子节点。k(2i+1)是右子节点

2)若将此序列所存储的向量R[1..n]看做是一棵全然二叉树存储结构,则堆实质上是满足例如以下性质的全然二叉树:

树中任一非叶子结点的keyword均不大于(或不小于)其左右孩子(若存在)结点的keyword。

(2)用大根堆排序的基本思想

① 先将初始文件R[1..n]建成一个大根堆。此堆为初始的无序区

② 再将keyword最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n]。且满足R[1..n-1].keys≤R[n].key

③因为交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。

然后再次将R[1..n-1]中keyword最大的记录R[1]和该区间的最后一个记录R[n-1]交换。由此得到新的无序区R[1..n-2]和有序区R[n-1..n]。且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,相同要将R[1..n-2]调整为堆。

……

直到无序区仅仅有一个元素为止。

(3)大根堆排序算法的基本操作:

①建堆。建堆是不断调整堆的过程,从len/2处開始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程。从len/2到0处一直调用调整堆的过程。相当于o(h1)+o(h2)…+o(hlen/2) 当中h表示节点的深度。len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。

②调整堆:调整堆在构建堆的过程中会用到。并且在堆排序过程中也会用到。利用的思想是比較节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者。假设最大(小)值不是节点i而是它的一个孩子节点。那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,由于是沿着深度方向进行调整的。

③堆排序:堆排序是利用上面的两个过程来进行的。首先是依据元素构建堆。

然后将堆的根节点取出(通常是与最后一个节点进行交换)。将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到全部节点都取出。堆排序过程的时间复杂度是O(nlgn)。由于建堆的时间复杂度是O(n)(调用一次)。调整堆的时间复杂度是lgn,调用了n-1次。所以堆排序的时间复杂度是O(nlgn)。

2、堆的存储

一般用数组来表示堆,若根结点存在序号0处, i结点的父结点下标就为(i-1)/2。

i结点的左右子结点下标分别为2*i+1和2*i+2。

3、堆排序算法实现(最大堆)

#include <iostream>

void printArray(int theArray[], int n)
{
	for(int i = 0; i < n; i++)
	{
		std::cout << theArray[i] << " ";
	}
	std::cout << std::endl;
}

void adjustHeap(int theArray[], int n, int start)  //从start索引处结点開始调整
{
	if(start < 0 || start >= n)
		return;
	int fatherIndex;
	int leftIndex;
	int rightIndex;
	int tmp;

	fatherIndex = start;
	leftIndex = 2 * fatherIndex + 1;        //start索引处结点的左孩子的索引
	rightIndex = leftIndex + 1;             //start索引处结点的右孩子的索引 

	if(rightIndex < n)          //start索引处结点存在右孩子结点.
	{
		if(theArray[fatherIndex] < theArray[rightIndex])     //右孩子结点大于start索引处结点 。交换
		{
			tmp = theArray[fatherIndex];
			theArray[fatherIndex] = theArray[rightIndex];
			theArray[rightIndex] = tmp;
		}
	}
	if(leftIndex < n)       //start索引处结点存在左孩子结点,但不一定存在右孩子结点
	{
		if(theArray[fatherIndex] < theArray[leftIndex])     //右孩子结点大于左孩子结点树,交换
		{
			tmp = theArray[fatherIndex];
			theArray[fatherIndex] = theArray[leftIndex];
			theArray[leftIndex] = tmp;
		}
	}
	adjustHeap(theArray, n, start-1);         //再次递归调整start索引处的上一个结点
}

void constructHeap(int theArray[], int n)
{
	int start = n / 2;
	adjustHeap(theArray, n, start);                 //从length/2处開始调整
}

void heapSort(int theArray[], int n)
{
	if(n == 1)                                    //当未排序序列中仅仅剩一个元素时,直接跳出递归
		return;
	int length = n;

	//printArray(theArray, length);              //打印出每次建立最大堆之后,数组排列情况 

	//将最大堆的堆顶元素与堆末尾元素交换,并取出该元素作为已排序数组元素
	int tmp = theArray[0];
	theArray[0] = theArray[length-1];
	theArray[length-1] = tmp;

	//再次调整交换后的堆,使得为满足最大堆
	--length;                 //未排序序列长度减一
	int start = length / 2;
	adjustHeap(theArray, length, start);
	heapSort(theArray, length);             //递归进行堆排序
}

int main(int argc, char *argv[]) {
	int myArray[] = {5,90,28,4,88,58,38,18,19,20};
	int length = sizeof(myArray) / sizeof(myArray[0]);
	constructHeap(myArray, length);
	heapSort(myArray, length);
	printArray(myArray, length);
	return 0;
}

4、堆的时间复杂度和空间复杂度

堆的最好、最坏以及平均时间复杂度是O(nlogn).

堆的空间复杂度是O(1).

感想:刚開始实现建立堆时,以为须要建立一个二叉树辅助帮助,事实上这个所谓堆(即全然二叉树)仅仅是逻辑结构。真实的物理结构还是那个顺序数组。在建立大堆或小堆的过程中,均是以顺序数组为基础,而逻辑上是操作一颗全然二叉树,故全然不须要建立一个辅助全然二叉树。

时间: 2024-10-13 07:16:54

堆排序实现的相关文章

算法 排序NB二人组 堆排序 归并排序

参考博客:基于python的七种经典排序算法     常用排序算法总结(一) 序前传 - 树与二叉树 树是一种很常见的非线性的数据结构,称为树形结构,简称树.所谓数据结构就是一组数据的集合连同它们的储存关系和对它们的操作方法.树形结构就像自然界的一颗树的构造一样,有一个根和若干个树枝和树叶.根或主干是第一层的,从主干长出的分枝是第二层的,一层一层直到最后,末端的没有分支的结点叫做叶子,所以树形结构是一个层次结构.在<数据结构>中,则用人类的血统关系来命名,一个结点的分枝叫做该结点的"

排序——堆排序算法

堆排序利用的完全二叉树这种数据结构所设计的一种算法,不过也是选择排序的一种. 堆实质上是满足如下性质的完全二叉树:k[i]<=k[2*i]&&k[i]<=k[2*i+1]或者k[i]>=k[2*i]&&k[i]>=k[2*i+1], 树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字. 堆分大顶堆和小顶堆:k[i]<=k[2*i]&&k[i]<=k[2*i+1]是小顶堆,k[i]>=k[2

排序算法Java版,以及各自的复杂度,以及由堆排序产生的top K问题

常用的排序算法包括: 冒泡排序:每次在无序队列里将相邻两个数依次进行比较,将小数调换到前面, 逐次比较,直至将最大的数移到最后.最将剩下的N-1个数继续比较,将次大数移至倒数第二.依此规律,直至比较结束.时间复杂度:O(n^2) 选择排序:每次在无序队列中"选择"出最大值,放到有序队列的最后,并从无序队列中去除该值(具体实现略有区别).时间复杂度:O(n^2) 直接插入排序:始终定义第一个元素为有序的,将元素逐个插入到有序排列之中,其特点是要不断的 移动数据,空出一个适当的位置,把待插

[算法学习笔记]排序算法——堆排序

堆排序 堆排序(heapsort)也是一种相对高效的排序方法,堆排序的时间复杂度为O(n lgn),同时堆排序使用了一种名为堆的数据结构进行管理. 二叉堆 二叉堆是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树.二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子树和右子树都是一个二叉堆. 如上图显示,(a)是一个二叉堆(最大堆), (b)是这个二叉堆在数组中的存储形式. 通过给个一个节点的下标i, 很容易计算出其父节点,左右子节点的的下标,为了方便,

排序算法三:堆排序(Heapsort)

堆排序(Heapsort)是一种利用数据结构中的堆进行排序的算法,分为构建初始堆,减小堆的元素个数,调整堆共3步. (一)算法实现 1 protected void sort(int[] toSort) { 2 buildHeap(toSort); 3 for (int i = toSort.length - 1; i > 0; i--) { 4 CommonUtils.swap(toSort, 0, i); 5 adjustHeap(toSort, 0, i); 6 } 7 } 8 9 /**

算法----堆排序(heap sort)

堆排序是利用堆进行排序的高效算法,其能实现O(NlogN)的排序时间复杂度,具体算法分析可以点击堆排序算法时间复杂度分析. 算法实现: 调整堆: void sort::sink(int* a, const int root, const int end) { int i=root; while(2*i +1 <= end) { int k = 2*i+1; if(k+1<=end && a[k]<a[k+1]) k++; if(a[k] < a[i]) break;

快速排序和堆排序

1.快速排序 快速排序是不稳定的排序算法,平均时间复杂度O(nlgn).快速排序是利用了partition( )进行排序的.partition( )有两种实现形式,(1)利用两个指针一个头指针,一个尾指针,通过交换头尾指针所指的数进行排序; (2)一前一后两个指针同时从左往右进行遍历,如果前指针所遇到的数比主元小,则后指针右移一位,然后交换.Partition方法还可以用在很多地方,注意举一反三. //quicksort 时间复杂度O(n^2),不稳定排序 void quicksort (int

阿布学排序之堆排序

/** * 需求:堆排序的实现 * 知识储备: * 满二叉树:除叶子结点外的所有结点均有两个子结点,所有叶子结点必须在同一层上. * 完全二叉树: * 若二叉树的深度为h,除第h层外,其它各层(1~h-1)的节点数都达到最大个数,第h层所有结点都连续集中在最左边. * 完全二叉树是有满二叉树而引出来的,对于深度为K的,有N个结点的二叉树,当且仅当每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称它为完全二叉树. * * 二叉堆是完全二叉树或者是近似完全二叉树. * 二叉堆特性: *

面试必考三大排序算法C++代码(快速排序、归并排序、堆排序)

前两个为C++风格代码,后一个为C风格代码,除了输入输出,其它无差别,但C输入输出要比C++更快. 快速排序 1 #include <iostream> 2 using namespace std; 3 4 void swap(int num[], int i, int j) 5 { 6 int temp = num[i]; 7 num[i] = num[j]; 8 num[j] = temp; 9 } 10 11 int partition(int num[], int left, int

【算法导论】第六章、堆排序

基本过程: 1.保持最大堆的性质:假设两个子堆都满足,只需要根节点依次换下去,复杂度O(lg n) 2.初始化堆:后半段都是叶子,在前半段从后往前,依次执行上述最大堆性质的操作,名义复杂度是O(n lg n),但是有更精确的计算, 在高度为h的节点为O(h), 因此为 n\sigma (h / 2^h),其复杂度为O(n).(思想是高层复杂度才高,指数衰减,而复杂度增长是lg级别,因此被dominate掉了) 堆排序算法:先建最大堆,每次把顶上的位置与合适的位置互换,然后执行过程1, 共执行n次