归并排序和计数排序

归并排序
(1)思想:

将待排序的元素序列分成两个长度相等的子序列,对每一个子序列排序,然后将她们合并成一个序列,合并两个子序列的过程的过程称为二路归并。

(2)步骤:

将数组按二分法分成两个区间,继续将区间进行递归划分,直到区间只有一个数为止;将相邻两个区间进行排序,使之合并为有序的区间;返回上一层递归,继续合并,直到全部被合并。

(3)图解:
这里写图片描述
(4)复杂度:

由于递归了logN层,每层排序了N常量个树,归并排序不依赖于待排序元素序列的初始输入状态,每次划分时两个序列的长度基本一致,所以归并排序的最好,最差和平均时间复杂度为O(N*log2^N).
它是一种稳定的排序算法。 由于申请了N个大小的空间进行临时存放,所以空间复杂度为O(1).

(5)代码实现:

//将子序列归并为一个完整的序列
递归版本:
void Merge(int* a,int first,int mid,int last,int *arr)
{
    int begin1 = first,end1 = mid;
    int begin2 = mid+1,end2 = last;
    int arr_index = 0;
    while(begin1 <= mid && begin2<= last)
    {
        if(a[begin1]<a[begin2])
        {
            arr[arr_index++] = a[begin1++];
        }
        else
        {
            arr[arr_index++] = a[begin2++];
        }
    }
    while(begin1 <= mid)
    {
        arr[arr_index++] = a[begin1++];
    }
    while(begin2<= last)
    {
        arr[arr_index++] = a[begin2++];
    }
    for(int i=0;i<arr_index;i++)
    {
        a[first+i] = arr[i];
    }
}
//递归不断划分区间
void Merge_sort(int* a,int first,int last,int* arr)
{
    if(first>=last)
        return;
    int mid = (first+last)/2;
    Merge_sort(a,first,mid,arr);//左有序边
    Merge_sort(a,mid+1,last,arr);//右边有序
    Merge(a,first,mid,last,arr);//将左右序列进行归并排序
}
bool MergeSort(int a[],int n)
{
    int *arr = new int[n];
    if(arr == NULL)
        return false;
    Merge_sort(a,0,n-1,arr);
    for(int i = 0;i < n;i++)
        cout<<a[i]<<" ";
    delete[] arr;
    return true;
}
int main()
{
    int a[] = {2,4,9,3,6,8,7,1,5};
    MergeSort(a,sizeof(a)/sizeof(a[0]));
    return 0;
}

递归方式将数组划分为不能再划分为止,再依次进行排序后归并,栈帧开销非常大,可以采用小区间优化的方式,在划分的过程利用插入排序直接完成此段空间的排序过程。较少递归次数当数据量很大的时候非递归就比较实用。
优化:

void Merge(int* a,int first,int mid,int last,int *arr)
{
    int begin1 = first,end1 = mid;
    int begin2 = mid+1,end2 = last;
    int arr_index = 0;
    while(begin1 <= mid && begin2<= last)
    {
        if(a[begin1]<a[begin2])
        {
            arr[arr_index++] = a[begin1++];
        }
        else
        {
            arr[arr_index++] = a[begin2++];
        }
    }
    while(begin1 <= mid)
    {
        arr[arr_index++] = a[begin1++];
    }
    while(begin2 <= last)
    {
        arr[arr_index++] = a[begin2++];
    }
    for(int i=0;i<arr_index;i++)
    {
        a[first+i] = arr[i];
    }
}
void MergeSort(int *a,int len)
{
    int size = 1;
    int left,right,mid;
    left=right=mid = 0;
    int *tmp=new int[len];
    while(size<=len-1)
    {
        left = 0;
        while(left+size<=len-1)
        {
            mid = left+size-1;
            right = mid+size;
            if(right>len-1)
                right = len-1;
            Merge(a,left,mid,right,tmp);
            left = right + 1;
        }
        size*=2;
    }
}

前面学习的一些排序算法均属于比较排序,接下来了解两种非比较排序的排序算法:

计数排序
(1)概念:

计数排序又叫鸽巢原理,是对哈希直接定址法的变形应用

(2)思想:

统计相同元素出现次数;根据统计结果将序列回收到原来的序列中。
哈希表是顺序的,所以我们统计完后直接遍历哈希表,将数据再重新写会原数据空间就可以完成排序。

(3)注意:

计数排序只适用于对数据范围比较集中的数据集合进行排序。如果数据过于分散就会浪费空间。比如有100个数,其中前99个数字都小于100,最后一个数是10000,如果开辟10000个数据大小的哈希表来进行排序就太浪费了。
还有一个问题就是数据集中段不是从0开始,比如有1000个数,1001~2000。此时哈希表如果开0~2000就太浪费空间,直接从1001开始开辟,就需要预先遍历一遍数组,求出数据范围:范围
= 最大值-最小值+1.这里2000-1001+1=1000,用1代表1001,1000代表2000,这样就节省了一半多的空间。

(4)复杂度:

时间复杂度:我们需要遍历两边数组,第一遍遍历原数据统计数据出现的次数,复杂度为O(N);
第二遍遍历哈希表,向原空间写数据,遍历了范围次,时间复杂度为O(range)。
空间复杂度:开辟了范围大小的辅助哈希表,所以空间复杂度为O(range)。

(5)图解:
这里写图片描述

(6)代码实现:

//计数排序
void CountSort(int *a,int n)
{
    assert(a);
    int max = a[0];
    int min = a[0];
    //找出最大数和最小数,
    for(int i = 0;i < n;i ++)
    {
        if(a[i]>max)
            max = a[i];
        if(a[i]<min)
            min = a[i];
    }
    //确定哈希表的大小
    int range = max-min+1;
    int *arr = new int[range];
    memset(arr,0,sizeof(int)*range);//开辟哈希表后初始化为0
    for(int i = 0;i < n;++i)
    {
        arr[a[i]-min]++;//统计各数据出现的次数
    }
    int j = 0;
    for(int i = 0;i < range;++i)
    {
        while((arr[i]--)>0)
        {
            a[j]=i+min;
            j++;
        }
    }
}
void Print(int *a,size_t n)
{
    assert(a);
    for(size_t i = 0;i<n;i++)
    {
        cout<<a[i]<<" ";
    }
    cout<<endl;
}
int main()
{
    int a[] = {3,4,3,2,1,2,6,5,4,7};
    CountSort(a,sizeof(a)/sizeof(a[0]));
    Print(a,sizeof(a)/sizeof(a[0]));
}

基数排序
(1)思想:

以十进制为例,基数指的是数的位,从低位到高位,先比较个位相同的放到一个位置,再比较十位,最后比较百位。这样子最后读取出来就是排序的结果:

(2)分类:

LSD:排序方式由数值的最右边(低位)开始。适用于位数多的数列。
MSD:由数值的最左边(高位)开始。适用于位数少的数列。

(3)图解:
这里写图片描述
根据个位数值,遍历数据时将它们分配至0-9号对应的桶内(个位数值与桶号一一对应):
这里写图片描述
分配结束按照桶号有小到大顺序重新收集数据,得到此序列:81 22 73 93 43 14 55 65 28 39
接着根据十位数值再进行一次分配(原理同按个位分配一样),分配结果如下:
这里写图片描述
得到最终有序序列:
14 22 28 39 43 55 65 73 81 93
(4)局限性:
只适用于正整数或者一定限制的字符串,而且内存花销比较大。

猜你喜欢

转载自blog.csdn.net/zjx624bjh/article/details/80395395
今日推荐