八大排序算法之归并排序

介绍到这里只剩下归并排序和基数排序没有介绍过了。这两种算法各有各的特点,归并排序是分治法的一种有效应用,
所谓归并是指将若干个已排好序的部分合并成一个有序的部分。而基数排序又称为桶排序,是唯一一种不需要元素之间相互比较就可以排好序的排序算法。

一、归并排序
1.基本思想
这里介绍的归并排序是非递归版本的归并排序。基本思想如下:首先我们知道单个元素都是有序,然后我们逐渐增加元素之间的距离,最开始的时候为1,即a[0]和a[1]比较,使两者有序;然后a[2]和a[3]比较,使两者有序,以此类推。然后进行第二趟排序时,距离以指数式增长为2^1=2,即将a[0]、a[1]看成一个整体(因为这两者已经有序),将a[2]、a[3]看成一个整体,将这两部分进行归并合成一个有序的部分。下一次距离增长为2^2=4,重复上述过程。
2.排序过程

这里写图片描述

3.代码实现
#include<stdio.h>
#include<stdlib.h>

void MergeSort(int *pArray, int iLen);
void PrintArray(int *pArray, int iLen);
void Merge(int *pArray, int iLen, int iGap);

int main(void)
{
    int iArray_a[] = { 123, 465, 789, 111, 232, 343, 566, 888, 89, 167, 890 };
    int iLen = sizeof(iArray_a) / sizeof(*iArray_a);
    MergeSort(iArray_a, iLen);
    PrintArray(iArray_a, iLen);
    return 0;
}
void Merge(int *pArray, int iLen, int iGap)
{
    int iLow1 = 0;                  //第一个归并段的起始下标,下标可取
    int iHigh1 = iLow1 + iGap - 1;  //第一个归并段的结束下标,下标可取

    int iLow2 = iHigh1 + 1;         //第二个归并段的起始下标,下标可取
    int iHigh2 = (iLow2 + iGap < iLen?(iLow2 + iGap - 1):(iLen -1));
    int *pTemp_a = (int *)malloc(sizeof(int)*iLen); //临时数组
    int i = 0;
    while (iLow2 < iLen)    //确定两个归并段都存在
    {
        while (iLow1 <= iHigh1 && iLow2 <= iHigh2)
        {
            if (pArray[iLow1] <= pArray[iLow2])
            {
                pTemp_a[i++] = pArray[iLow1++];
            }
            else if(pArray[iLow2] <= pArray[iLow2])
            {
                pTemp_a[i++] = pArray[iLow2++];
            }
        }
        while (iLow1 <= iHigh1)
        {
            pTemp_a[i++] = pArray[iLow1++];
        }

        while (iLow2 <= iHigh2)
        {
            pTemp_a[i++] = pArray[iLow2++];
        }
        iLow1 = iHigh2 + 1;
        iHigh1 = iLow1 + iGap - 1;

        iLow2 = iHigh1 + 1;         //第二个归并段的起始下标,下标可取
        iHigh2 = (iLow2 + iGap < iLen ? (iLow2 + iGap - 1) : (iLen - 1));
    } 

    //处理只有一个归并段的数据
    while (iLow1 <= iLen - 1)
    {
        pTemp_a[i++] = pArray[iLow1++];
    }
    for (i = 0; i < iLen; ++i)
    {
        pArray[i] = pTemp_a[i];
    }
    free(pTemp_a); 
}
void MergeSort(int *pArray, int iLen)
{
    for (int i = 1; i < iLen; i *= 2)   //控制归并段的长度
    {
        Merge(pArray, iLen, i);
    }
}

void PrintArray(int *pArray, int iLen)
{
    int iIndex;
    for (iIndex = 0; iIndex < iLen; ++iIndex)
    {
        printf("%5d", pArray[iIndex]);
    }
    printf("\n");
}
4.算法分析
(1)时间复杂度
从上面代码可知,对于N个元素组成的序列,共需要logN趟归并过程,每次归并的距离2^N(N=0,1,2,......),而每次归并的过程中是把序列中所有元素都遍历了一遍,所以一次遍历过程的时间复杂度为O(logN)。所以整个排序过程时间复杂度为O(NlogN).
(2)空间复杂度
归并排序的时间复杂度比较大,需要和原数组一样的大小,所以空间复杂度为O(N)。
(3)稳定性
稳定。
二、基数排序
1.基本思想
基数排序的过程很好理解,但是其代码实现比较复杂,主要是因为需要做很多辅助性工作。基本思想是在排序过程中,对于一个数而言,先排权值比较小的位数,如三位数字序列{195,683, 756},先按个位排序,则为{683,195,756};再按十位排序,则为{756, 683,195};最后按百位进行排序,则为{195, 683, 756}。具体过程见排序过程。
2.排序过程
代码实现过程中需要明确几个地方,一是入桶和出桶本质上就是入队和出队的过程,所以需要我们在代码实现时构建一个队列;二是入桶和出桶的次数由序列中值最大的元素的位数决定;三是桶的个数由单个数字的取值范围决定,比如这里是每个数字由0~9组成,所以需要十个桶。

