1. 程式人生 > >八大排序演算法之歸併排序

八大排序演算法之歸併排序

介紹到這裡只剩下歸併排序和基數排序沒有介紹過了。這兩種演算法各有各的特點,歸併排序是分治法的一種有效應用,
所謂歸併是指將若干個已排好序的部分合併成一個有序的部分。而基數排序又稱為桶排序,是唯一一種不需要元素之間相互比較就可以排好序的排序演算法。

一、歸併排序

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)穩定性
穩定。