數據結構中各種排序算法比較


http://space.itpub.net/15203236/viewspace-616582

http://student.zjzk.cn/course_ware/data_structure/web/paixu/paixu8.1.1.1.htm

 

 

 

 

 

把內排序分為:插入排序、交換排序、選擇排序和歸並排序。

 

  • 插入排序(Insertion Sort)的基本思想是:每次將一個待排序的記錄,按其關鍵字大小插入到前面已經排好序的子文件中的適當位置,直到全部記錄插入完成為止。     本節介紹兩種插入排序方法:直接插入排序和希爾排序(分組插入排序,直到增量為1)

希爾排序基本思想

  基本思想:
     先取一個小於n的整數d1作為第一個增量,把文件的全部記錄分成d1個組。所有距離為dl的倍數的記錄放在同一個組中。先在各組內進行直接插人排序;然后,取第二個增量d2<d1重復上述的分組和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有記錄放在同一組中進行直接插入排序為止。
     該方法實質上是一種分組插入方法。

希爾排序的時間性能優於直接插入排序的原因:
  ①當文件初態基本有序時直接插入排序所需的比較和移動次數均較少。
  ②當n值較小時,n和n2的差別也較小,即直接插入排序的最好時間復雜度O(n)和最壞時間復雜度0(n2)差別不大。
  ③在希爾排序開始時增量較大,分組較多,每組的記錄數目少,故各組內直接插入較快,后來增量di逐漸縮小,分組數逐漸減少,而各組的記錄數目逐漸增多,但由於已經按di-1作為距離排過序,使文件較接近於有序狀態,所以新的一趟排序過程也較快。
     因此,希爾排序在效率上較直接插人排序有較大的改進。

  • 交換排序的基本思想是:兩兩比較待排序記錄的關鍵字,發現兩個記錄的次序相反時即進行交換,直到沒有反序的記錄為止。     應用交換排序基本思想的主要排序方法有:冒泡排序和快速排序
  • 選擇排序(Selection Sort)的基本思想是:每一趟從待排序的記錄中選出關鍵字最小的記錄,順序放在已排好序的子文件的最后,直到全部記錄排序完畢。     常用的選擇排序方法有直接選擇排序和堆排序

直接選擇排序中,為了從R[1..n]中選出關鍵字最小的記錄,必須進行n-1次比較,然后在R[2..n]中選出關鍵字最小的記錄,又需要做n-2次比較。事實上,后面的n-2次比較中,有許多比較可能在前面的n-1次比較中已經做過,但由於前一趟排序時未保留這些比較結果,所以后一趟排序時又重復執行了這些比較操作。
     堆排序可通過樹形結構保存部分比較結果,可減少比較次數。

  • 分配排序的基本思想:排序過程無須比較關鍵字,而是通過"分配"和"收集"過程來實現排序.它們的時間復雜度可達到線性階:O(n)。


箱排序(Bin Sort)

1、箱排序的基本思想

https://www.cnblogs.com/my_life/articles/5097914.html
     箱排序也稱桶排序(Bucket Sort),其基本思想是:設置若干個箱子,依次掃描待排序的記錄R[0],R[1],…,R[n-1],把關鍵字等於k的記錄全都裝入到第k個箱子里(分配),然后按序號依次將各非空的箱子首尾連接起來(收集)。

按照一定的算法,把數字分到不同的組,然后在每個非空的桶中進行快速排序,然后收集。


【例】要將一副混洗的52張撲克牌按點數A<2<…<J<Q<K排序,需設置13個"箱子",排序時依次將每張牌按點數放入相應的箱子里,然后依次將這些箱子首尾相接,就得到了按點數遞增序排列的一副牌。

 

