归并排序与统计排序

1. 归并排序概述

归并排序,其排序的实现思想是先将所有的记录完全分开,然后两两合并,在合并的过程中将其排 好序,最终能够得到⼀个完整的有序表。

归并思想:
        大问题,拆分成小问题、而且要求每个小问题都是独立的

1.1 归并排序的执⾏流程:

1. 不断地将当前序列平均分割成2个⼦序列;例如下⾯的序列,被分割成2个⼦序列,然后继续将这些⼦序列分割成⼦序列,直到不能再分割位置(序列中只剩⼀个元素)

2. 接下来,在不断的将两个⼦序列合并成⼀个有序序列;也就是说,刚刚是拆分,现在是合并

由于是不断的合并成⼀个有序序列,所以最终只剩下⼀个有序序列:

 

1.2 分割的过程

由于在分割时,都是将⼀个有序序列分割为2个两个⼦序列,并且该操作是重复执⾏的,所以肯定会使⽤到递归。由于是从中间进⾏分割,所以需要计算出中间的位置。所以实现流程是:

  1. 计算拆分的中间位置
  2. 分别继续对左边序列和右边序列进⾏拆分

1.3 合并的过程

在merge时,肯定会得到2个有序的数组。所以要做的事情就是将这两个有序的数组合并为⼀个有序的数组。现有两个有序的数组,然后根据这两个有序的数组合并为⼀个。

现在要将这两个有序序列合并为⼀个更⼤的有序序列,所以可以先⽐较两个序列的头元素,谁的值 ⽐较⼩,就先放⼊⼤序列中,利⽤两个索引,分别记录两个序列中待⽐较值的下标。然后将值⼩的序列下标右移⼀个单位,继续⽐较。最终将两个有序数组合并为⼀个的流程图如下:

合并后,会出现merge左边先结束,merge右边先结束的情况。

#ifndef MERGE_SORT_H
#define MERGE_SORT_H
#include "sortHelper.h"

void mergeSort(SortTable *table);

#endif
#include "mergeSort.h"

// merge合并
static void merge(SortTable *table, int left, int mid, int right) 
{
 // 1. 将左右任务的区域进行拷贝,方便从拷贝的空间里,按照归并的思想,向原空间填入有序的值
    int n1=mid-left+1;
    int n2=right-mid;
    // 分配aux1和aux2
    Element *aux1 = (Element*)malloc(sizeof(int) * n1);
    Element *aux2 = (Element*)malloc(sizeof(int) * n2);
    if(aux1==NULL || aux2==NULL)
    {
        printf("aux malloc error!\n");
        exit(0);
    }
    // 将左右任务的子空间进行拷贝
    for(int i=0;i<n1;i++)
    {
        aux1[i]=table->data[left+i];
    }
    for(int j=0;j<n2;j++)
    {
        aux2[j]=table->data[mid+1+j];
    }
    // 2. 将有序的临时aux1和aux2的空间,进行归并
    int i=0;
    int j=0;
    int k=left;
    // 归并任务的访问区域[left...right]
    while(i<n1 && j<n2)
    {
        if(aux1[i].key<=aux2[j].key)
        {
            table->data[k++]=aux1[i++];
        }
        else    {
            table->data[k++]=aux2[j++];
        }
    }
    // 把还有值的区域,填入到结果区域
    while(i<n1)
    {
        table->data[k++]=aux1[i++];
    }
    while(j<n2)
    {
        table->data[k++]=aux2[j++];
    }

    // 3. 释放临时空间
    free(aux1);
    free(aux2);
}

// 设计一个通用的任务,在某一个区间内,完成归并算法
// 归并任务的访问区域[left...right]
static void mergeLoop(SortTable *table, int left, int right) 
{
    // 递归出口
    if (left >= right) {
        return;
    }
    
    // 中间位置
    int mid = (left + right) / 2+left;
    // 合并
    mergeLoop(table, left, mid);
    mergeLoop(table, mid + 1, right);
    
    // 合并
    merge(table, left, mid, right);
}


// 归并排序
void mergeSort(SortTable *table)
{
    mergeLoop(table, 0, table->length - 1);
}

2. 计数排序

前⾯介绍的冒泡,选择,插⼊,归并,快速,希尔,堆排序,都是基于⽐较的排序,这些基于⽐较 的排序,有以下⼏个特点:平均时间复杂度最低的是O(nlogn)。

⽽计数排序,不是基于⽐较的排序。其中不基于⽐较的排序还有桶排序,基数排序等。

它们是典型的⽤空间换时间,在某些时候,平均时间复杂度可以⽐O(nlogn)更低,也就是说,在某 些时候,这种利⽤空间换时间的排序算法,性能⽐前⾯基于⽐较的排序算法更快。