这里写图片描述

3.代码实现
#include <stdio.h>
#include <stdlib.h>

typedef struct Node
{
    int iData;
    struct Node *pNext;
}Node, *List;

void InitList(List pList)
{
    pList->pNext = NULL;
}

bool IsEmpty(List pList)
{
    return (pList->pNext == NULL);
}

static Node* BuyNode(int iVal)
{
    Node *p = (Node*)malloc(sizeof(Node));
    p->iData = iVal;
    p->pNext = NULL;
    return p;
}
//获取序列中值最大的元素
int GetMaxFigure(int *pArray, int iLen)
{
    int iMax = pArray[0];
    for (int i = 1; i < iLen; ++i)
    {
        if (iMax < pArray[i])
        {
            iMax = pArray[i];
        }
    }
    return iMax;
}

//获取数字的位数
int GetWidth(int iNum) //获取最大位数
{
    int iWidth = 1;
    int iRemind = iNum / 10;
    while (0 != iRemind)
    {
        iRemind /= 10;
        ++iWidth;
    }
    return iWidth;  //返回数字的位数
}

//在尾部插入一个节点
void InsertTail(List pList, int iVal)
{
    Node *p;
    for (p = pList; NULL != p->pNext; p = p->pNext)
    {
        NULL;   //空语句
    }
    p->pNext = BuyNode(iVal);
}

//删除首节点
bool DeleteFirstNode(List pList, int *rtVal)
{
    if (NULL == pList->pNext) //如果队列为空则直接返回
    {
        return false;
    }
    Node *p = pList->pNext;
    *rtVal = p->iData;
    pList->pNext = p->pNext;
    free(p);
    p = NULL;
    return true;
}

void RadixSort(int *pArray, int iLen)
{
    Node *pList_a[10];  //十个头指针,每个数字一个头指针
    int iTemp;
    for (int iIndex = 0; iIndex < 10; ++iIndex) //初始化每个头指针
    {
        pList_a[iIndex] = BuyNode(0);
        InitList(pList_a[iIndex]);
    }
    int iRemind;    
    int iCount = GetWidth(GetMaxFigure(pArray, iLen));  //获取值最大元素的位数,决定了我们进行基数排序的次数

    for (int i = 0; i < iCount; ++i)    
    {
        //获取每个位置上的数,如789,则一次获得9、8、7
        for (int j = 0; j < iLen; ++j)
        {
            iTemp = pArray[j];
            for (int k = 0; k <= i; ++k)
            {               
                iRemind = iTemp % 10;
                iTemp /= 10;                
            }           
            //将每个元素按其指定位置的数来入队
            InsertTail(pList_a[iRemind], pArray[j]);  //入队
        }
        //全部入队后依次出队
        int iIndex = 0;
        for (int j = 0; j < 10; ++j)
        {
            int iTemp;
            while (!IsEmpty(pList_a[j]))
            {
                DeleteFirstNode(pList_a[j], &pArray[iIndex++]);
            }
        }
    }
}
void PrintArray(int *pArray, int iLen)
{
    int iIndex;
    for (iIndex = 0; iIndex < iLen; ++iIndex)
    {
        printf("%5d", pArray[iIndex]);
    }
    printf("\n");
}

int main(void)
{
//  printf("%5d\n", GetMaxWidth(0));
    int iArray_a[] = { 123, 465, 789, 111, 232, 343, 566, 888, 89, 167, 890 };
    int iLen = sizeof(iArray_a) / sizeof(*iArray_a);
    RadixSort(iArray_a, iLen);
    PrintArray(iArray_a, iLen);
    return 0;
}
4.算法分析
假设入桶的次数为r,且有d个桶,n个元素。
(1)时间复杂度
时间复杂度为O(r*d)。
(2)空间复杂度
空间复杂度为O(n)。
(3)稳定性
稳定。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值