堆排序(大根堆)
初始化过程
按照数组下标的规律,先建立一个初始堆。
这个初始堆只需要将整个堆中所有元素的最大值放在堆顶
(这就是Adjust函数要做的,就是把所有元素中最大的那一个放在堆顶);
遍历的主过程
之后依次,从堆的最后一个元素开始,与堆顶元素交换。之后对剩余的元素再次进行Adjust。
这样的效果是,每次Adjust之后,堆的最后一个元素都是所有数据中最大的。
(注意Adjust中传入的总元素个数每次都减一,保证堆的最后一个元素每次都往前移动一位。)
这样的操作从后往前除了第一个结点都要做一遍。
void Adjust(int arr[],int parent,int length)
//arr[]是存放数据的数组
//parent是堆顶元素的下标(堆顶元素就是堆中最大的元素)
//length是要操作的堆的元素个数
{
int tmp=arr[parent];
//记录最大元素的值
int child=parent*2;
//child:根据二叉树的规律,下标为i的节点的左孩子下标为i*2
while(child<=length){
///因为要找到所有元素中最大的那一个,所以要遍历所有的节点
if(child+1<=length&&arr[child]<arr[child+1]){
child++;
//如果这个节点的还有右结点,并且这个右结点比左结点还要大。那么就对右结点进行操作
}
if(tmp>arr[child])break;
//如果父节点已经大于最大的孩子节点了,就说明现在的child对应的parent可以存放tmp
//这一点卡了很久。要注意这里循环的目的是一直把最大的child放在他的parent中,child变成下一次循环的parent,每次循环的时候其实只有parent在变化
//比方说,如果tmp一开始就是所有数据中的最大的,那么parent不会被改变,arr[parent]还是原来的值
else{
//如果孩子节点的确要比之前的最大值大,说明这个堆顶元素不应该放在这里
//这里卡了很久。初始化的时候是从整个堆的非叶子节点的最后一个自底向上遍历的,所以每一步都可以保证如果child比堆顶元素大,这个child已经是剩下所有中最大的了,就可以放在堆顶!
arr[parent]=arr[child];
//当前的孩子节点比堆顶的元素还要大,那么孩子节点才应该是堆顶的。此时把child的值给parent,此时tmp就是之前的堆顶还不知道放在哪,现在要找能让之前堆顶大的两个孩子节点,把他放到这两个孩子节点的parent中(这个parent是上一轮换过的child,这个child已经在上一轮赋值给了他的parent,所以其中的数据已经没有意义了,可以被覆盖)
parent=child;
child=parenr*2
};
}
arr[parent]=tmp;
//找到了parent,这个parent的特征是两个孩子节点都要比之前的堆顶元素要小,即找到了之前堆顶元素正确的位置
}
void HeapSort(int arr[],int n)//注意这个数组的有效下标的首位是1
{
for(int i=n/2;i>0;i--)
{ //初始化一个大根堆,符合完全二叉树的定义,所有的父节点都比他的子节点大
Adjust(arr,i,n);
{
for(int i=n;i>1;i--)
{
//从n开始一直到1,arr逐渐变成从小到大的顺序
int tmp=arr[i];
arr[i]=arr[1];
arr[i]=tmp;
//交换最后一个数字和堆顶的顺序,这样树的倒序就是从大到小了
Adjust(arr,1,i-1);
}
}