void bucketSort(vector<int> &a, int bucketSize)
{
    int len = a.size();
    if (len < 2)
        return;
    int Min = a[0], Max = a[0];
    for (int i = 1; i < len; i++)
    {
        Max = max(Max, a[i]);
        Min = min(Min, a[i]);
    }
    int bucketCount = (Max - Min) / bucketSize + 1;
    //這個區間是max-min+1,但是我們要向上取整,就是+bucketSize-1,和上面的形式是一樣的
    vector<int> bucketArr[bucketCount];
    for (int i = 0; i < len; i++)
    {
        bucketArr[(a[i] - Min) / bucketSize].push_back(a[i]);
    }
    a.clear();
    for (int i = 0; i < bucketCount; i++)
    {
        int tlen = bucketArr[i].size();
        sort(bucketArr[i].begin(),bucketArr[i].end());
        for (int j = 0; j < tlen; j++)
            a.push_back(bucketArr[i][j]);
    }
}

  

 

按照算法的復雜度分為兩大類,冒泡排序、簡單選擇排序和直接插入排序屬於簡單算法,而希爾排序、堆排序、歸並排序、快速排序屬於改進算法。

希爾排序是D.L.Shell於1959年提出來的一種排序算法,在這之前排序算法的時間復雜度基本都是O(n2)的,希爾排序算法是突破這個時間復雜度的第一批算法之一。將相距某個“增量”的記錄組成一個子序列,這樣才能保證在子序列內分別進行直接插入排序后得到的結果是基本有序而不是局部有序

 

 歸並排序(Merging Sort)就是利用歸並的思想實現的排序方法。它的原理是假設初始序列含有n個記錄,則可以看成是n個有序的子序列,每個子序列的長度為1,然后兩兩歸並,得到⌈n/2⌉(⌈x⌉表示不小於x的最小整數)個長度為2或1的有序子序列;再兩兩歸並,……,如此重復,直至得到一個長度為n的有序序列為止,這種排序方法稱為2路歸並排序。 

 

 

1 快速排序(QuickSort)  (分而治之)

快速排序是一個就地排序,分而治之,大規模遞歸的算法。從本質上來說,它是歸並排序的就地版本。快速排序可以由下面四步組成。

(1) 如果不多於1個數據,直接返回。
(2) 一般選擇序列最左邊的值作為支點數據。
(3) 將序列分成2部分,一部分都大於支點數據,另外一部分都小於支點數據。
(4) 對兩邊利用遞歸排序數列。

快速排序比大部分排序算法都要快。盡管我們可以在某些特殊的情況下寫出比快速排序快的算法,但是就通常情況而言,沒有比它更快的了。快速排序是遞歸的,對於內存非常有限的機器來說,它不是一個好的選擇。 

 

int quicksort(vector<int> &v, int left, int right){
if(left < right){
int key = v[left];
int low = left;
int high = right;
while(low < high){
while(low < high && v[high] > key){
high--;
}
v[low] = v[high];
while(low < high && v[low] < key){
low++;
}
v[high] = v[low];
}
v[low] = key;
quicksort(v,left,low-1);
quicksort(v,low+1,right);
}
}




2 歸並排序(MergeSort)

歸並排序先分解要排序的序列,從1分成2,2分成4,依次分解,當分解到只有1個一組的時候,就可以排序這些分組,然后依次合並回原來的序列中,這樣就可以排序所有數據。合並排序比堆排序稍微快一點,但是需要比堆排序多一倍的內存空間,因為它需要一個額外的數組。

3 堆排序(HeapSort)

我們前面講到簡單選擇排序,它在待排序的n個記錄中選擇一個最小的記錄需要比較n-1次。本來這也可以理解,查找第一個數據需要比較這么多次正常的,否則如何知道它是最小的記錄。
        可惜的是,這樣的操作並沒有把每一趟的比較結果保存下來,在后一趟的比較中,有許多比較在前一趟已經做過了,但由於前一趟排序時未保存這些比較結果,所以后一趟排序時又重復執行了這些比較操作,因而記錄的比較次數較多。
如果可以做到每次在選擇到最小的記錄的同時,並根據比較對其他記錄做出相應的調整,那樣排序的總體效率就會非常高了。而堆排序(Heap Sort),就是對簡單選擇排序進行的一種改進,這種改進的效果是非常明顯的。

堆排序適合於數據量非常大的場合(百萬數據)。

堆排序不需要大量的遞歸或者多維的暫存數組。這對於數據量非常巨大的序列是合適的。比如超過數百萬條記錄,因為快速排序,歸並排序都使用遞歸來設計算法,在數據量非常大的時候,可能會發生堆棧溢出錯誤。

