归并排序与计数排序

本文深入探讨了两种排序算法——归并排序和计数排序。归并排序通过递归或非递归方式实现,将有序子序列合并成完全有序序列,时间复杂度为O(N*logN)。计数排序则是一种非比较算法,通过统计数组元素出现次数直接排序,适合于极值相差不大的整型数组,时间复杂度为O(max(N,size))。
摘要由CSDN通过智能技术生成

归并排序

动图演示

实现思想:将已有序的子序合并,从而得到完全有序的序列,即先使每个子序有序,再使子序列段间有序。

如何实现每个子序有序?当序列分解到只有一个元素或是没有元素时,就可以认为是有序了,这时分解就结束了,开始合并。看下图:

递归实现 

void _MergeSort(int* a, int left, int right, int* tmp)//子函数
{
    if (left >= right)//判断归并区间是否存在
    {
        return;
    }
    int mid = left + (right - left) / 2;//得到中间位置下标,划分左右两个区间
    _MergeSort(a, left, mid, tmp);//递归左区间
    _MergeSort(a, mid + 1, right, tmp);//递归右区间
    int begin1 = left, end1 = mid;
    int begin2 = mid + 1, end2 = right;
    int i = left;//tmp数组开始位置,看动图很清楚
    while (begin1 <= end1 && begin2 <= end2)//两段区间都存在
    {
        if (a[begin1] < a[begin2])//比较
        {
            tmp[i++] = a[begin1++];
        }
        else
        {
            tmp[i++] = a[begin2++];
        }
    }
    //其中一个区间不存在(走下面两个循环,就可以将数据走完)两个区间都不存在(不用处理了)
    while (begin1 <= end1)
    {
        tmp[i++] = a[begin1++];
    }
    while (begin2 <= end2)
    {
        tmp[i++] = a[begin2++];
    }
    for (int j = left; j <= right; j++)//每一次归并结束后将这段区间的数据拷贝回原数组
    {
        a[j] = tmp[j];
    }
}


void MergeSort(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);//开辟一个与原数组大小一样的临时数组,存两个区间比较后的元素
    if (tmp == NULL)
    {
        exit(-1);
    }
    _MergeSort(a, 0, n-1, tmp);//子函数去递归实现排序
    free(tmp);//申请了就要释放
    tmp = NULL;
}

非递归实现 

控制每次参与合并的元素个数,最终便能使序列变为有序

 但是如果排序个数是奇数时,就会有非法访问,需要特殊处理。假设比较时第一个为左区间,第二个为右区间

1.当只有右区间([6,不存在])右半区间不存在时,需要修正右半区间的位置到最后一个元素位置,使左[4,5]右区间[6,6]可以比较。

在这里插入图片描述

2.当整个右区间不存在或者左区间的右半区间不存在时,没有要比较的数据不需要处理。

理解以上两点是实现非递归的关键

void MergeSortNonR(int* a, int n)
{
    int* tmp = (int*)malloc(sizeof(a) / sizeof(int));
    if (tmp == NULL)
    {
        exit(-1);
    }
    
    int gap = 1;
    while (gap < n)
    {
        for (int j = 0; j < n; j += gap * 2)
        {
            int begin1 = j, end1 = j + gap - 1;//左区间
            int begin2 = j + gap, end2 = j + 2 * gap - 1;//右区间
            //这里是非递归的关键
            if (end1 >= n || begin2 >= n)//左区间的右半区间不存在,整个右区间不存在,不需要处理
            {
                break;
            }
            if (end2 >= n)//右区间的右半区间不存在,左半区间存在,得修正右半区间,与左区间比较。
            {
                end2 = n - 1;
            }
            int i = j;//tmp数组开始位置,看动图很清楚
            while (begin1 <= end1 && begin2 <= end2)//两段区间都存在
            {
                if (a[begin1] < a[begin2])//比较
                {
                    tmp[i++] = a[begin1++];
                }
                else
                {
                    tmp[i++] = a[begin2++];
                }
            }
            while (begin1 <= end1)
            {
                tmp[i++] = a[begin1++];
            }
            while (begin2 <= end2)
            {
                tmp[i++] = a[begin2++];
            }
            for (int index = j; index <= end2; index++)
            {
                a[index] = tmp[index];
            }
        }
        gap *= 2;
    }
}       

时间复杂度:O ( N*logN )  空间复杂度:O ( N )
 

计数排序

是一个非比较算法,通过统计数组中数字出现的次数,根据统计的结果将序列写回数组。

上列中的映射方法称为绝对映射,即arr数组中的元素是几就在count数组中下标为几的位置++,但这样会造成空间浪费。例如,我们要将数组:1999,1997,1996,进行排序,我们就要开辟2000个整型空间。

所以我们应该使用相对映射,简单来说,数组中的最小值就相对于count数组中的0下标,数组中的最大值就相对于count数组中的最后一个下标。这样,对于数组:1999,1997,1996,我们就只需要开辟用于储存4个整型的空间大小了,此时count数组中下标为i的位置记录的实际上是1996+i这个数出现的次数。

所以计数排序适合极值相差不大的整型。

 void CountSort(int* a, int n)
{
    int max = a[0];
    int min = a[0];//数组中可能有负数,不能单纯初始化为0
    int i = 0;
    for (i = 0; i < n; i++)
    {
        if (max < a[i])
        {
            max = a[i];
        }
        if (min > a[i])
        {
            min = a[i];
        }
    }//找出数组最大最小值
    int size = max - min + 1;
    int* count = (int*)malloc(sizeof(int) * size);//为count数组开辟空间
    if (count == NULL)
    {
        exit(-1);
    }
    memset(count, 0, sizeof(int) * size);//将count数组初始化为0
    for (i = 0; i < n; i++)//遍历整个数组,将数组的每个值出现次数统计到count
    {
        count[a[i] - min]++;
    }
    i = 0;
    for (int j = 0; j < size; j++)//执行size次,将count的值映射回a
    {
        while (count[j]--)//后置--执行count次
        {
            a[i++] = j + min;
        }
    }
    free(count);//释放
    count = NULL;
}

 时间复杂度O(max(N,size))  空间复杂度O(size)

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值