排序算法總結

外部排序

爲什麼需要外部排序
指的是待排序記錄的數量很大,以致內存一次不能容納全部記錄,在排序過程中需要對外存進行訪問的排序過程。
定義
外部排序指的是大文件的排序,即待排序的記錄存儲在外部存儲器上,在排序過程中需進行多次的內、外存之間的交換。
怎麼實現
首先將打文件記錄分成若干個子文件,然後讀入內存中,並利用內部排序的方法進行排序;然後把排序好的有序子文件,重新寫入外存,再對這些歸併段進行逐個歸併,直到整個有序文件爲止。

內部排序

指的是待排序記錄存放在計算機隨機存儲器(內存)中進行的排序過程;
下面總結我們常用的內部排序算法:

算法 平均時間複雜度 空間複雜度 最壞情況 最好情況 穩定性算法
插入排序 O(n2 ) O(1) O(n) O(n2 ) 穩定性算法
選擇排序 O(n2 ) O(1) O(n2 ) O(n2 ) 穩定性算法
冒泡排序 O(n2 ) O(1) O(n2 ) O(n) 穩定性算法
基數排序 O(d(n+rd) ) O(rd ) O(d(n+rd) ) 穩定性算法
歸併排序 O(nlogn ) O(n ) O(nlogn ) O(nlogn ) 穩定性算法
快速排序 O(nlogn ) O(logn )-O(n ) O(n2 ) O(nlogn ) 不穩定算法
希爾排序 O(nlogn )-O(n2 ) O(1) O(n2 ) O(n^1.3) 不穩定算法
堆排序 O(nlogn ) O(1) O(nlogn ) O(nlogn ) 不穩定算法

穩定性的定義:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj之前,而在排序後的序列中,ri仍在rj之前,則稱這種排序算法是穩定的;否則稱爲不穩定的。

插入排序

將一個待排序的記錄,按照其關鍵字的大小將其插入到前邊已經排好序的子序列的適當的位置,直到全部插入完畢.這就像我們在玩撲克牌時,將每一張拍插入到其他已經有序的牌中適當的位置.在計算及中,爲了給要插入的元素騰出位置,需要將其餘所有元素在插入位置之前都向右移動一位。
[] 5 7 9 3 1
[5] 7 9 3 1
[5 7] 9 3 1
[5 7 9] 3 1
[3 6 7 9] 1
[1 3 6 7 9]

void insertSort(int *data, int n){
    int i, j, k;
    for(i=1;i<n;i++){
        for(j=i-1;j>=0;j--){
            if(data[j] > data[i])
                break;
        }
        if (j!=i-1) {
            int temp = data[i];//比data[i]大的數據往後移動
            for(k=i-1; k>j; k--){
                data[k+1] = data[k];
            }//將data[i]放在待插入的位置
            data[k+1] = temp;
        }
    }
}

選擇排序

剛開始數組爲空,i從0開始,對於第i個位置,選擇(i+1,n)之間最小值存入。

[] 5 7 9 3 1
[1] 5 7 9 3
[1 3] 5 7 9
[1 3 5] 7 9
[1 3 5 7] 9
[1 3 5 7 9]

void selectSort(int *data, int n){
    for(int i=0;i<n;i++){
        int min = data[i];
        int min_index = i;
        for(int j=i+1;j<n;j++){
            if(data[j]<min){
                min = data[j];
                min_index = j;
            }
        }
        if(min_index != i){
            swap(data, i, min_index);
        }
    }
}

冒泡排序

兩兩相鄰進行比較,如果它們的排序與排序要求不符合,則交換兩個相鄰的值。
[5 7 9 3 1]
[5 7 9 3 1]
[5 7 3 9 1]
[5 3 7 9 1]
[3 5 7 9 1]
[3 5 7 1 9]
[3 5 1 7 9]
[1 3 5 7 9]

void bubbleSort(int *data, int n){
    for(int i=1;i<n;i++){
        for(int j=i-1;j>=0;j--){
            if(data[j] > data[j+1]){
                swap(data, j,j+1);
            }
        }
    }
}

希爾排序

