堆分为大根堆(双亲节点大于孩子节点)和小根堆(双亲节点小于孩子节点)
一般用数组来表示堆。当一个节点的下标为i时,它的左右孩子下标为2i+1,2i+2
为什么最后一个非叶子节点的位置是n/2-1?
①堆的最后一个非叶子节点若只有左孩子
②堆的最后一个非叶子节点有左右两个孩子
完全二叉树的性质之一是:如果节点序号为i,在它的左孩子序号为2i+1,右孩子序号为2i+2。
对于①左孩子的序号为n-1,则n-1=2*i+1,推出i=n/2-1;
对于②左孩子的序号为n-2,在n-2=2i+2,推出i=(n-1)/2-1;右孩子的序号为n-1,则n-1=2i+2,推出i=(n-1)/2-1;
很显然,当完全二叉树最后一个节点是其父节点的左孩子时,树的节点数为偶数;当完全二叉树最后一个节点是其父节点的右孩子时,树的节点数为奇数。
整数除不尽时向下取整,则若n为奇数时(n-1)/2-1=n/2-1。
因此对于②最后一个非叶子节点的序号也是n/2-1。
由于我将进行从小到大的排序,因此我将使用的是大根堆。
此处我所将要进行的堆排序思路大致如下:
1)假设待排序数据元素有n个,那么我们就从n/2-1下标处(最后一个非叶子节点的位置)开始倒着进行进行调整
2)当我们进行了n/2logn次调整之后,此时最大的值就在堆顶,交换堆顶数据和n-1处的数据,此时最大的数值就在数组的末尾了。
3)至此,我们已经大致上建立了一个大根堆,但是仍需进一步调整。由于n-1下标处已经是最大,这下我们从下标为0开始调整,直到n-2处,每调整一次则交换一次。
代码如下:
void Show_arr(int *arr, int n)
{
for (int i = 0; i<n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void swap(int *x, int *y)
{
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
基本思想:
先调整一遍堆, 为大根,然后从头依次再去遍历非叶子节点,调整堆
void Heap_Adjust(int *arr, int start, int end)
{
int tmp = arr[start];
int i = 2 * start + 1;
while (i <= end)
{
if (arr[i] < arr[i + 1] && i + 1 <= end)
{
i++;
}
if (arr[i] > tmp)
{
arr[start] = arr[i];
}
else
{
break;
}
start = i;
i = 2 * start + 1;
}
arr[start] = tmp;
}
void HeapSort(int *arr, int n)
{
//建立堆(大根堆)n/2*logN
for (int i = n / 2 - 1; i >= 0; --i)
{
Heap_Adjust(arr, i, n - 1);
}
swap(&arr[0], &arr[n - 1]);
//调整堆 n*logN
for (int j = n - 2; j>0; --j)
{
Heap_Adjust(arr, 0, j);
swap(&arr[0], &arr[j]);
}
}
int main()
{
int arr[] = { 16, 3, 6, 18, 2, 45, 4, 38, 2, 99, 34, 36, 27, 1 };
int n = sizeof(arr) / sizeof(arr[0]);
HeapSort(arr, n);
Show_arr(arr, n);
return 0;
}
运行结果如下: