五、桶排序
有一个数量为Size个数的数组A,数组的值范围为(0 - Max),然后创建一个大小为Max+1的数组B,每个元素都为0.从头遍历A,当读取到A[i]的时候,B[A[i]]的值+1,这样所有的A数组被遍历后,直接扫描B之后,输出表B就可以了。然后再根据B来对A进行排序。
//获得未排序数组中最大的一个元素值
int GetMaxVal(int* arr, int len)
{
int maxVal = arr[0]; //假设最大为arr[0]
for (int i = 1; i < len; i++) //遍历比较,找到大的就赋值给maxVal
{
if (arr[i] > maxVal)
maxVal = arr[i];
}
return maxVal; //返回最大值
}
void BucketSort(int *numbers, int length){
if (numbers == NULL || length <= 0){
cout << "wrong input!";
return;
}
int size = GetMaxVal(numbers,length) + 1;
vector<int> bucket(size);
for (int i = 0; i < length + 1; i++){
bucket[i] = 0;
}
// 计算数组中每个元素出现的次数
for (int i = 0; i < length; i++){
int j = numbers[i];
bucket[j] += 1;
}
// 排序
int count = 0;
for (int i = 0; i < size; i++){
if (bucket[i] > 0){
for (int j = 0; j < bucket[i]; j++){
numbers[count] = i;
count++;
}
}
}
}
*
*
*
*
*
*
*
*
六、堆排序
最坏和平均时间复杂度为O(n*logn), 排序时,时间主要花费在前期构建,所以适合大量数据的排序。
简单选择排序在待排序的nn个记录中选择一个最小的记录需要比较n−1次,
这是查找第一个数据,所以需要比较这么多次是比较正常的,
但是可惜的是它没有把每一趟的比较结果保存下来,
这导致在后面的比较中,实际有许多比较在前一趟中已经做过了。
因此,如果可以做到每次在选择到最小记录的同时,
并根据比较结果对其他记录做出相应的调整,
那样排序的总体效率就会变得很高了。
堆排序(Heap Sort)就是对简单选择排序进行的一种改进,并且效果非常明显。
堆是具有下列性质的完全二叉树:
每个结点的值都大于或等于其左右孩子结点的值,
称为最大堆或者大顶堆;
或者每个结点的值都小于或等于其左右孩子结点的值,
称为最小堆或者小顶堆。
也就意味着根节点是要么是最大,要么是最小。
下图是一个例子(完全二叉树哦~),左:大顶堆 ; 右:小顶堆。
(1)用大根堆排序的基本思想
① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,
由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key
③由于交换后新的根R[1]违反堆性质,故应将当前无序区R[1~n-1]调整为堆。
然后再次将R[1~n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,
由此得到新的无序区R[1~n-2]和有序区R[n-1~n],
且仍满足关系R[1~n-2].keys≤R[n-1~n].keys,
同样要将R[1~n-2]调整为堆。
直到无序区只有一个元素为止。
(2)大根堆排序算法的基本操作:
①建堆,
建堆是不断调整堆的过程,
从len/2处开始调整,此处len是堆中元素的个数,
一直到第一个节点。建堆的过程是线性的过程。
从len/2到0处一直 调用 调整堆的过程,
相当于O(h1)+O(h2)…+O(hlen/2) 其中h表示节点的深度,
len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
你可能要问,为什么是从len/2 开始调整呢? ↓
这些黑节点共同的特点就是有孩子的结点。具体作用我们
在堆的调整函数里可见一斑。 ↓
②调整堆:
调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。
利用的思想是比较节点 i 和它的孩子节点 left(i),right(i),选出三者最大(或者最小)者,
如果最大(小)值不是节点i而是它的一个孩子节点,那便交换节点 i 和该节点,
然后再 调用 调整堆 过程,这是一个递归的过程。
调整堆的过程时间复杂度与堆的深度有关系,
是lgn的操作,因为是沿着深度方向进行调整的。
③堆排序:
堆排序是利用上面的两个过程来进行的。首先是根据元素构建堆。
然后将堆的根节点取出(一般是与最后一个节点进行交换),
将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,
这样一直到所有节点都取出。堆排序过程的时间复杂度是O(nlgn)。
因为建堆的时间复杂度是O(n)(调用一次);
调整堆的时间复杂度是lgn,调用了n-1次,
所以堆排序的时间复杂度是O(nlgn)[2]
注意
①只需做 n-1 趟排序,选出较大的 n-1 个关键字即可以使得文件递增有序。
②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。
堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前,
且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止
特点
堆排序(HeapSort)是一树形选择排序。堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系(参见二叉树的顺序存储结构),在当前无序区中选择关键字最大(或最小)的记录
算法分析
堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。
平均性能
O( n*log n)。
其他性能
由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
堆排序是就地排序,辅助空间为O(1).
它是不稳定的排序方法。(排序的稳定性是指如果在排序的序列中,存在前后相同的两个元素的话,排序前 和排序后他们的相对位置不发生变化)
——————-图片来自维基百科
<<<<<代码君—百度百科>>>>>>>>
#include <stdio.h> //亲测完美运行
//a[]是待调整的堆数组,i是待调整的数组元素的位置,
//length是数组的长度,
void HeapAdjust(int a[],int i,int Length)
{
int Child;
int Temp;
for( ;2*i+1 < Length ; i = Child)
{
Child=2*i+1;
//得到子结点中较大的结点。
if(Child<Length-1&&a[Child+1]>a[Child])
++Child;
//如果较大的子结点大于父结点那么把较大的子结点往上移动,替换它的父结点
if(a[i]<a[Child])
{
Temp=a[i];
a[i]=a[Child];
a[Child]=Temp;
}
else break; //否则退出循环
}
}
//堆排序算法
void HeapSort(int a[],int length)
{
int i;
//调整序列的前半部分元素,调整完之后第一个元素是序列的最大的元素
//length/2-1是最后一个非叶节点,此处"/"为整除
for(i=length/2-1;i>=0;--i)
HeapAdjust(a,i,length);
//从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
for(i=length-1 ; i > 0 ; --i)
{ //把第一个元素和当前的最后一个元素交换,
//保证当前的最后一个位置的元素都是在现在的这个序列之中最大的
a[i]=a[0]^a[i];
a[0]=a[0]^a[i];
a[i]=a[0]^a[i];
//不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值
HeapAdjust(a,0,i);
}
}
int main()
{
int i;
int a[]={9,8,7,6,5,4,3,2,1,0,345,465,587,79,3542,56,321,56,4657,43,4656,432};
HeapSort(a,sizeof(a)/sizeof(int));
for(i=0;i<sizeof(a)/sizeof(int);i++)
{
printf("%d \n",a[i]);
}
printf("\n ok\n");
return 0;
}
*
*
*
*
*
*
*
<<<<<代码君:来自大话数据结构>>>>>
二叉树的性质 五 (后面会用到这个性质)
如果有一颗有n个节点的完全二叉树的节点按层次序编号,对任一层的节点i(1<=i<=n)有
1.如果i>1,则其双亲节点为[i/2],向下取整
2.如果2i>n那么节点i没有左孩子,否则其左孩子为2i
3.如果2i+1>n那么节点没有右孩子,否则右孩子为2i+1
void HeapSort(SqList *L)
{
int i;
for(i = L->length/2 ; i > 0 ; i--)
HeapAdjust( L , i , L->length);
for (i = L->length; i > 1; i--)
{
swap(L,1,i);
HeapAdjust(L,1,i-1);
}
}
/*已知L->r[s ~ m] 中记录的关键字除了 r[s] 之外均满足堆的定义
本函数调整L->r[s] 的关键字,使得L->r[s ~ m]成为一个大顶堆*/
void HeapAdjust(SqList *L,int s;int m)
{
int temp,j;
temp = L->r[s];
for(j = 2*s ; j <= m ;j = j*2) /*沿着关键字较大的向下筛选*/
{
if( j < m && L->r[j] < L->r[j+1] )
++j; /*j 较大的记录的下标*/
if(temp >= L->r[j])
break;
L->r[s] = L->r[j];
s = j;
}
L->r[s] = temp; //插入
}
/*正式排序*/
for(i = L->length ; i>1 ;i--)
{
swap(L,1,i);
HeapAdjust(L,1,i-1); /*L->r[1 ~ r-1]重新调整为大顶堆*/
}
解析 ↓ :
鸣谢:
<大话数据结构>
htp://ccc013.github.io/2016/11/20/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93/?ref=myread