都说堆排序是时间复杂度最小的排序算法,讲了各种堆排序的好,我这里就不赘述了,今天花了一个下午和半个晚上的时间算是差不多研究明白了堆排序。其实堆排序的逻辑并不是特别复杂,如果理解了就会觉得没有那么难,但是写代码的实现的时候,还是需要考虑很多事情的。反正我是被绕晕过的~
首先,写一下我对堆排序思想的认识。堆排序主要分为以下两个步骤:
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;
}