堆排序會將所有的數據建成一個堆,最大的數據在堆頂,然后將堆頂數據和序列的最后一個數據交換。接下來再次重建堆,交換數據,依次下去,就可以排序所有的數據。

堆排序的算法步驟如下:

  1. 把無序數列構建成二叉堆;

  2. 循環刪除堆頂元素,替換到二叉堆的末尾,調整堆產生新的堆頂。

 


4 Shell排序(ShellSort)(選擇一個間隔,分組進行插入排序)

Shell排序通過將數據分成不同的組,先對每一組進行排序,然后再對所有的元素進行一次插入排序,以減少數據交換和移動的次數。平均效率是O(nlogn)。其中分組的合理性會對算法產生重要的影響。現在多用D.E.Knuth的分組方法。

Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相對比較簡單,它適合於數據量在5000以下並且速度並不是特別重要的場合。它對於數據量較小的數列重復排序是非常好的。

5 插入排序(InsertSort)(打撲克牌,理牌

插入排序通過把序列中的值插入一個已經排序好的序列中,直到該序列的結束。插入排序是對冒泡排序的改進。它比冒泡排序快2倍。一般不用在數據大於1000的場合下使用插入排序,或者重復排序超過200數據項的序列。

printline("before sort:", v);
for (int i=1; i<v.size(); i++){
int key = v[i];
int j = i-1;
while (j >= 0 && v[j] > key){
v[j+1] = v[j];
j--;
}
v[j+1] = key;

}
printline("after sort:", v);



6 冒泡排序(BubbleSort)

冒泡排序是最慢的排序算法。在實際運用中它是效率最低的算法。它通過一趟又一趟地比較數組中的每一個元素,使較大的數據下沉,較小的數據上升。它是O(n^2)的算法。

7 交換排序(ExchangeSort)和選擇排序(SelectSort)

這兩種排序方法都是交換方法的排序算法,效率都是 O(n2)。在實際應用中處於和冒泡排序基本相同的地位。它們只是排序算法發展的初級階段,在實際中使用較少。

選擇排序:

for(int i=0; i<v.size(); i++){
int min = v[i];
int temp;
int index = i;
for(int j=i+1;j<v.size();j++){
if(v[j] < min){
min = v[j];
index = j;
}
}

temp = v[i];
v[i] = min;
v[index]= temp;
}



8 基數排序(RadixSort)

基數排序和通常的排序算法並不走同樣的路線。它是一種比較新穎的算法,但是它只能用於整數的排序,如果我們要把同樣的辦法運用到浮點數上,我們必須了解浮點數的存儲格式,並通過特殊的方式將浮點數映射到整數上,然后再映射回去,這是非常麻煩的事情,因此,它的使用同樣也不多。而且,最重要的是,這樣算法也需要較多的存儲空間。

基數排序(Radix sort) 是一種比較型整數排序算法,其原理是將整數按位數切割成不同的數字,然后按每個位數分別比較。由於整數也可以表達字符串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是只能使用於整數。

 

 

void RadixSortSort(vector<int> &a)
{
    int len = a.size();
    if (len < 2)
        return;
    int Max = a[0];
    for (int i = 1; i < len; i++)
    {
        Max = max(Max, a[i]);
    }
    int maxDigit = log10(Max) + 1;
    //直接使用log10函數獲取位數,這樣的話就不用循環了,這里被強制轉換是向下取整
    int mod = 10, div = 1;
    vector<int> bucketList[10];
    for (int i = 0; i < maxDigit; i++, mod *= 10, div *= 10)
    {
        for (int j = 0; j < len; j++)
        {
            int num = (a[j] % mod) / div;
            bucketList[num].push_back(a[j]);
        }
        int index = 0;
        for (int j = 0; j < 10; j++)
        {
            int tlen=bucketList[j].size();
            for (int k = 0; k < tlen; k++)
                a[index++] = bucketList[j][k];
            bucketList[j].clear();
        }
    }
}

  

基數排序 、 桶排序 、 計數排序 原理都差不多,都借助了 “桶” 的概念,但是使用方式有明顯的差異,其差異如下:

  • 基數排序:根據鍵值的每位數字來分配桶;

  • 桶排序:每個桶存儲一定范圍的數值;

  • 計數排序:每個桶只存儲單一鍵值。



9 總結

下面是一個總的表格,大致總結了我們常見的所有的排序算法的特點。

排序法  平均時間 最差情形 穩定度 額外空間 備注
冒泡  O(n2)   O(n2)  穩定 O(1) n小時較好
交換   O(n2)   O(n2) 不穩定 O(1) n小時較好
選擇  O(n2)  O(n2) 不穩定 O(1) n小時較好
插入  O(n2)  O(n2) 穩定 O(1) 大部分已排序時較好
基數 O(logRB) O(logRB) 穩定 O(n)

B是真數(0-9),

R是基數(個十百)

Shell O(nlogn) O(ns) 1<2 不穩定 O(1) s是所選分組
快速 O(nlogn) O(n2) 不穩定 O(nlogn) n大時較好
歸並 O(nlogn) O(nlogn) 穩定 O(1) n大時較好
O(nlogn) O(nlogn) 不穩定 O(1) n大時較好

 

 

 

========

https://www.cnblogs.com/BobHuang/p/11263183.html

 

dp其實就是在用空間去換取時間

 

常見時間復雜度的 “大O表示法” 描述有以下幾種:

時間復雜度 非正式術語
O(1) 常數階
O(n) 線性階
O(n2) 平方階
O(log n) 對數階
O(n log n) 線性對數階
O(n3) 立方階
O(2n) 指數階

一個算法在N規模下所消耗的時間消耗從大到小如下:

O(1) < O(log n) < O(n) < O(n log n) < O(n2) < O(n3) < O(2n)

指數級的增長是非常快的

 

常見的排序算法

根據時間復雜度的不同,常見的算法可以分為3大類。

1.O(n²) 的排序算法

  • 冒泡排序

  • 選擇排序

  • 插入排序

2.O(n log n) 的排序算法

  • 希爾排序
  • 歸並排序

  • 快速排序

  • 堆排序

3.線性的排序算法

  • 計數排序

  • 桶排序

  • 基數排序

各種排序的具體信息

 

 

冒泡排序(Bubble Sort)

冒泡排序(Bubble Sort) 是一種基礎的 交換排序

//兩兩比較,大的后移,移動最后
//穩定:因為相鄰的兩個數如果相等的話,不會做交換

 

 

插入排序:打撲克

 

 

希爾排序【分組的插入排序】

 

 

#include <iostream>
using namespace std;

void InsertSort(int arr[], int arr_size)
{
    for(int i = 1; i < arr_size; ++i)
    {
        int tmp = arr[i];
        int j = i - 1;
        for(; j > 0; --j)
        {
            if(tmp >= arr[j])
            {
                break;
            }
            else
            {
                arr[j+1] = arr[j];
            }
        }
        arr[j+1] = tmp;
    }
}

void InsertSort2(int arr[], int arr_size)
{
    int start;
    int end;
    int temp=0;
    int mid,j;

    for(int i=1;i<arr_size;i++)
    {
        start=0;
        end=i-1;
        temp=arr[i];

        while(start<=end)
        {
            mid=(start+end)/2;
            if (temp<arr[mid])
            {
                end=mid-1;
            }
            else
            {
                start=mid+1;
            }
        }
        cout << start << ", " << end << endl;

        //while循環完后,start=end+1,此時start為當前插入數字所待坑位!
        //把坑位給當前插入的數據挪出來
        /*
        for( j = i-1;j >= start;j-- )
        {
            arr[j+1] = arr[j];
        }
        */

        for( j = i;j > start;j-- )
        {
            arr[j] = arr[j-1];
        }

        //將當前插入數字挪入它該待的坑位
        arr[start] = temp;
    }
}

//二分排序 = 插入排序 + 二分查找
void InsertSort3(int arr[], int arr_size)
{
    int start;
    int end;
    int temp=0;
    int mid,j;

    for(int i=1;i<arr_size;i++)   //准備給i位進行插入排序
    {
        start=0;
        //end=i-1;   //此時前i-1位是已經排序好的, 所以作為end;
        end = i; //如果用左閉右開,end = i.
        temp=arr[i];   //待插入的數

        while(start<end)   //[左閉,右開)
        {
            mid=start + (end - start) / 2; //從閉區間向中點靠近
            //mid = last - (last - first) // (左開,右閉],從右向左靠近
            if (temp<arr[mid])
            {
                end=mid;  //左閉右開
            }
            else
            {
                start=mid+1;
            }
           }
        }
        cout << start << ", " << end << endl;

        for( j = i;j > start;j-- )   //依次后移,給i騰位置
        {
            arr[j] = arr[j-1];
        }

        //將當前插入數字挪入它該待的坑位
        arr[start] = temp;
    }
}

