排序算法——堆排序

都说堆排序是时间复杂度最小的排序算法,讲了各种堆排序的好,我这里就不赘述了,今天花了一个下午和半个晚上的时间算是差不多研究明白了堆排序。其实堆排序的逻辑并不是特别复杂,如果理解了就会觉得没有那么难,但是写代码的实现的时候,还是需要考虑很多事情的。反正我是被绕晕过的~

首先,写一下我对堆排序思想的认识。堆排序主要分为以下两个步骤:

1、由待排序的数组,构建一个最大堆(或者最小堆)。最大堆的意思就是说,任何一个非叶子节点的值不大于(或者不小于)其左右孩子的值;

2、由最大堆的根元素于最末叶子结点交换,获取到最大值(因此根元素就是最大值)。将交换后的堆,重复1的步骤,变成最大堆。重复步骤2。直到取完所有元素,出去的元素就是已经排好序的。

下面结合图讲一下整个思路:

1、最大堆的构建过程。(图中a为待排序的元素,将它表示为一个二叉树如下所示,绿色数字表示该元素在数组中对应的下标)

(1)将待排序的数组用二叉树的形式表示出来,则对应于任意下标为 i 的结点i,其父节点下标为(i-1)/2,左孩子结点为2*i+1,右孩子结点下标对应为2*i+2;

(2)对于非叶子结点的元素,从下标最大的开始(本例中为i=4,a[4]=16的元素),判断其是否符合最大堆的要求,即该结点值是否大于它所有的叶子结点。

如果不满足条件,将该结点与其叶子结点中较大的数进行交换(如i=3时,a[3]=2<14,故交换2与14的值);

如果满足条件,比较下一个非叶子结点。(如I=4时,a[4]=16大于它所有的叶子结点,进行i=3时的判断)

一直重复上述过程,知道所有非叶子结点均遍历完成。

【注意】这里有一点要很注意,我在写程序的时候,一开始忽略了,也比绕晕了。那就是,比如i=1的时候,不简单是仅仅比较它与左右叶子结点的大小就可以了,当它的叶子结点是其他结点的根节点时,还需要进一步判断,它们是否符合最大堆的条件。这里是要用递归来实现的。(手画的图,有助于理清思路)



2、获得最大堆之后,就开始推排序。堆排序的基本思想,就是将根元素a[0]与下标最大的数进行交换(这一过程实际上是取出最大的元素,此时的根元素就是最大的元素了),再对a[0]~a[8]执行1的步骤,即将它们变成一个最大堆,然后再取根元素(此时的根元素就是该树a[0]~a[8]中最大的)。由此循环下去,直到只剩最后一个元素,即是最小的元素。(下图是排序过程的示意图)


思路大概就是这样,还是花了一个小时才差不多理清了。写代码能力真的有限,这个堆排序的代码,我没看任何别人的参考,硬是自己硬着头皮写的,写了我一个下午,晚上再调试修改,才可以用了,放在这里,欢迎大家批评指教。

#include<iostream>
#include<iomanip>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <list>
using namespace std;

void CreateMaxTree(int a[], int num, int i);//最大堆生成函数,参数包括数组,数组大小,根节点对应的下标
void swap(int a[], int x, int y);//交换函数,参数分别是数组,需要交换的两个数在数组中对应的下标

int main()
{
	int a[] = {4,1,3,2,16,9,10,14,8,7};
	int num = sizeof(a) / sizeof(int);
	int i_num = (num - 1) / 2;
	for (int k = num; k > 0; k--)//外层循环用于堆排序中,交换后依次取出前k个数作为新数组,调用生成最大堆的函数
	{		
		int i_num = (k - 1) / 2;//非叶子结点个数
		for (int i = i_num; i >= 0; i--)
		{
			CreateMaxTree(a,k,i);//循环调用最大堆生成函数
		}
		swap(a, k-1, 0);//特别注意这是k-1,否则会出现数组越界,此时k是数组的长度
	}

	for (int j = 0; j < num; j++)
	{
		cout << a[j] << " ";
	}

	return 0;
}

void CreateMaxTree(int a[], int num,int i)//函数实现的功能是,给定一个数组,数组大小,对应根节点的下标,将根节点下标之下的树变成一个最大堆
{	
	int father = i;
	int leftChild = 2 * i + 1;
	int rightChild = 2 * i + 2;
	int childmax = 0;
	if (leftChild<num)//判断是否存在左叶子
	{
		if (rightChild<num)//是否存在右叶子
		{
			if (a[leftChild]> a[rightChild])//获取左右叶子中较大的那个
			{
				childmax = leftChild;
			}
			else
			{
				childmax = rightChild;
			}
			if (a[father] < a[childmax])//将较大的数与根节点交换
			{
				swap(a, father, childmax);
			}			
		}
		else
		{
			if (a[father] < a[leftChild])
			{
				swap(a, father, leftChild);
			}			
		}
	}

	if ((leftChild * 2 + 1) < num) //判断此时的孩子结点,是否是其他结点的父结点
	{
		CreateMaxTree(a, num, leftChild);//如果是,则递归调用,重复上述的过程
	}

	if ((rightChild * 2 + 1) < num)//同理,对此时的右叶子操作
	{
		CreateMaxTree(a, num, rightChild);
	}	
}

void swap(int a[], int x, int y)
{
	int temp;
	temp = a[x];
	a[x] = a[y];
	a[y] = temp;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值