目录
1、什么是堆?
堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于等于其左右孩子结点的值,称为小顶堆!
2、堆排序
堆排序算法就是利用堆(小顶堆或者大顶堆)进行排序的方法。
将待排序的序列构造成一个大顶堆,此时整个序列的最大值就是根节点。将它移走(跟堆的最后一个元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。
大顶堆排序成小顶堆:先把数据构造成一个大顶堆或小顶堆的完全二叉树,假设构造成如图1所示的大顶堆,根结点的值定为最大值,将20和90调换位置,假设数据的个数为n个,此前数据组成为完全二叉树,移走以后就不是完全二叉树,如图2所示,将剩余的n-1个值重新进行排列,再次排列成一个大顶堆,如图3所示,最后一个值就不算在内,其已经是最大值;再把80和30调换,将剩余的n-2个值重新进行排列,再次构造成大顶堆,把最大值放在后面,将剩下的值继续构造成大顶堆,一直到大顶堆里剩最后一个值,那这个值一定是最小值。
3、案例
将系列数据进行大顶堆排序
使用顺序结构进行存储,把一个无序的完全二叉树构造成一个大顶堆,先从最小的子树开始,每个叶子结点也是一个子树(8,9等单独结点),(4,8,9组成的也是子树),8结点也是子树,可以是大顶堆或者小顶堆,但是没有排序的必要,至少从有1个叶子的结点的子树开始,把所有有子结点的结点都遍历完,有子结点时才可以有比较。如4,从4开始,一直遍历到1,遍历到4,把(4,8,9)构造成一个大顶堆做比较,把最大值放到4的位置,从下往上开始,再把(3,6,7)构造成大顶堆作比较,把最大值放到3的位置,再把(4,2,5)构造成大顶堆,此时就和(4,8,9)有一定关系,4是里面最大的,取4,2,5中最大的值放在2的位置,如果5是最大的,那么5和2交换,如果4是最大的,4和2交换,那么2,8,9中2就不一定是最大的,2反过来后要继续遍历往下移动,和下面两个做比较,如果发现有比它更大的,则2往下移。(注:上述中数字为数据的编号)
实现的代码:添加Test脚本挂载
/****************************************************
* 功能:堆排序—顺序存储 序号代表编号 For循环步骤:
* 1、判断(4,8,9)i=4,maxNodeNumber=4,tempI=4,8>4,maxNodeNumber=8,8>9,最大编号还是8,maxNodeNumber!=tempI,4、8数据调换,maxNodeNumber=tempI=8,再次循环,不成立跳出循环
* 2、判断(3,6,7)i=3,maxNodeNumber=3,tempI=3,6不大于3,7不大于3,maxNodeNumber=tempI跳出循环
* 3、判断(2,4,5)i=2,maxNodeNumber=2,tempI=2,4>2,maxNodeNumber=4,5>4,maxNodeNumber=5,maxNodeNumber!=tempI,2、5数据调换,maxNodeNumber=tempI=5,再次循环,不成立跳出循环
* 4、判断(1,2,3)i=1,maxNodeNumber=1,tempI=1,2>1,maxNodeNumber=2,3>2,maxNodeNumber=3,1、3数据调换,maxNodeNumber=tempI=3,再次循环判断(3,6,7),6<3,7>3,maxNodeNumber=7,3、7数据调换
* 循环结束,数据排序成大顶堆
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
int[] date = { 50, 10, 90, 30, 70, 40, 80, 60, 20 };//存储开始的无序数据
void Start()
{
HeadSort(date);
foreach (int i in date)
{
Debug.Log(i + " ");
}
}
/// <summary>
/// 从最小的堆开始遍历,最小的堆构造成大顶堆,遍历所有的非叶子结点,最末的叶子结点的父亲肯定就是最后一个有子结点的最小的子树
/// 编号:n(9) 末叶的父亲:(n/2-1)(4) 末叶父类左子结点:(n-1)(8) 末叶父类右子结点(9)
/// 遍历从最后一个非子结点到1号 编号为案例的编号
/// </summary>
/// <param name="date"></param>
public static void HeadSort(int[] date) //传递排序的数组
{
//遍历这个数的所有非叶结点,挨个把所有的子树,变成大顶堆 date.lengte/2:非子结点(4)
for (int i = date.Length / 2; i >= 1; i--)
{
HeapAjust(i,date,date.Length);
//经过上面的方法,是把二叉树变成大顶堆
}
//利用大顶堆的方法对数据组进行堆排序
//首末数据交换,把剩余的再次构造成大顶堆,再交换,再构造,如此循环,直到i=1,循环结束
for (int i = date.Length; i > 1; i--) //i从最后一个数据开始,跟第一个数据进行交换,然后--循环,不需要=1,=1时就不需要排序
{
//把编号1和编号i位置交换,交换结束1位置存储最小值,i位置存储最大值
//1到(i-1)构造成大顶堆,再次位置交换
int temp1 = date[0]; //临时存储第一个数据
date[0] = date[i - 1]; //最后一个数据交换给第一个数据的位置
date[i - 1] = temp1; //第一个数据交换给最后一个数据的位置
HeapAjust(1,date,i-1);
}
}
//堆调整
private static void HeapAjust(int numberToAjust,int[]date,int maxNumber)//将要调整的结点,调整的数组,堆的最大编号
{
//从根结点开始,用根结点和下面的子结点做比较,找有没有比根结点大的,找到之后做交换,再次比较,一直到下面的没有比根结点大的
int maxNodeNumber = numberToAjust; // 4 最大结点的编号 结点里面编号最大
int tempI = numberToAjust; //如 4 和左右子结点做比较
while (true)
{
//把i结点的子树变成大顶堆
int leftChildNumber = tempI * 2; // 如 8 左子结点编号
int rightChildNumber = leftChildNumber + 1; // 如 9 右子结点编号
//leftChildNumber <= date.Length判断左孩子编号是否存在
//date[leftChildNumber - 1] > date[maxNodeNumber - 1]数据大于最大结点编号的数据 索引=编号-1
if (leftChildNumber <= maxNumber &&
date[leftChildNumber - 1] > date[maxNodeNumber - 1]) //60>30 60和30做调换
{
maxNodeNumber = leftChildNumber; //找到最大左子结点对应的编号
}
if (rightChildNumber <= maxNumber && date[rightChildNumber - 1] > date[maxNodeNumber - 1])
{
maxNodeNumber = rightChildNumber; //找到最大右子结点对应的编号
}
if (maxNodeNumber != tempI) //发现了一个比i更大的子结点,交换i和maxNodeNumber里面的数据
{
int temp = date[tempI - 1];
date[tempI - 1] = date[maxNodeNumber - 1];
date[maxNodeNumber - 1] = temp;
tempI = maxNodeNumber;
}
else //子结点没有比它更大 终止
{
break;
}
}
}
}