各主流排序算法詳細介紹(時間/空間復雜度,適用范圍和穩定性)


一,插入排序

插入排序基本思想:

  在一個已經有序的序列里插入新的元素,直到有序序列包含所有被排序元素。

例子:

   

代碼實現:

void InsertSort(vector<int> &v)
{
    for(int i = 1;i < v.size(); ++i)//i表示有序集合里的元素數目和待插入元素下標
    {
        for(int j = i; j > 0 && v[j-1] > v[j]; --j)
        {
            int temp = v[j-1];
            v[j-1] = v[j];
            v[j] = temp;
        }
    }
}
View Code

時間復雜度為O(N^2)

空間復雜度為O(1)

插入排序在小規模數據時或者基本有序時比較高效。

 

二,希爾排序

希爾排序基本思想:

  希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止。本質上是分組插入排序。

例子:

   

 

 在取增量d的時候一般有以下要求:

①最后一次排序d = 1;

②每次取的d並不包含處1以外的公因子;

③采用縮小增量法,d的取值逐步縮小。

代碼實現:

void ShellSort(vector<int> &v)
{
    int k = 0;
    int t = v.size() - 1;
    int delta = 2*t - k - 1;//有研究表明,delta取2*t-k-1時時間復雜度可達O(N^1.5) 
    for(k = 0;k < v.size(); ++k)
    {
        delta = (2*t-k-1) == t?(2*t-k-1):1;//最后一次delta要取1
        for(int i = 0; i < v.size()-delta; ++i)
        {
            if(v[i] > v[i+delta])
            {
                int temp = v[i];
                v[i] = v[i+delta];
                v[i+delta] = temp;
            }
        }
    }
}
View Code

希爾排序delta開始會很大,所以該排序不穩定。

希爾排序的復雜度與delta相關:

{1,2,4,8,...}使用這個增量序列的時間復雜度(最壞情形)是O(N^2)

代碼中所給序列的時間復雜度時O(N^1.5)

空間復雜度為O(1)

希爾排序為插入排序的一種優化,其適用范圍為:大規模且無序的數據。

 

三,冒泡排序

冒泡排序基本思想:

  將第一個記錄的關鍵字與第二個記錄的關鍵字進行比較,若逆序則交換;然后比較第二個記錄與第三個記錄;依次類推,直至第n-1個記錄和第n個記錄比較為止——第一趟冒泡排序,結果關鍵字最大的記錄被安置在最后一個記錄上。

  對前n-1個記錄進行第二趟冒泡排序,結果使關鍵字次大的記錄被安置在第n-1個記錄位置。重復上述過程,直到在一趟排序過程中沒有進行過交換操作”為止。

例子:

  

代碼實現:

void BubbleSort(vector<int> &v)
{
    for(int i = 0; i < v.size(); ++i)
    {
        for(int j = 0;j < v.size() - i; ++j)
        {
            if(v[j] < v[j-1])
            {
                int temp = v[j];
                v[j] = v[j-1];
                v[j-1] = temp;
            }
        }
    }
}
View Code

時間復雜度為O(N^2)

空間復雜度為O(1)

冒泡排序是穩定的排序算法。

 

四,快速排序

快速排序基本思想:

  快速排序的本質是分治法。從數列中挑出一個元素,稱為 “基准”(pivot) 分區(partition):將序列分區,使所有元素比基准值小的放在基准前面,所有元素比基准值大的放在基准的后面(相等的數可以到任一邊)(Divide) 排序:遞歸地(recursive)把小於基准值元素的子數列和大於基准值元素的子數列排序(Conquer)。每次遞歸都會把基准放在正確的位置,即每次遞歸都排好一個元素,並將小於這個元素的放左邊,大於這個元素的放右邊。

例子:

  記low為起始位置,high為末位置。基准一般取R[low],R[high],R[(low+high)/2]處於中間大小的數。這里敘述方便,直接取R[0]為基准。

