1. 程式人生 > >內部排序演算法5(基數排序)

內部排序演算法5(基數排序)

基數排序

多排序碼排序的概念

如果每個元素的排序碼都是由多個數據項組成的組項,則依據它進行排序時就需要利用多排序碼排序。實現多排序碼排序有兩種常用的方法,最高位優先(Most Significant Digit (MSD) First)和最低位優先(Least Signnificant Digit(LSD) first)。利用多排序碼排序實現對單個排序碼排序的演算法就稱為基數排序。

MSD基數排序

思想

在基數排序中,將單排序碼Ki看作是一個子排序碼組:(K1i,K2i,...,Kdi)。例如,有一組元素,它們的排序碼取值範圍是0~999,如{332,633,059,598,232,664,179,457,825,714,405,361},可以把這些排序碼看作是(

K1,K2,K3)的組合,按K1,K2,K3的順序對所有的元素做3次排序,如下圖所示。
在排序過程中,首先是根據K1進行排序,按各個資料在百位上的取值,分配到各個子序列(稱為桶)中,然後再按桶的編號對每個桶遞迴地進行基數排序。

這裡寫圖片描述

步驟

  1. 演算法要求事先設定Radix個桶,Radix叫做基數,即排序碼的每一位可能取值的數目。為了知道每個桶中會有多少個元素,在演算法中還設定了一個輔助陣列count[Radix],用count[k]記憶在處理第i位時第i位取值為k的元素有多少個。k與基數Radix有關。如果k屬於十進位制整數,Radix等於10。例如,在上圖中當i=1時,count各陣列元素記憶了不同取值的元素個數,c
    ount[0]=1
    表示值為0的元素有1個,count[3]=2表示值為3的元素有2個。
  2. 在演算法中還使用了一個輔助陣列auxArray[]存放按桶分配的結果,根據count[]預先算定各桶元素的使用位置。在每一趟向各桶分配結束時,元素都被複制回原表中。

演算法實現

#pragma once
#include<iostream>

#define RADIX 10

class MSDRaixSort
{
public:
    MSDRaixSort(int length);
    void create();
    void sort();
    void print();
    ~MSDRaixSort();

private
: int *elem; int len; void radixSort(int left, int right, int d); int getDigit(int num, int d); }; MSDRaixSort::MSDRaixSort(int length) { len = length; elem = new int[len]; } inline void MSDRaixSort::create() { std::cout << "please input the list" << std::endl; for (int i = 0; i < len; i++) { int temp; std::cin >> temp; elem[i] = temp; } std::cout << "finish!" << std::endl; } inline void MSDRaixSort::sort() { radixSort(0, len - 1, 3); } inline void MSDRaixSort::print() { for (int i = 0; i < len; i++) { std::cout << elem[i] << " "; } std::cout << std::endl; } MSDRaixSort::~MSDRaixSort() { delete[] elem; } inline void MSDRaixSort::radixSort(int left, int right, int d) //MSD基數排序演算法,從高位到低位對序列進行分配,實現排序 { //其中d時位數,n時待排序元素的個數。left和right時待排序 int i, j, count[RADIX + 1], p1, p2; //元素子序列的始端和尾端,最低位d=1,最高位是d int *auxArray = new int[right - left + 1]; if (d <= 0) { return; } for (j = 0; j < RADIX; j++) { count[j] = 0; } for (i = left; i <= right; i++) //統計各桶元素的個數 { count[getDigit(elem[i], d)]++; } count[RADIX] = right - left + 1; for (j = 1; j < RADIX; j++) //安排各桶元素位置 { count[j] = count[j] + count[j - 1]; } for (i = left; i <= right; ++i) { j = getDigit(elem[i], d); auxArray[count[j] - 1] = elem[i]; --count[j]; } for (i = left, j = 0; i <= right; j++, i++) //從輔助陣列auxArray寫入原陣列。 { elem[i] = auxArray[j]; } for (j = 0; j < RADIX; j++) //將各桶內的元素迭代進行MSD基數排序,直到桶內只有一個元素為止。 { p1 = count[j]; p2 = count[j + 1] - 1; if (p1 < p2) { radixSort(p1, p2, d - 1); } } } inline int MSDRaixSort::getDigit(int num, int d) { int count = 0; while (num > 0) { count++; if (count == d) { return num % 10; } num = num / 10; } return 0; }
//MSD基數排序main檔案
using namespace std;
#include"MSDRadixSorting.h"
int main() {
    MSDRaixSort m(15);
    m.create();
    m.sort();
    m.print();
    system("pause");
}

演算法分析

時間複雜度

在演算法中呼叫一個getDigit按位獲取用來排序的元素排序碼。在上述例子中,從高位到低位一次取待排序元素的各位作為排序碼,並設定排序的基數RADIX為10。這就相當於定義了10個接收器,分別接受不同排序碼對應的待排序元素。如果待排序元素序列的規模為n,則每個接收器中的待排元素平局為n/RADIX。所以在基數排序演算法中選擇較大的基數有利於得到個數較多而規模較小的子序列劃分,從而提高效率。但實際上,各個接收器中接收到的元素數目不可能是平局分配的,會有較大的差異,有可能出現很多空接收器。空接收器的大量存在會影響基數排序的效率。

空間複雜度

在演算法中用到了兩個陣列,一個是count用於處理第i位的值得元素有多少個,用auxArray陣列存放按桶分配的結果。其空間的大小分別為RADIX+1(桶的大小+1)和n(排序序列的長度)。故空間複雜度為O(RADIX+n+1)

演算法的穩定性

MSD基數排序演算法是穩定的。

LSD基數排序

思想

LSD基數排序抽取排序碼的順序和MSD基數排序正好相反。使用這種方法,把單排序碼ki看成是一個d元組:利用“分配”和“收集”兩種運算對單排序碼進行排序。(K1i,K2i,...,Kdi)。其中的每一個分量也可以看成是一個排序碼,分量Kji(1jd)有RADIX種取值,RADIX為基數。針對d元組中的每一位分量Kji,把待排序元素序列中的所有元素按Kji的取值先“分配”到RADIX個桶中去。然後再按各個桶的編號,依次把元素從桶中“收集”起來,這樣所有元素按Kji取值排序完成。
如果對於所有元素的排序碼K0,K1,...,Kn1,依次對各位的分量Kji,讓j=d,d1,...,1,分別用這種“分配”和“收集”的運算逐趟進行排序,在最後一趟“分配”和“收集”完成後,所有元素就按其排序碼的值從小到大排好序了。
各個桶都採用鏈式佇列結構,分配到同一桶的排序碼用連結指標連結起來。每一個桶設定兩個佇列指標:一個指向隊頭(第一個進入此佇列的排序碼),記為intf[RADIX];一個指向隊尾(最後一個進入此佇列的排序碼),記為inte[RADIX]
待排序的n個元素組織成迴圈連結串列,A[1]A[n]存放元素,A[0]為表頭結點。這樣在元素重排時不必移動元素,只需要修改各元素的link指標即可。

演算法的實現

//LSD基數排序演算法標頭檔案
#pragma once
#include<iostream>

#define Radix 10
struct ElementType
{
    int data;
    int link;
};

class LSDRadixSort
{
public:
    LSDRadixSort(int length);