目录
- 1、堆的概念
- 2、实现堆排序前的准备工作
- 3、堆排序的思路
- 3.1 第一步
- 3.2 第二步
- 4、结语
1、堆的概念
在了解堆排序之前我们先要了解什么是堆:一个按照完全二叉树的储存方式存储的一维数组我们称之为堆。
而堆又可以分为大堆和小堆。
大堆:二叉树中的父结点在数值上都比子结点大。
小堆:二叉树中的父结点在数值上都比子结点小。
下图在就是一组较为明显的大小堆。
故堆排序就是在堆的前提下进行排序。
2、实现堆排序前的准备工作
我们需要完成的准备工作是实现一个关于二叉树即堆的向下调整函数,此处我们写为HeapAdjustDown。
void Swap(int* p1,int* p2) //简单的交换函数
{int* tmp = *p1;*p1 = *p2;*p2 = tmp;
}void HeapAdjustDown(int* a,int n,int parent)//a代表数组的地址,n为数组元素个数,parent为当前元素所在下标。
{int child = parent * 2 + 1; //假设左孩子大于或小于右孩子.while(child < n) //结束条件为到达最后一层时,最后一层没有子。{if(child + 1 < n && a[child + 1] > a[child])//判断左孩子是否真的大于或小于右孩子,如果不是{ //让child代表右边孩子的下标。++child;}if(a[child] > a[parent]) //判断父与子的关系,如果父大于或小于子,让其交换位置。{Swap(&a[child],&a[parent])parent = child; //此处令交换的子的下标成为新的parent下标,即沿着交换路径一致进行//比较,直至到达最后一层。child = parent * 2 + 1;}else{break; //当不满足大小条件是就会进来,即已经按照需求将堆排列成了大堆或者小堆。}}
}
上面就是我们所完成的准备工作,其中
if(child + 1 < n && a[child + 1] < a[child])
if(a[child] < a[parent])
这两条判断语句中的数组内成员比较可以更换比较符号。
当使用 “ < ” 号时,第一条判断依据所筛选的时两个子中较小的一个。
第二条判断语句就是比较所选的子是否小于父,如果小于就进行交换。
这样进行调整后的堆就称之为小堆,即父皆比子小。
当使用 “ > ” 号时,第一条判断依据所筛选的时两个子中较大的一个。
第二条判断语句就是比较所选的子是否大于父,如果大于就进行交换。
这样进行调整后的堆就称之为大堆,即父皆比子大。
3、堆排序的思路
3.1 第一步
我们首先要做的就是将得到的数组进行调整,将其调整为大堆或者小堆。
故写一个函数来进行操作较为方便:HeapSort
void HeapSort(int* a,int n)//此处的n为数组的元素个数
{for(int i = (n - 1 - 1) / 2; i >= 0; i--)//此处为何从(n - 1 - 1) / 2开始,后面会有图解,耐心一点哦~{//然后调用我们的向下调整函数。HeapAdjustDown(a,n,i);}
}
到这里后,我们就完成了对数组的大堆或者小堆化,此处我们所进行的是大堆化处理。
下面就进行我们此截止到目前的看图说话:
假设我们所拥有的数组是{1,3,5,7,9,2,4,6,8,10}。
其在数组中和在堆中的存放方式如上图所示,我们按照上述代码执行。
而后我们在数组元素9的基础上进行向下调整。
这一步进行完后,我们的i–,此时的i = 3,也就是我们的元素7所在下标,故此时在数组元素7的基础上向下调整。
故循环的意义就是从最后一层的父开始进行向下调整,一致循环到根位置,后面我们直接展示过程图,不在进行比较讲解。
到此处我们的大堆构建也算完成了。
根据VS验证我们所进行的数组大堆化也是正确无误的。
此处又会有看官提出问题,我们为什么不从数组的头部开始进行向下调整呢,反而从尾部开始呢?
我们直接上图,当我们从头部开始时,进行向下调整,得到的却不是大堆。这是因为:
而后我们也同样直接展示过程图:
至此我们所得到的新数组也就和上述实验数组结果一致,为{5,9,4,8,10,2,1,6,7,3}。
总而言之,我们不从头部开始进行向下调整,是因为以这样的方式进行时,每一次调整只会调整当前位置及当前位置向后的数据,而不会向前进行比较,会造成不可避免地bug。
3.2 第二步
我们将数组大堆或小堆化后,就可以进行排序了,此时我们进行的是升序排列,排序的代码如下:
void HeapSort(int* a,int n)//此处的n为数组的元素个数
{for(int i = (n - 1 - 1) / 2; i >= 0; i--)//此处为何从(n - 1 - 1) / 2开始,后面会有图解,耐心一点哦~{//然后调用我们的向下调整函数。HeapAdjustDown(a,n,i);}int end = n - 1; //原理下文解释while(end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}
我们可以看到代码中创建了一个变量 “ end ”,其代表的含义为最后一个元素的数组下标,我们在前文中已经将数组进行了大堆化,故我们数组的第一个元素是大于其他元素的。
此时我们将第一个数和最后一个数据交换位置,这样最大的数就处在了末尾,然后按照同样的方式对前面九个数字进行大堆化,这样就又会有一个第二大的数字排列在第一位,然后进行下一次循环,然后我们将 end–,进入下一次循环。下面我们展示过程图,并不在对途中内容进行讲解:
依次向下进行最终就得到了如下的数组。
通过验证我们可以得到上述方法是可行且正确无误的。
从中我们也可以发现一个规律,我们通过创建大堆可以得到升序的序列,通过类比,我们明白也可以通过创建小堆可以得到降序的序列。
4、结语
十分感谢您观看我的原创文章。
本文主要用于个人学习和知识分享,学习路漫漫,如有错误,感谢指正。
如需引用,注明地址。