①首先從后半部分開始,如果掃描到的值大於基准數據就讓high減1,如果發現有元素比該基准數據的值小,就將high位置的值賦值給low位置 。

  

 ②然后開始從前往后掃描,如果掃描到的值小於基准數據就讓low加1,如果發現有元素大於基准數據的值,就再將low位置的值賦值給high位置的值,指針移動並且數據交換后的結果如下:

 

 ③繼續進行以上步驟,直到low >= high 停止掃描,此時基准R[0]處於正確的位置上。

 ④然后遞歸的處理左子序列和右子序列。

代碼實現:

int Parition(vector<int>&v,int low,int high)
{
    int ref = v[low];
    if(low < high)
    {
        while(low < high)
        {
            while(low < high && v[high] >= ref)
            {
                --high;
            }
            v[low] = v[high];
            while(low < high && v[low] <= ref)
            {
                ++low;
            }
            v[high] = v[low];
        }
        v[low] = ref;
    }
    return low;
}

void QSort(vector<int>&v,int low,int high)
{
    if(low < high)
    {
        int index = Parition(v,low,high);
        QSort(v,low,index-1);
        QSort(v,index+1,high);
    }
}

void QuickSort(vector<int>&v)
{
    QSort(v,0,v.size()-1);
}
View Code

 時間復雜度為O(NlogN)

空間復雜度為最優的是O(logN),最差的為O(N)

 不穩定。

 

五,簡單選擇排序

簡單選擇排序基本思想:

  首先通過n-1次關鍵字比較,從n個記錄中找出關鍵字最小的記錄,將它與第一個記錄交換;

  再通過n-2次比較,從剩余的n-1個記錄中找出關鍵字次小的記錄,將它與第二個記錄交換;

  重復上述操作,共進行n-1趟排序后,排序結束。

例子:

  

代碼實現:

void SelectSort(vector<int>& v)
{
    for(int i = 0; i < v.size(); ++i)
    {
        int MIN = v[i],pos = i;;
        for(int j = i; j < v.size(); ++j)
        {
            if(MIN > v[j])
            {
                MIN = v[j];
                pos = j;
            }
        }
        v[pos] = v[i];
        v[i] = MIN;
    }
}
View Code

時間復雜度為O(N^2)

空間復雜度為O(1)

 不穩定,交換過程中可能發生相同數字的次序顛倒。

 

六,堆排序

堆排序基本思想:

  堆是一個具有這樣性質的完全二叉樹:每個非終端結點(記錄)的關鍵字大於等於(小於等於)它的孩子結點的關鍵字。堆排序是利用堆的特性對記錄序列進行排序的一種排序方法。將無序序列建成一個堆,得到關鍵字最小(或最大)的記錄;輸出堆頂的最小(大)值后,使剩余的n-1個元素重又建成一個堆,則可得到n個元素的次小值;重復執行,得到一個有序序列。因為堆是完全二叉樹,所以我們可以用數組來儲存。只要將需要排序的數組建立成堆,然后每次取出根結點,就把剩下的結點調整成堆;再取出根結點,如此下去,最后便能得到排好序的數據。

例子:

 建堆:

給定需要排序的6個數字:

①保證[0,0]范圍內是個最大堆,12本身是個堆。

②保證[0,1]范圍內是個最大堆,因為34 > 12,12在34的父節點上,所以需要調換12和34

③再保證[0,3]內是個最大堆,12 54都是34的兒子,所以調換34和54

 ④重復以上步驟,直到[0,n-1]都是個最大堆

建堆結果是:

調整堆並排序:

成為堆以后那么heap[0]一定是該組數的最大值/最小值(這里建的是最大堆,所以heap[0]是最大值)。

①交換63和24,已知63是最大值,則63在排序后的正確位置上,我們假裝數組只有n-1個數,把63踢出去。

 ②然后對[0,n-2]的所以數據進行調整,挑24左右兒子最大的與其比較,54 > 24,所以交換位置。