選擇增量,根據增量來劃分數組,然後排序子數組,交換位置,然後再改變增量,再劃分子數組,再排序,最後直至增量爲1,全排序。
592 401 874 141 348 72 911 887 820 283
增量爲5
72 401 874 141 283 592 911 887 820 348
增量爲5/2=2
72 141 283 348 820 401 874 592 911 887
增量爲1
72 141 283 348 401 592 820 874 887 911

歸併排序

先分組,再排序
2個一組,4個一組,8個一組…
10,8,12,7,5
第一趟:(10,8),(12,7),5->(8,10),(7,12),5
第二趟:(8,10,7,12),5->(7,8,10,12),5
第三趟:5,7,8,10,12

void _mergeSort(vector<int>& nums, int start, int mid, int end){
    int index1 = start;
    int index2 = mid+1;
    int index = start;
    vector<int> tmp;
    while((index1<=mid)&&(index2<=end)){
        if(nums[index1]<nums[index2]){
            tmp.push_back(nums[index1++]);
        }
        else{
            tmp.push_back(nums[index2++]);
        }
    }
    while(index1<=mid){
        tmp.push_back(nums[index1++]);
    }
    while(index2<=end){
        tmp.push_back(nums[index2++]);
    }
    int count = 0;
    for(int i=0;i<tmp.size();i++){
        nums[start+i] = tmp[i];
    }
}
void mergeSort(vector<int>& nums, int start,  int end){
    if(start>=end){
        return;
    } 
    int mid = (start+end)>>1;
    mergeSort(nums, start, mid);
    mergeSort(nums, mid+1, end);
    _mergeSort(nums, start, mid, end);
}

快速排序

[5 7 9 3 1] key=5, first = 0, last=4
[1 7 0 3 1] data[first]=data[last]
[1 7 0 3 7] data[last]=data[first]
[1 3 0 3 7]
[1 3 0 5 7]
[1 3 0] 5 [7]
1. 先選控制值key,這裏默認數組的第一個值
2. 然後設置頭尾指針,
3. while(頭指針<尾指針)
首先從尾指針開始移動,直至遇到比key小的值,就把該值移動到data[first]的位置
然後從頭指針開始移動,直至遇到比key大的值,就把該值移動到data[last]的位置
4. 最後將a[first]設置爲控制值key

void quickSort(int *data, int low, int high){
    if(low>=high)
        return;
    int first = low;
    int last = high;
    int key = data[first];
    while(first < last){
        while(first<last && data[last]>=key){
            last --;
        }
        data[first] = data[last];//將較小的值移到左端
        while(first<last && data[first]<=key){
            first++;
        }
        data[last] = data[first];//將較大的值移到右端
    }
    data[first] = key;
    quickSort(data, low, first-1);
    quickSort(data, first+1, high);
}

堆排序

步驟
1. 將無需序列構建成一個堆,根據升序降序需求選擇大頂堆或小頂堆;
2. 將堆頂元素與末尾元素交換,將最大元素”沉”到數組末端;
3. 重新調整結構,使其滿足堆定義,然後繼續交換堆頂元素與當前末尾元素,反覆執行調整+交換步驟,直到整個序列有序。

基數排序

先比較個位,再比較十位

92 35 67 76 84 51
第一趟:51→92→84→35→67→76
第二趟:35→51→67→76→84→92

大數據排序:

  • 位圖法
    位圖法的基本思想就是利用一位代表一個數字,例如3位上爲1,則說明3在數據中出現過,若爲0,則說明3在數據中沒有出現過。所以當題目中出現每個數據最多重複一次這個條件時,我們可以考慮使用位圖法來進行大數據排序。
    步驟
    一般來說是整數,第k位表示第k個數,每一位初始化爲0。可以通過累加來計算這個數出現的次數。
  • 堆排序法
    使用場景:從1億個整數裏找出100個最大的數
    步驟
    1.讀取前100個數字,建立最大值堆。(這裏採用堆排序將空間複雜度講得很低,要排序1億個數,但一次性只需讀取100個數字,或者設置其他基數,不需要1次性讀完所有數據,降低對內存要求)
    2、依次讀取餘下的數,與最大值堆作比較,維持最大值堆。可以每次讀取的數量爲一個磁盤頁面,將每個頁面的數據依次進堆比較,這樣節省IO時間。
    3.將堆進行排序,即可得到100個有序最大值。
    最大堆最小堆…
  • 分治策略
    很多情況下,分治策略的解法都不是最優解,但是其通用性很強。
    步驟
    1. 從大數據中抽取樣本,將需要排序的數據切分爲多個樣本數大致相等的區間,例如:1-100,101-300…
    2. 將大數據文件切分爲多個小數據文件,這裏要考慮IO次數和硬件資源問題,例如可將小數據文件數設定爲1G(要預留內存給執行時的程序使用)
    3. 使用最優的算法對小數據文件的數據進行排序,將排序結果按照步驟1劃分的區間進行存儲
    4. 對各個數據區間內的排序結果文件進行處理,最終每個區間得到一個排序結果的文件
    5. 將各個區間的排序結果合併.
      通過分治將大數據變成小數據進行處理,再合併。
  • 多臺機器上處理
    考慮下分佈式計算唄
