一、堆
将一个数组arr = {12,23,25,17,48,49,56,65,73,89},也就是一个无序序列,看作是一个完全二叉树,则对应以下结构:
如果该结构是是堆的话,则堆顶元素(二叉树的根)必须为序列中的最大值或最小值,因而堆又分为大根堆和小根堆。
1.大根堆:整个堆中,左子结点的数据和右子结点的数据都比父结点的数据小。
2.小根堆:整个堆中,左子节点的数据和右子节点的数据都比父结点的大。
二、堆排序
先补充几个概念:
叶子结点:没有子节点的结点。
非叶子结点:至少有一个左子节点的结点。
已知父结点的位置为n,则左子节点的位置为2*n+1,右子节点的位置为2*(n+1)。
已知左子节点或右子节点的位置为n,父结点的位置则为(n-1)/2。
排序思想(升序):将一个含有n个数的无序序列调整为一个大根堆,利用大根堆的特性——根节点的数据为整个序列中最大的数据,得到最大数据之后,放入最后一个叶子结点中(置后),再对剩下的(n-1)个数据进行调整,再建一次大根堆,由此循环,直到得到一个递增序列。其中,最重要的部分就是调整为大根堆的具体方法。
1.将无序序列调整为一个大根堆
从最后一个树开始进行调整,对其中的左、右子节点的下标进行标记,记录较大值的下标,判断是否要和树中的父结点交换数据,再对改变了的左子节点或右子节点下的树进行调整(如果有的话)。
总之就是:从后往前多次调整,每次调整都是从上至下。
还是拿上面的arr = {12,23,25,17,48,49,56,65,73,89}举例,开始第一次建大根堆。
最后得到一个大根堆:
2.将最大值后置,再进行调整
第一次建成大根堆后,堆顶元素即为该序列中的最大值,因为我们要得到的是一个递增序列,所以将堆顶元素与最后一个叶子结点交换,再让剩下的(n-1)个数据进行调整,再建大根堆,不断地筛选出最大值,再置后,再建堆调整,直到得到一个完整的递增序列。
又得到剩下元素中的最大值,后置:
重复以上步骤,得:
调整完毕,得到递增序列:
即arr = {12,17,23,25,48,49,56,65,73,89}.
实现代码见下:
void Adjust(int *arr, int i, int pos)
{
int maxnode;//记录左、右结点中最大值的下标
for(maxnode=i*2+1; maxnode<=pos; maxnode=maxnode*2+1)
{
if(maxnode<pos && arr[maxnode]<arr[maxnode+1])//右结点存在,且比左结点的数据大,maxnode交换
{
maxnode++;
}
if(arr[maxnode] <= arr[i])//父结点的值大于左结点的值,跳出,不需要调整
{
break;
}
int tmp = arr[maxnode];//与父结点交换数据
arr[maxnode] = arr[i];
arr[i] = tmp;
i = maxnode;
}
}
void HeapSort(int *arr, int len)//递增排序
{
int index = len-1;//index表示元素下标
for(int i=(index-1)/2; i>=0; i--)//先把无序序列调整为大根堆,从最后一个非叶子结点开始
{
Adjust(arr, i, index);
}
for(int j=index; j>=0; j--)//开始堆排序
{
int tmp = arr[j]; //1.将父结点的数据交换到最后一个叶子结点
arr[j] = arr[0]; //2.交换之后,再对前i-1个元素进行调整->大根堆
arr[0] = tmp;
Adjust(arr, 0, j-1);
}
}
void Show(int *arr, int len)
{
for(int i=0; i<len; i++)
{
printf("%d ",arr[i]);
}
}
int main()
{
int arr[6] = {2,25,24,89,53,62};
int len = sizeof(arr)/sizeof(arr[0]);
Show(arr,len);
printf("\n");
HeapSort(arr,len);
Show(arr,len);
return 0;
}