然后12和31是交換位置后的24的左右兒子,又因為31 > 24交換24,31。

 這已經是一個最大堆了,將54和24交換位置(首位交換),54處在正確的位置上。

 繼續重復上述過程,直到每個數都排好。

代碼實現:

void GetHeap(vector<int>& v)//將數組v調整為最大堆
{
    for(int i = 0; i < v.size(); ++i)
    {
        int CurrentIndex = i;//新插入的結點
        int Father = (CurrentIndex - 1) / 2;
        while(v[CurrentIndex] > v[Father])
        {
            int temp = v[CurrentIndex];
            v[CurrentIndex] = v[Father];
            v[Father] = temp;
            CurrentIndex = Father;
            Father = (CurrentIndex - 1) / 2;
        }
    }
}

void AdjustHeap(vector<int>& v,int index,int size)//交換首尾元素后調整最大堆
{
    int right = 2*index + 2;
    int left = 2*index + 1;
    while(left < size)
    {
        int LargestIndex;
        if(v[left] < v[right] && right < size)
        {
            LargestIndex = right;
        }
        else
        {
            LargestIndex = left;
        }
        if(v[index] > v[LargestIndex])
        {
            LargestIndex = index;
        }
        if(index == LargestIndex)
        {
            break;
        }
        int temp = v[LargestIndex];
        v[LargestIndex] = v[index];
        v[index] = temp;
        index = LargestIndex;
        left = 2*index + 1;
        right = 2*index + 2;
    }
}

void HeapSort(vector<int>& v)
{
    GetHeap(v);
    int size = v.size();
    while(size > 1)
    {
        int temp = v[0];
        v[0] = v[size - 1];
        v[size - 1] = temp;
        --size;
        AdjustHeap(v,0,size); 
    }
}
View Code

時間復雜度為O(NlogN)

空間復雜度為O(1)

不穩定。

 

七,歸並排序

歸並排序基本思想:

  歸並——將兩個或兩個以上的有序表組合成一個新的有序表,叫歸並。

  2-路歸並排序:將兩個位置相鄰的記錄有序子序列歸並為一個記錄的有序序列。

  如果記錄無序序列 R[s..t] 的兩部分 R[s...(s+t)/2]   和   R[(s+t)/2+1..t] 分別按關鍵字有序, 則很容易將它們歸並成整個記錄序列是一個有序序列。具體看例子。

例子:

  對一個序列不斷遞歸(分治法),然后兩兩歸並並排序。

  

代碼實現:

void MergeArray(vector<int> &v, int left, int mid, int right, int temp[])//歸並並排序
{
    int i = left,j = mid+1;
    int n = mid,m = right;
    int k = 0;

    while(i <= n && j <= m)
    {
        if(v[i] < v[j])
        {
            temp[k++] = v[i++];
        }
        else
        {
            temp[k++] = v[j++];
        }
    }
    while(i <= n)
    {
        temp[k++] = v[i++];
    }
    while(j <= m)
    {
        temp[k++] = v[j++];
    }
    for(int i = 0; i < k; ++i)
    {
        v[left+i] = temp[i];
    }
}

void Divide(vector<int>& v,int left,int right,int temp[])//分治
{
    if(left < right)
    {
        int mid = (left+right)/2;
        Divide(v,left,mid,temp);
        Divide(v,mid+1,right,temp);
        MergeArray(v,left,mid,right,temp);
    }
}

bool MergeSort(vector<int>& v)
{
    int* temp = new int[v.size()];
    if(temp == NULL)
    {
        return false;
    }
    Divide(v,0,v.size()-1,temp);
    delete [] temp;
    return true;
}
View Code

時間復雜度非常穩定,最好和最差都是O(NlogN)

空間復雜度為O(N) (new了一個大小為n的數組)


免責聲明!

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



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