堆排序
第一次写blog,希望尽量能表达清楚。
堆排序是根据一个数组,可以把它想象成一个按层遍历的完全二叉树,不用在乎它左右孩子的大小关系,只注意孩子与双亲的大小关系,根据双亲的与孩子的大小关系可以分为大根堆 和小根堆。显而易见,大根堆就是双亲的值比孩子的值都大,而小根堆就是双亲的值比孩子的值都小,通俗一点的说,在双亲,左孩子,右孩子中如果双亲是最大的值就是大根堆,最小的值就是小根堆。
那搞清楚大根堆和小根堆后,可以把堆排序分成两个过程,一是调整(Adjust)就是把无序的完全二叉树(数组)调整成大根堆或小根堆,在此过程中是只针对父节点的;二就是排序(HeapSort),排序的过程就是把根节点(数组中的最值)与数组中的最后一个值相互交换并调整,在调整的过程向后交换的值不参与调整,就这样再一次调整后就是除了交换的值中的又一个最值,然后重复此过程。
还有一点值得注意的是:大根堆调整是排序最后是升序,而小根堆调整的最后是降序。很好理解,例如大根堆的大的在根节点,在排序中又与最后的值交换成升序。
不罗嗦了,上代码。
#include<stdio.h>
#include<stdlib.h>
#define lchild 2*nroot+1
#define rchild 2*nroot+2
void Adjust(int arr[],int length,int nroot)
{
int max;
for(max = lchild;max<length;max=lchild)
{
//先找到两个孩子中最大的
if(rchild<length)
{
if(arr[rchild]>arr[max])
{
max = rchild;
}
}
//拿最大的跟双亲比较,交换
if(arr[max]>arr[nroot])
{
printf("%d %d\n",arr[max],arr[nroot]);
arr[max] = arr[max]^arr[nroot];
arr[nroot] = arr[max]^arr[nroot];
arr[max] = arr[max]^arr[nroot];
//交换后继续交换 已交换的孩子
nroot = max;
continue;
}
else
{
break;
}
}
}
void HeapSort(int arr[],int length)
{
if(arr == NULL ||length <=0) return ;
int i;
for(i=length/2-1;i>=0;i--) //只调整双亲节点 (0-n/2-1 是双亲节点的位置)
{
Adjust(arr,length,i);
}
for(i=length-1;i>0;i--)
{
//arr[0]和最后的元素交换
arr[0] = arr[0]^arr[i];
arr[i] = arr[0]^arr[i];
arr[0] = arr[0]^arr[i];
Adjust(arr,i,0);
}
}
void Print(int arr[],int length)
{
int i;
for(i=0;i<length;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {6,5,4,7,4,10};
HeapSort(arr,sizeof(arr)/sizeof(arr[0]));
Print(arr,sizeof(arr)/sizeof(arr[0]));
return 0;
}
不得不说堆排序是所有排序中效率最高的一个算法了,尤其是在处理成千上万的数据的时候。堆排序在ACM中也是常常会遇到,往往是多数组合并排序,或者是多数组求前k个最大(小)值的问题。我们可以把她统称为Top(k)的问题。
下面我将用堆排序的思想求11个随机数中前5个最大的数。
思想:求5个最大的数,可以向创建一个五个数的小根堆,然后用根和后面的6个数依次比较,如果跟比随机的数小,则让根成为该随机数调整,继续直到完成6个数的比较。
#include<stdio.h>
#include <stdlib.h>
#include <sys/time.h>
void Adjust(int arr[],int nLength,int nRoot) //利用小根堆
{
int nMin;
for(nMin = 2*nRoot+1 ; nMin < nLength ; nMin = 2*nRoot+1)
{
if(2*nRoot+2 < nLength)
{
if(arr[2*nRoot+2] < arr[nMin])
{
nMin = 2*nRoot+2;
}
}
// 跟父亲交换值
if(arr[nMin] < arr[nRoot])
{
arr[nMin] = arr[nMin]^arr[nRoot];
arr[nRoot] = arr[nMin]^arr[nRoot];
arr[nMin] = arr[nMin]^arr[nRoot];
nRoot = nMin;
continue ;
}
else
{
break;
}
}
}
int FirstBig(int nBig,int nSmall) //大的交换,便于扩展
{
return nBig - nSmall;
}
void Insert(int arr[],int nLength,int (*rules)(int ,int ),int nInsertValue)
{
if((*rules)(nInsertValue,arr[0]) > 0)
{
arr[0] = nInsertValue;
Adjust(arr,nLength,0);
}
}
void Print(int arr[],int nLength)
{
for(int i=0;i<nLength;i++)
printf("%d ",arr[i]);
printf("\n");
}
int main()
{
//基础堆
int arr[5];
int nLength = 5;
// 随机产生 5 个数 放入堆中
for(int i=0;i<5;i++)
{
arr[i] = rand()%100;
}
// 调整 堆,使根节点为最小值
for(int i=nLength/2-1 ; i>=0 ; i--)
{
Adjust(arr,nLength,i);
}
printf("初始堆序列为:");
Print(arr,nLength);
int nInsert;
printf("插入的序列为:");
for(int i=0 ; i<10; i++)
{
nInsert = rand()%100;
Insert(arr,nLength,&(FirstBig),nInsert);
printf("%d ",nInsert);
}
printf("\n");
printf("插入调整完后的序列为:");
Print(arr,nLength);
printf("\n");
}