問題一:若有1T的數據,需要實現由大到小排序,你用什麼辦法,說說你的思路和想法?
簡單思路
如果單純用內部排序的話,很不切合實際。首先要考慮內存,不可能一次性載入這麼多數據。
思路:分治法
1. 先將所有的數據分成多份,然後對於每一份進行排序
2. 兩兩進行歸併,進行內部排序
3. 將排序好的歸併段重新寫入外存,在進行兩兩歸併,直到得到一個有序文件爲止。
問題二:有10個G的數據,如果兩條數據一樣,則表示該兩條數據重複了,現在給你512的內存,把這10G中重複次數最高的10條數據取出來。
思路一:
1. 先排序, 10G數據分成40份,每份256M,排序,合併相同數據並加上計數器,寫到臨時文件chunk01~chunk20。
2. 對每一chunk, 讀入內存,對每一條數據,再依次讀入其後續個chunk, 合併相同數據的計數,後寫入一個文件count。爲了避免重複計數,在計數累加後需要將原來chunk的計數清零並回寫文件。
以chunk01爲例。假設chunk01中有數據A-8(數據A, 8次),chunk02中有A-2,那麼合併chunk02後
chunk01的內存中爲A-10, chunk02中爲A-0,這時把chunk02寫回文件,然後讀入chunk03繼續處理處理。最後把chunk01中計數不爲0的數據(chunk01裏不會有計數爲0的,但是後面的chunk會有)寫入文件count.
3. 對count文件進行按重複次數排序。(分組,排序,然後每組選前10,再排序
思路2:
計數排序
其次是這10G數據時什麼,是10G個BYTE,還是10G個字符串
因爲BYTE的範圍0-255,也就是說這個問題就變成了,找0-255範圍內,出現次數最多的10個數,
用int64[255]來計數,遍歷一次,每個數值對應下標裏面記錄其出現的次數就可以了,用int64是因爲DWORD表示不了10G。
如果是字符串,或者是其他2進制數據塊,就比較複雜,需要多次遍歷
2進制數據塊有多少個字節,就需要準備多少個int64[255]計數數組
假定每條記錄,就是每個2進制數據塊長10個字節,對於不足10字節的記錄,不足的部分以0計算
需要的計數數組爲int64[10][255]
對於每條記錄的第一個字節相同的,算爲相同記錄的情況下,得出表:
A********* 1000次
B********* 900次
C********* 900次
D********* 890次
…統計結果計入int64[0][255]
然後對於100次的A*********,統計
AE******** 50次
AA******** 50次
AD******** 49次
AC******** 47次
…統計結果計入int64[1][255]
依此類推
AEDBBCADE* 10次
AEDBBCADB* 9次
AEDBBCADC* 9次
AEDBBCADA* 8次
…統計結果計入int64[8][255]
最終
AEDBBCADEA 3次
AEDBBCADEF 3次
AEDBBCADEC 2次
AEDBBCADEB 2次
…統計結果計入int64[9][255]
將這個結果放入另一個int64[255] res,這個就是當前的最終結果
然後逐個向前遞歸
對於int64[8][255]中排行第二的“AEDBBCADB* 9次”
統計出前10,得到一個新的int64[9][255],將其與res中的結果比較,得出這20箇中的前10,更新res

參考:
https://bbs.csdn.net/topics/390360278?page=1
http://blog.csdn.net/guyuealian/article/details/51151674

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章