《算法導論》 — Chapter 8 線性時間排序

到目前爲止,關於排序的問題,前面已經介紹了不少,從插入排序、合併排序、堆排序以及快速排序,每一種都有其適用的狀況,在時間和空間複雜度上各有優點。它們都有一個相同的特色,以上全部排序的結果序列,各個元素的次序都是基於輸入元素之間的比較,所以,把這類排序成爲比較排序。
對一個含有n個元素的輸入序列,任何比較排序在最壞狀況下都要用(nlogn)次比較來進行排序,由此也能夠知道合併排序和堆排序是漸進最優的。
本章介紹了三種線性時間排序算法,計數排序、基數排序和桶排序,這些算法都是用非比較的操做來肯定排序順序。
下面將詳細介紹這三種排序算法的實現。ios

GitHub 算法導論 第八章程序代碼git

計數排序

計數排序是基於對輸入數據做某種假設條件下進行的排序算法。其做出,輸入是由一個小範圍內整數構成,即n個輸入元素中的每個都是介於0~k之間的整數。當k = O(n)時,計數排序的運行時間爲O(n)。
其基本思想是,對每個輸入元素x,肯定出小於x的元素個數,即要得出這個元素x是第幾個位置,有了這樣的信息,就能夠把x直接放在最終的輸出數組當中。
下面給出計數排序的算法實現,輸入數據爲data[] , 輸出結果存在result[]中,輸入數據個數爲N = 10 , 每一個元素都是位於 0~101 之間的整數,MAX = 101;github

#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10
#define MAX 101

using namespace std;

//計數排序函數聲明
void CountingSort(int *data, int *result, int k);

int main()
{
    //聲明一個待排序數組 
    int array[N];
    //聲明排序後數組
    int result[N];
    //設置隨機化種子,避免每次產生相同的隨機數 
    srand(time(0));
    for (int i = 0; i<N; i++)
    {
        array[i] = rand() % MAX;//數組賦值使用隨機函數產生1-100之間的隨機數 
    }
    cout << "排序前:" << endl;
    for (int j = 0; j<N; j++)
    {
        cout << array[j] << " ";
    }
    cout << endl << "排序後:" << endl;
    //調用快速排序函數對該數組進行排序 
    CountingSort(array, result , MAX);
    for (int k = 0; k<N; k++)
    {
        cout << result[k] << " ";
    }
    cout << endl;

    system("pause");
    return 0;
}//main

//計數排序算法實現
void CountingSort(int *data, int *result, int k)
{
    int C[MAX] = { 0 };
    //(1) 費時 O(k)
    for (int i = 0; i < k; i++)
        C[i] = 0;

    //(2) 費時O(n)
    for (int j = 0; j < N; j++)
        C[data[j]] = C[data[j]] + 1;

    //(3) 費時O(k)
    for (int i = 1; i < k; i++)
    {
        C[i] = C[i] + C[i - 1];
    }

    //測試當前源數據的目標位置
    /*for (int k = 0; k < N; k++) { cout << C[data[k]] << "\t"; }*/

    //獲得排序後的目標序列 
    //(4)費時 O(n)
    for (int j = 0; j < N ; j++ )
    {
        //保證數據下標不會越界 需-1
        result[C[data[j]]-1] = data[j];
        C[data[j]] -= 1;
    }
}

計數排序測試結果

計數排序是一種穩定的排序算法,所謂穩定性,便是指具備相同值的元素在輸出數組中的相對次序與它們在輸入數組中的次序相同。算法

對於計數排序的性能,它因爲前面介紹的比較排序時間下界(nlogn),從以上代碼能夠看出,計數排序算法步驟(1)~(4)所需的時間複雜度爲O(n+k) , 當k=O(n)時,運行時間則爲O(n)是一個線性時間排序算法。數組

基數排序

基數排序是一種按位排序算法,對輸入待排序序列,求出其最大位數,從低有效位到最高有效位,分別對改組數據進行排序。
代碼實現以下:markdown

#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10
#define MAX 1000


using namespace std;

//基數排序函數聲明
void RadixSort(int *data, int n);

//計算待排數組中最長位數
int ComputeDigits(int *data , int n);

//按照d位數字對數組排序算法
void digitSort(int *data, int n, int d);

int main()
{
    //聲明一個待排序數組 
    int array[N];

    //設置隨機化種子,避免每次產生相同的隨機數 
    srand(time(0));
    for (int i = 0; i<N; i++)
    {
        array[i] = rand() % MAX;//數組賦值使用隨機函數產生1-100之間的隨機數 
    }
    cout << "排序前:" << endl;
    for (int j = 0; j<N; j++)
    {
        cout << array[j] << " ";
    }
    cout << endl << "排序後:" << endl;
    //調用快速排序函數對該數組進行排序 
    RadixSort(array, N);

    for (int k = 0; k<N; k++)
    {
        cout << array[k] << " ";
    }
    cout << endl;

    system("pause");
    return 0;
}//main

//基數排序算法實現
void RadixSort(int *data, int n)
{
    int digits = ComputeDigits(data, n);
    //選用一個穩定排序以各位數字對輸入序列排序
    for (int i = 0; i < digits; i++)
    {
        digitSort(data, n, i);
    }

}

//計算待排數組中最長位數
int ComputeDigits(int *data , int n)
{
    int max = data[0];
    for (int i = 1; i < n; i++)
    {
        if (data[i] > max)
            max = data[i];
    }

    //臨時計數變量
    int count = 0;

    while (max)
    {
        count++;
        max /= 10;
    }

    return count;
}

//按照d位數字對數組排序算法
void digitSort(int *data, int n, int d)
{
    int digitArray[10][N];
    for (int i = 0; i < 10; i++)
        for (int j = 0; j < N; j++)
            digitArray[i][j] = -1;

    //當前輸入序列中有n個數字待排
    for (int i = 0; i < n; i++)
    {
        //獲得當前位對應的數字
        int index = data[i] / (int)pow(10, d) % 10;
        for (int j = 0; j < n; j++)
        {
            if (digitArray[index][j] == -1)
            {
                digitArray[index][j] = data[i];
                break;
            }
        }
    }


    int k = 0;
    //將按位排序後的數組更新到源序列
    for (int i = 0; i < 10; i++)
    {
        for (int j = 0; j < N; j++)
        {
            if (digitArray[i][j] != -1)
            {
                data[k++] = digitArray[i][j];
                digitArray[i][j] = -1;
            }       
        }
    }
}

基數排序是一種穩定排序,以上代碼中,對各位分別排序採用的是接下來介紹的桶排序。函數

桶排序

對於桶排序與計數排序相似也是對輸入作了某種假設,於是運行很快。假設輸入待排序列是由一個隨機過程產生,該過程將元素均勻而獨立的分佈在[0 , 1) 上,桶排序的思想就是將該區間均勻的分紅n個大小相同的桶,分別對各個桶中的元素按照直接插入排序,而後再把各個桶列出來便是排序結果。
對於桶排序的程序實現,輸入採用[0 , 1000)的一組數據,道理同上,按照元素的最高位,創建下標爲0~9的十個桶,將相應元素加入到相應桶中,加入過程採用直接插入排序,而後將桶中元素按照下標遞增的方式羅列,便是最終排序結果。性能

#include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10
#define MAX 1000

using namespace std;


//桶排序函數聲明
void BucketSort(int *data, int n);

//計算待排數組中最長位數
int ComputeDigits(int *data, int n);



int main()
{
    //聲明一個待排序數組 
    int array[N];

    //設置隨機化種子,避免每次產生相同的隨機數 
    srand(time(0));
    for (int i = 0; i<N; i++)
    {
        array[i] = (rand() % MAX);//數組賦值使用隨機函數產生1-1000之間的隨機數 
    }
    cout << "排序前:" << endl;
    for (int j = 0; j<N; j++)
    {
        cout << array[j] << " ";
    }
    cout << endl << "排序後:" << endl;
    //調用快速排序函數對該數組進行排序 
    BucketSort(array , N);

    for (int k = 0; k<N; k++)
    {
        cout << array[k] << " ";
    }
    cout << endl;

    system("pause");
    return 0;
}//main

//計算待排數組中最長位數
int ComputeDigits(int *data, int n)
{
    int max = data[0];
    for (int i = 1; i < n; i++)
    {
        if (data[i] > max)
            max = data[i];
    }

    int count = 0;

    while (max)
    {
        count++;
        max /= 10;
    }

    return count;
}

void BucketSort(int *data, int n)
{
    //計算輸入序列中最大元素的位數
    int digits = ComputeDigits(data, n);

    //按照最高位0~9 建立10個桶
    int bucket[10][N+1];
    for (int i = 0; i < 10; i++)
    {
        //該桶的第一個元素設置爲存儲桶中元素個數
        bucket[i][0] = 0;
        //其他元素初始化爲-1
        for (int j = 1; j < N + 1; j++)
            bucket[i][j] = -1;
    }

    //對每一個輸入元素按照插入排序放入相應的桶中
    for (int i = 0; i < n; i++)
    {
        //獲得目標桶的序號
        int index = data[i] / (int)pow(10, digits-1);

        //獲得當前桶中元素個數
        int count = bucket[index][0];

        int j = count;
        //按照直接插入排序將元素插入桶中
        while (j >0 && bucket[index][j] > data[i])
        {       
            bucket[index][j + 1] = bucket[index][j];
            j--;
        }
        bucket[index][j + 1] = data[i];
        bucket[index][0]++;
    }

    //將每一個桶中的元素合併到data中
    int k = 0;
    for (int i = 0; i < 10; i++)
    {
        for (int j = 1; j <= bucket[i][0] ; j++)
        {
            data[k++] = bucket[i][j];
        }
    }
}

桶排序是一種線性時間排序算法,運行時間能夠達到 O(n)測試

相關文章
相關標籤/搜索