说到排序,我们不得不提及堆排序,说到堆排序,我们就不禁想到了完全二叉树!!!
别问我为什么会这么想,因为他们本来就是一家人!!!
所以在学习堆排序之前,我们要先学习以下完全二叉树的一些性质和用法:
/*
堆:完全二叉树
i:
左孩子:2*i+1
右孩子:2*i+2
父:(int)(i-1)/2
一个数组可以看作一个完全二叉树(堆)
大根堆
所有子树的最大值是这个根
小根堆
所有子树的最小值是这个根
注:大根堆不一定是从大到小排序的数组,但从大到小排序的数组一定是大根堆
*/
注意这里父坐标的求法:(int)(i - 1) / 2!
问题1:当i = 0的时候,父坐标是多少呢?也就是,当一个结点的坐标是0的时候,他的父节点的坐标是多少?
答案是:0!也就是不会出现索引越界的问题!!!
话不多说,我们先放代码:
首先堆有两个特点:
1.大根堆的形成:(我们把数组看成一个完全二叉树!再生成这个树的同时产生大根堆)
// 生成大根堆
void heapInsert(int *arr, int index)
{
//把新加入索引处的数不断和父比较,使之成为大根堆
while (arr[index] > arr[(index - 1) / 2])
{
arr[index] ^= arr[(index - 1) / 2] ^= arr[index] ^= arr[(index - 1) / 2];
index = (index - 1) / 2;
}
}
计算过程:
按照层序遍历的顺序(数组索引的顺序),依次对每个结点和其父节点(父节点再和父节点比较)进行比较,当遍历完整棵树的所有结点的时候这个完全二叉树就变成了堆。
2.进行挑选最大值!
因为大根堆的根节点就是最大值,所以我们直接得到i一个最大值10,之后我们在把10和树的最后一个结点数值交换,再把最后一个结点删掉,在重新生成一遍大根堆!!!
重新生成大根堆的方法:
//某个数在index位置,判断能否往下移动
void heapify(int *arr, int index, int heapsize)
{
int left = index * 2 + 1;
while (left < heapsize)
{
//两个孩子比,选出较大的孩子
int largest = left + 1 < heapsize && arr[left + 1] > arr[left] ? left + 1 : left;
//较大的孩子和父亲比,选出较大者的索引
largest = arr[largest] < arr[index] ? index : largest;
//如果父亲就是最大的,就跳出循环
if (index == largest)
{
break;
}
//把最大值移到父亲位置
swap(arr, index, largest);
index = largest;
left = index * 2 + 1;
}
}
最后,我们把这两个处理堆的方法合并一下,每一次都得到一个最大值,就实现了堆排序:
//swap
void swap(int* arr, int x, int y)
{
arr[x] ^= arr[y] ^= arr[x] ^= arr[y];
}
//堆排序
void heapsort(int* arr, int length)
{
int heapsize = length;
if (arr == NULL || heapsize < 2)
{
return;
}
for (int i = 1; i < heapsize; i++)
{
heapInsert(arr, i);
}
swap(arr, 0, --heapsize);
while (heapsize > 1)
{
heapify(arr, 0, heapsize);
swap(arr, 0, --heapsize);
}
}
!!!亲测可行!!!