//兩兩比較,大的后移,移動最后
//穩定:因為相鄰的兩個數如果相等的話,不會做交換
void BubbleSort(int arr[], int arr_size)
{
    for(int i = arr_size - 1; i >=0; --i)  //從后往前,最后的是最大的
    {
        for(int j = 0; j < i; ++j)  //j不要超過i
        {
            if(arr[j] > arr[j+1])
            {
                std::swap(arr[j], arr[j+1]);
            }
        }
    }
}

void BubbleSort2(int arr[], int arr_size)
{
    for (int i = 0; i < arr_size - 1; i++)
    {
        for (int j = 0; j < arr_size - 1 - i; j++) //這里的兩個for跟上面的BubbleSort一個意思,只不過不太好理解,j < arr_size -1 - i; 也是從后往前,最后的最大
        {
            if (arr[j] > arr[j + 1])
            {
                swap(arr[j], arr[j + 1]); //不滿足偏序,交換
            }
        }
    }
}

//選擇最大的,放到最后;或者選擇最小的,放在最前面。
void SelectSort(int arr[], int arr_size)
{
    for(int i = arr_size - 1; i >=0; --i)  //從后往前,最后的是最大的
    {
    {
        int max_index = i;
        for(int j = 0; j < i; ++j)
        {
            if(arr[j] > arr[max_index])
            {
                max_index = j;
            }
        }
        std::swap(arr[max_index], arr[i]);
    }
}

void SelectSort2(int arr[], int arr_size)
{
    for (int i = 0, minIndex; i < arr_size - 1; i++) //從前往后,選擇最小的,放到前面已有序的最后
    {
        minIndex = i;                     //最小下標
        for (int j = i + 1; j < arr_size; j++) //訪問未排序的元素
        {
            if (arr[j] < arr[minIndex])
                minIndex = j; //找到最小的
        }
        swap(arr[i], arr[minIndex]);
    }
}

//把剩下的未排序的數字插入到前面已拍好序的正確位置[從后向前比較,往后移],打撲克。
void InsertSortNew(int arr[], int arr_size)
{
    for (int i = 1; i < arr_size; ++i)   //第一個認為已排好序,從第二個開始進行插入排序;i:下一個待插入的數字
    {
        int tmp = arr[i];
        int j = i;   //從i開始,向前找到合適的位置
        for(; j > 0; --j)
        {
            if(tmp < arr[j-1])  //比arr[i]大的數字,依次后移
            {
                arr[j] = arr[j-1];
            }
            else
            {
                break;
            }
        }
        arr[j] = tmp;
    }
}

void ShellSort(int arr[], int arr_size)
{
}

int main()
{
    int arr[] = {1, 867, 76, 12, 40, 16, 678, 990, 1089, 431, 23, 68, 9, 3,2, 10, 6, 4, 7, 11, 20, 8, 6};
    //InsertSort3(arr, sizeof(arr) / sizeof(arr[0]));
    //BubbleSort(arr, sizeof(arr) / sizeof(arr[0]));
    //BubbleSort2(arr, sizeof(arr) / sizeof(arr[0]));
    //SelectSort(arr, sizeof(arr) / sizeof(arr[0]));
    InsertSortNew(arr, sizeof(arr) / sizeof(arr[0]));


    [&arr]() {for(auto i : arr) cout << i << endl;}();

}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM