16 - 12 - 17 十大排序算法总结(二) 之 桶排序,堆排序

五、桶排序

有一个数量为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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值