一、基本原理
//构建二叉树
//大堆顶调整
//堆顶往后放
//不停变堆顶
//关键规则
//最大非叶子节点
//数组长度/2 - 1
//父节点和叶子节点
//父节点为i
//左节点为2i+1
//右节点为2i+2
单词解释:二叉树 可以理解为只有两个节点的树 树是一种数据结构 叶子结点就是单独的一个个节点 像树上的叶子一样 堆排序并非要将元素按照树的形式进行存储 而是利用这种思想 处理我们的数据
二、图像化解释
下图是可以将数据想象成的结构示意,就是上面提到的二叉树的结构,堆排序的关键就是先从最大的非叶子节点开始先将自身的三角关系排清楚,然后其余的每个地方依葫芦画瓢就是,所以接下来我们看如何进行堆排序的
见下图,由前面说的堆排序的规则所知道,我们开始排序肯定是从最大的非叶子节点开始。也就是8/4-1=3,现在开始就默认他是当前节点的最大值(父节点为5两个子节点为3和9这三个节点中)。 然后开始父节点5和子节点3和9分别作比较,由二叉树的规则得知,位于父节点左边的子节点的下标为父节点的二倍-1右节点为二倍+2,这样很容易便得到了子节点的下标,方便做比较
本次排序我们仍旧采用升序排序。父节点开始一一和子节点做比较,先和左节点3比较,大于3所以最大值索引不会发生变化,仍旧是自己的索引。再和右节点做比较,小于9于是交换位置,最大值索引改为8,如下图所示。这个最大值的索引只在本三个元素的小二叉树有用,比较完毕后会重新赋值到下一个需要比较的位置上,换完位置后,原下标3--变为2,于是最大值索引变为2准备比较1 2 6,见下一张图
如这张图,这里换了一个最大值索引于是,又有了新的三角关系,所以要重新排序给他们三个
由于1<2所以此时的最大值索引改为5,此时不用立即改变二者的位置,只用记录最大值索引下标即可,如下图所示
然后比较此时最大值索引所指向的元素和另一个元素 做比较,如果比他大那么自己就是最大索引值,就把自身元素的值赋给父节点。如果比另一个子节点的值小的话,就将另一个子节点的索引值赋给最大索引值,因为此时他是最大的元素,如下图所示,然后交换子节点和父节点元素位置
此时已经将原数组中下标为2的元素的二叉树已经排好了,就可以继续减减 排1所在的二叉树的三个元素 7 9 4
这里原理就和前面的一样了,父节点和左子结点做比较,发现7<9于是记录自身左子结点的下标3将他作为此时的最大值索引,4<9于是此时的最大值索引仍然是3。比较完成后,于是交换元素位置。注意此时由于9和7换了位置,下层的二叉树的父节点发生了改变,此刻你应该重新往下走一步比较子二叉树中的三个元素的位置关系,因为你不知道你换下去的数是否就是三个元素中最大的,假如你换下去一个2是不是就要重复之前的的工作了
然后此时1又减减变成0,这个变的条件一定是你当前二叉树中已经是父节点为最大值或最小值才会变,不然你的移动是没有意义的。
这里的头顶的三个元素的就和前面的一模一样了,最大值索引为0的8先和左节点9比较,然后记录此时的最大值索引1然后继续和6比较,大于6所以最大值索引仍然是1然后将9和8换位置这样就完成了原理中的大堆顶调整,是不是每个父节点就是自身二叉树中的最大值。
下一步呢就需要完成堆顶往后放,咱们不是进行升序吗,最顶上的元素就是最大的,然后将他和最大索引的位置进行元素互换,也就是和5进行换位置,此时9是已经确定的元素,于是就将他和其他元素断开联系,不再参与到比较当中来,见下图
到这里已经确定了一个元素的位置,接下来继续换堆顶,就是从当前最大值索引开始比较,然后交换元素位置,此时的最大值索引是0于是就比较父节点0和子节点1和2的元素值5<8于是记录此时的最大值索引为1,8又和6进行比较,6<8于是最大值索引不会发生改变仍旧是1如下图所示,然后交换他们的位置。咦 你看8是不是就是剩余所有元素中最大的值,这就是堆排序。
前面咱们提到过,由于你改变了子节点的位置,那么一定要打破砂锅追到底,因为子节点不一定就是以他为父节点的二叉树的最大值,显然移动后5<7,所以要重复前面的工作继续换位置,改变最大值索引。如下图,5也换了位置,经过比较5可以坐在父节点的位置上面。(前面说过比较完的元素不会再次参与进来比较,所以此时产生了新的堆顶8)然后将8继续重复前面的操作,和下标为7的元素进行交换位置,见下一张图
这样咱们就又确定了一个元素,以后的步骤就是不断的继续比较最上面的二叉树,然后发生了变化就继续往下追究然后放元素确定位置,反复就可得到元素的有序排列
原理的部分就简单介绍到这里 接下来我们开始代码实现
三、代码实现
分为三步走
① //第一步:实现父节点和左右节点比较
static void HeapCompareare(int[] array, int nowIndex, int arraylength)
{
//通过传入的索引 得到他对应的左右叶子结点的索引
//可能算出来的会溢出数组的索引 一会处理
int leftIndex = 2 * nowIndex + 1;
int rightIndex = 2 * nowIndex + 2;
//用于记录较大数的索引值
int biggerIndex = nowIndex;
//先比左后比右
//不能溢出
if (leftIndex < arraylength && array[leftIndex] > array[biggerIndex])
{
//认为目前最大的是左节点 记录索引
biggerIndex = leftIndex;
}
//比较右节点
if (rightIndex < arraylength && array[rightIndex] > array[biggerIndex])
{
//认为目前最大的是右节点 记录索引
biggerIndex = rightIndex;
}
//如果比较过后 发现最大索引发生变化了 那就以为这要换位置了
if (biggerIndex != nowIndex)
{
//交换
int temp = array[nowIndex];
array[nowIndex] = array[biggerIndex];
array[biggerIndex] = temp;
//通过递归 看是否影响了叶子结点他们的三角关系
HeapCompareare(array, biggerIndex, arraylength);
}
}
②//第二步:构建大堆顶
static void BulidBigHeap(int[] array)
{
//从最后一个非叶子结点开始 不停地往前 去构建大堆顶
for (int i = array.Length / 2 - 1; i >= 0; i--)
{
HeapCompareare(array, i, array.Length);
}
}
③ //第三步:结合大堆顶和节点比较 实现堆排序 把堆顶不断地往后移动
static void HeapSort(int[] array)
{
//构建大堆顶
BulidBigHeap(array);
//执行过后最大的数就在顶上
//往屁股后面放 得到 屁股后面最后一个索引
for (int i = array.Length - 1; i > 0; i--)
{
//直接把堆顶的数放在最后一个位置即可
int temp = array[0];
array[0] = array[i];
array[i] = temp;
//重新进行大堆顶调整
HeapCompareare(array, 0, i);
}
}
相关解释:
/// <summary>
/// 堆排序
/// </summary>
/// <param name="arr">需要排序的数组</param>
/// <param name="nowIndex">当前作为根节点的索引</param>
/// <param name="arraylength">哪些位置没有确定</param>
完整代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 堆排序
{
internal class Program
{
static void Main(string[] args)
{
int[] arr = new int[] {8,7,1,5,4,2,6,3,9 };
HeapSort(arr);
foreach (var item in arr)
{
Console.WriteLine(item);
}
Console.ReadKey();
}
/// <summary>
/// 堆排序
/// </summary>
/// <param name="arr">需要排序的数组</param>
/// <param name="nowIndex">当前作为根节点的索引</param>
/// <param name="arraylength">哪些位置没有确定</param>
//第一步:实现父节点和左右节点比较
static void HeapCompareare(int[] array, int nowIndex, int arraylength)
{
//通过传入的索引 得到他对应的左右叶子结点的索引
//可能算出来的会溢出数组的索引 一会处理
int leftIndex = 2 * nowIndex + 1;
int rightIndex = 2 * nowIndex + 2;
//用于记录较大数的索引值
int biggerIndex = nowIndex;
//先比左后比右
//不能溢出
if (leftIndex < arraylength && array[leftIndex] > array[biggerIndex])
{
//认为目前最大的是左节点 记录索引
biggerIndex = leftIndex;
}
//比较右节点
if (rightIndex < arraylength && array[rightIndex] > array[biggerIndex])
{
//认为目前最大的是右节点 记录索引
biggerIndex = rightIndex;
}
//如果比较过后 发现最大索引发生变化了 那就以为这要换位置了
if (biggerIndex != nowIndex)
{
//交换
int temp = array[nowIndex];
array[nowIndex] = array[biggerIndex];
array[biggerIndex] = temp;
//通过递归 看是否影响了叶子结点他们的三角关系
HeapCompareare(array, biggerIndex, arraylength);
}
}
//第二步:构建大堆顶
static void BulidBigHeap(int[] array)
{
//从最后一个非叶子结点开始 不停地往前 去构建大堆顶
for (int i = array.Length / 2 - 1; i >= 0; i--)
{
HeapCompareare(array, i, array.Length);
}
}
//第三步:结合大堆顶和节点比较 实现堆排序 把堆顶不断地往后移动
static void HeapSort(int[] array)
{
//构建大堆顶
BulidBigHeap(array);
//执行过后最大的数就在顶上
//往屁股后面放 得到 屁股后面最后一个索引
for (int i = array.Length - 1; i > 0; i--)
{
//直接把堆顶的数放在最后一个位置即可
int temp = array[0];
array[0] = array[i];
array[i] = temp;
//重新进行大堆顶调整
HeapCompareare(array, 0, i);
}
}
}
四、总结
//构建二叉树
//大堆顶调整
//堆顶往后放
//不停变堆顶
//套路写法
//三个函数
//一个堆顶比较
//一个构建大堆顶
//一个堆排序
//重要规则
//最大非叶子节点索引:
//数组长度/2 - 1
//父节点和叶子节点索引
//父节点为i
//左节点为2i+1
//右节点为2i+2
//注意
//堆是一类特殊的树
//堆的通用特点就是父节点会大于等于所有子节点
//我们并没有真正的把数组变成堆
//只是利用了堆的特点来解决排序问题