一、堆排序
1、概念和思想
选择排序是在待排序的n个记录中选择一个最小的记录需要比较n-1次,而且选择排序并没有把每一趟的比较结果保存下来,在后一趟的比较中,有多次比较可能在前面几趟已经比较过了。但是因为每一趟都没有保存比较结果,所以导致后面会有多次重复比较。
堆排(Heap Sort),是对选择排序进行的一种改进。
堆是具有下列性质的完全二叉树:每个节点的值都大于或等于其左右孩子结点的值,称为大根堆;每个结点的值都小于或等于其左右孩子结点的值,称为小根堆。
**堆排序(Heap Sort)**就是利堆进行排序的方法。它的基本思想是,将待排序的序列构造成一个大根堆。此时,整个序列中最大值就是堆的根节点。将它与最后一个结点交换,这时最后一个元素就变成了最大值。然后将剩余的n-1个元素重新构建成一个大根堆,反复交换重建大根堆,最终就可以得到一个有序的序列。
2、排序过程分析
事实上,堆排过程中的这棵二叉树并不存在,只是利用数组的下标来将其下标之间的关系看做是一棵二叉树节点之间的关系。
首先,有这么一组序列 {5,9,0,2,4,7,8,23,6,55,1,34}。
以0号下标的元素为根节点,将其整个序列看成一棵总共有12个结点的二叉树。
根据二叉树的特点可以得到:
已知父结点为 i,求孩子结点的下标:左孩子 = 2i + 1,右孩子 = 2i + 2
已知孩子结点为 i,求父节点的下标:( i - 1 ) / 2
接下来是构造大根堆:第一次构造大根堆的时候,要从最后一棵子树从后往前开始逐个调整,每次调整的过程都是从上到下。如图左,最后一棵子树的根节点是5号下标,倒数第二棵子树的根节点是4号下标,等等。
确定最后一个元素,再次调整,使剩下的元素仍是一个大根堆。
每重新调整一次大根堆,就会确定一次当前堆里的最大值,循环直至最后一个元素,就成了有序序列。
3、代码实现
static void HeapAdjust(int *arr, int start, int end) //O(log2n)
{
int tmp = arr[start];
int parent = start;
for (int i = 2 * start + 1; i <= end;i=2*i+1)
{
if (i + 1 <= end && arr[i] < arr[i + 1])//有右孩子,且右孩子大于左孩子
{
++i;//i保存当前左右孩子较大值的下标
}
if (arr[i] > tmp)//如果孩子的值大于根,那就交换
{
arr[parent] = arr[i];
parent = i;//改变parent。便于调整下面的
}
else
{
break;
}
}
arr[parent] = tmp;
}
void HeapSort(int *arr, int len) //O(nlog2n) O(1) 不稳定
{
//建立大根堆
int i = 0;
for (i = (len - 1 - 1) / 2; i >= 0; --i) //O(nlog2n)
{
HeapAdjust(arr, i, len - 1);
}
int tmp = 0;
for (i = 0; i < len - 1; i++) //O(nlog2n)
{
//交换
tmp = arr[0];
arr[0] = arr[len - 1 - i];
arr[len - 1 - i] = tmp;
//重新调整为大根堆
HeapAdjust(arr, 0, len - 1 - i - 1);
}
}
二、基数排序
1、概念和思想
基数排序是一种很神奇的排序思想,和变魔术差不多。反正我第一次看老师演示基数排序的过程时,看完真的是有点惊呆。
首先它的排序过程,不需要你和相邻的或者别的数进行比较和交换,它只需要根据自己的每一位数的大小,放入不同的桶里,最后再从桶里读取出来就OK。而要几次入桶,这就依赖于这组数据中最大数的位数了。
所以说,基数排序适合于较大数的排序,并且对空间上不做很多要求时使用。
2、排序过程分析
有这么一组序列 {323,514,267,872,398,120,418,672,913,752}
首先根据个位数的大小,入相应的桶。0入0号桶,1入1号桶···
然后根据桶的顺序和先入的顺序来按序拿出这一组数据。
得到 {120,872,672,752,323,913,514,267,398,418}
然后接下来按十位的大小,依次入桶。
然后再次读出数据。得到 {913,514,418,120,323,752,267,872,672,398}
接下来再按照百位数的大小,依次入桶。
再次读出数据,得到 {120,267,323,398,418,514,672,752,872,913}。
很明显,这组数据已经有序了。
真的是好神奇。
3、代码实现
//得到最大值的位数
static int GetMaxFigure(int *arr, int len)
{
int max = arr[0];
for (int i = 1; i < len; ++i)
{
if (arr[i] > max)
max = arr[i];
}
int count = 0;
while (max != 0)
{
max /= 10;
count++;
}
return count;
}
//得到十进制数字从右数第figure位的值
static int GetFigure(int num, int figure)
{
for (int i = 0; i < figure; ++i)
{
num /= 10;
}
return num % 10;
}
//figure为10进制数字的右数第figure位,0代表第一位,以此类推
static void Radix(int *arr, int len,int figure) //O(n)
{
//初始化10个队列
HNode head[10];
int i = 0;
for (; i < 10; ++i)
{
InitQueue(&head[i]);
}
int key = 0;
for (i = 0; i < len; ++i)
{
key = GetFigure(arr[i], figure);
Push(&head[key], arr[i]);
}
int j = 0;
for (i = 0; i < len;)
{
if (Pop(&head[j], &arr[i]))
{
++i;
}
else
++j;
}
}
//基数排序
void RadixSort(int *arr,int len)//假设最大数是d位的数字 O(d*n),空间:O(n) 稳定
{
int count = GetMaxFigure(arr, len);
for (int i = 0; i < count; ++i)
{
Radix(arr, len, i);
}
}