2.1 计数排序

计数排序是在1954年由Harold H.Seward提出,适合对⼀定范围内的整数进⾏排序。

计数排序核⼼思想:
        统计每个整数在序列中出现的次数,进⽽推导出每个整数在有序序列中的索引。

我们以数组[1,4,1,2,5,2,4,1,8]为例进⾏说明。

第⼀步:建⽴⼀个初始化为 0 ,⻓度为 9 (原始数组中的最⼤值 8 加 1) 的数组count[]。

第⼆步:遍历数组 [1,4,1,2,5,2,4,1,8] ,访问第⼀个元素 1 ,然后将数组 标为 1 的元素加 1,表示当前 1 出现了⼀次,即 count[1] = 1 ; 依次遍历,对count进⾏统计。

2.2 计数排序的改进

上述算法的问题如下:

  1. ⽆法对负整数进⾏排序 
  2. 极其浪费内存空间 
  3. 是⼀个不稳定排序

2.2.1 优化1

只要不再以输⼊数列的 [最⼤值+1] ,作为统计数组的⻓度,⽽是以数列 [最⼤值-最⼩值+1] 作为统 计数组的⻓度即可。数列的最⼩值作为⼀个偏移量,⽤于计算整数在统计数组中的下标。

⽐如,假设下⾯的数组的数列:

95,94,91,98,99,90,99,93,91,92

统计出数组的⻓度为99-90+1=10,偏移量等于数列的最⼩值90。对于第1个整数95,对应的统计数组下标是95-90=5。

2.2.2 优化2

假设数据如下:

上述数据按照计数排序得到的结果如下:

如何判断5中的2个⼈谁是C谁是D那。那么需要进⾏变形。

这是如何变形的呢?其实就是从统计数组的第2个元素开始,每⼀个元素都加上前⾯所有元素之和。

这样相加的⽬的,是让统计数组存储的元素值,等于相应整数的最终排序位置的序号。例如,下标 是9的元素值为5,代表原始数列的整数9,最终的排序在第5位。

⾸先,遍历成绩表最后⼀⾏E的成绩,E是95,那么5下标元素是4,表示E在最后的排名在第4位, 同时将原来的值减1,表示下次再遇到95的成绩时,最终排名是第3。

3. 桶排序

其实桶排序重要的是它的思想,⽽不是具体实现,桶排序从字⾯的意思上看:

  1. 若⼲个桶,说明此类排序将数据放⼊若⼲个桶中。
  2. 每个桶有容量,桶是有⼀定容积的容器,所以每个桶中可能有多个元素。
  3. 从整体来看,整个排序更希望桶能够更匀称,即既不溢出(太多)⼜不太少。

假设有⼀个⾮整数数列,如下:
                                                4.5, 0.84, 3.25, 2.18, 0.5

桶排序的第1步,就是创建这些桶,并确定每⼀个桶的区间范围。

具体需要建⽴多少个桶,如何确定桶的区间范围,有很多种不同的⽅式。我们这⾥创建的桶数量等 于原始数列的元素数量,除最后⼀个桶只包含数列最⼤值外,前⾯各个桶的区间按照⽐例来确定。

                                区间跨度=(最⼤值-最⼩值)/(桶的数量-1)

第2步,遍历原始数列,把元素对号⼊座放⼊各个桶中。

第3步,对每个桶内部的元素分别进⾏排序。

第4步,遍历所有的桶,输出所有元素。

4. 基数排序

与基于⽐较的排序算法(归并排序、堆排序、快速排序、冒泡排序、插⼊排序等等)相⽐,基于⽐ 较的排序算法的时间复杂度最好也就是O(nlogn),⽽且不能⽐ O(nlogn)更⼩了。

计数排序(Counting Sort)的时间复杂度为O(n)量级,更准确的说,计数排序的时间复杂度 为 O(n + k),其中k表示待排序元素的取值范围(最⼤与最⼩元素之差加 1 )。那么问题来了,当这个元 素的的范围在 1 到 n^2 怎么办呢?

此时就不能⽤计数排序了奥,因为这种情况下,计数排序的时间复杂度达到了O(n^2)量级。

⽐如对数组 [170, 45, 75, 90, 802, 24, 2, 66] 这个⽽⾔,数组总共包含 8 个元素,⽽数组中的最⼤ 值和最⼩值之差为 802 - 2 = 800 ,这种情况下,计数排序就 ”失灵了“ 。

那么有没有那种排序算法可以在线性时间对这个数组进⾏排序呢?

基数排序(Radix Sorting) 。基数排序的总体思想就是从待排序数组当 中,元素的最低有效位到 最⾼有效位 逐位 进⾏⽐较排序;此外,基数排序使⽤计数排序作为⼀个排序的 ⼦过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值