排序【3】交換排序

冒泡排序

基本思想:爲什麼叫冒泡排序呢,,是因爲每一次排序就像是魚兒吐泡泡,一個個泡泡從水底到水面會變得越來越大,泡泡就像元素,隨着算法的進行,較大的元素就會到後面去。如果升序排列,那麼就會對相鄰兩個元素進行比較,如果前者大於後者,那麼就交換這了兩個元素,直到最大的元素被排到最後一個位置,第一趟冒泡完成,進行第二趟冒泡,把次大的元素排到倒數第二的位置,重複這個過程。直到所有的元素都被冒泡到合適的位置,停止。
具體步驟:

這裏寫圖片描述
代碼實現

template<class T>//仿函數,本質是函數對象,對()進行重載,功能類似於函數  
struct Less  
{  
    bool operator ()(const T& left,const T&right)  
    {  
        return left < right;  
    }  

};  
template<class T>  
struct Greater  
{  
    bool operator ()(const T& left, const T&right)  
    {  
        return left > right;  
    }  
};  
template < class T,class compare=Less<T>>//使用模板參數,處理不同類型數據,以及指定排序方式,這裏默認降序  
void bubblesort(T* array,int sz)  
{  
    bool IsChanged = false;//  
    for (int i = 0; i < sz - 1; i++)  
    {  
        bool IsChanged = false;  
        for (int j = 0; j < sz - 1 - i; j++)  
        {  
            if (compare()(array[j], array[j + 1]))//仿函數  
                swap(array[j], array[j + 1]);  
                IsChanged = true;  
        }  
        if (IsChanged == false)//如果一次都沒交換,結束循環,提高程序效率  
            break;  
    }  

} 

算法性能
時間複雜度:平均時間複雜度O(N^2)最好和最壞情況下。
空間複雜度:O(1)
穩定性:穩定


快速排序

基本思想:首先,在待排序列中選擇一個key值,然後將其他元素以此和key值比較,比key值小的放到key的前面,比key大的放到key的後面,這樣就吧序列分成兩個區間,然後遞歸起左右區間,直到區間只剩一個元素的時候,那麼停止遞歸,此時可以認爲子區間是已經有序,然後一層一層的往上返回,結束後,整個區間就已經排好序。具體看下圖。
這裏寫圖片描述
具體步驟

  1. 首先,在待排序的區間選出來一個基準值key.
  2. 用key依次跟去他元素比較,把比key小的元素放到key的左邊,比key大的放到key的右邊,此時key就像是一個分水嶺,將數據分成兩部分。
  3. 遞歸排序key的左右區間,重複上述1,2步驟。
  4. 直到區間元素只剩一下的時候,停止遞歸。

實現方法
我目前瞭解的實現快排的方法有三種,分別是左右指針法,挖坑法,和前後指針法。
1.左右指針法
實現步驟:

  1. 定義兩個指針left和right分別指向待排序區間的第一個元素和最後一個元素,這裏我們選擇left指向的元素爲key,那麼左指針就需要先走。
  2. 左指針向右走,當尋找到比key大的元素就停下,右指針向左走,找到比key小的元素就停下來。
  3. 如果兩個指針沒有相遇,就交換左右指針指向的元素。
  4. 直到兩個指針相遇就結束,然後把左指針指向的位置賦值爲key。
    思想流程圖:
    這裏寫圖片描述
    實現代碼
int PartSort1(int *array, int left,int right)//區間左畢右畢
{
    int key = right;
    while (left < right)
    {
        //左指針尋找比key大的元素
        while (left < right&&array[left] < key)
        {
            left++;
        }
        while(left<right&&array[right]>key)//右指針尋找比key小的元素
        {
            right--;
        }
        if (array[left] != array[right])
        {
            swap(array[left], array[right]);
        }
    }
    array[left] = key;
    return left;
}   

2.挖坑法
具體步驟:

  1. 首先用left和right兩個指針標記待排序列第一個和最後一個元素,這裏選擇key爲right指向的元素,並且第一個坑設置在key的位置.
  2. 讓left向右尋找比key大的元素,找到了就停下來,把這個元素放到坑的位置,把left指向的位置設爲新的坑。
  3. 接下來right向左走,找比key小的元素,找到了就把這個元素填入坑中,並把right現在的位置設爲新的坑。
  4. 重複上述2,3步驟,直到left和right相遇停止,然後最後把坑的位置設置成key.
    思想流程圖:
    這裏寫圖片描述
    實現代碼:
int PartSort2(int *array, int left, int right)//挖坑法
{
    int key = right;
    int keng = right;
    while (left < right)
    {
        while (left < right&&array[left] < key)
        {
            left++;
        }
        array[keng] = array[left];
        keng = left;
        while (left<right&&array[right]>key)
        {
            right--;
        }
        array[keng] = array[right];
        keng = right;
    }
    array[keng] = key;
    return keng;

}

3.前後指針法
具體步驟:

  1. 先讓pcur指向第一個元素,prev指向pcur的前一個元素,此時選取最後一個元素爲key。
  2. pcur向後走找到比key小的元素就停止,++prev,當prev和pcur不相等的時候,交換兩個位置的元素,相等不交換。
  3. pcur一直往後走,重複2過程,當走到右邊界的時候,++prev,並且交換prev和pcur位置的元素。
    思想流程圖:
    代碼實現:
int PartSort(int *array, int left, int right)
{
    assert(array);
    int key = right;
    int pcur = left;
    int prev = left - 1;
    while (pcur<right)
    {

        if (array[++prev] !=array[ pcur]&&array[pcur]<key)
        {
            swap(array[prev], array[pcur]);
        }
    }
    ++prev;
    swap(array[prev], array[pcur]);
}

快排的性能

時間複雜度:快排是一種已知道的最快的排序算法,它的平均時間複雜度爲O(NlogN),但是它也是一種不穩定的算法,當它的基準值選取的不合理的時候,那麼就會造成O(N^2)的時間複雜度
空間複雜度:O(logN)
穩定性:不穩定
所以說,選擇合理的基準值key,影響着快排的效率,上述的基準值都是取的是最後一個元素,下面我們將算法進行改進,通過幾種合理的取數法,使得快排左右區間儘可能的分配均勻。
1.三數取中法
基本思想:我們爲了把區間儘可能的分配均勻,在取基準值的時候,選取數組最左邊,最中間,最右邊的元素三個元素中中間大小的元素作爲基準值。

int GetMidIndex(int *array, int left, int right)
{
    int mid = left + ((right - left) >> 1);
    if (array[left] < array[right])
    {
        if (array[mid] < array[left])
            return left;
        else if (array[mid] > array[right])
            return right;
        else
            return mid;

    }
    else
    {
        if (array[mid] > array[left])
            return left;
        else if (array[mid] < array[right])
            return right;
        else
            return mid;
    }
}

2.小區間優化
基本思想:當區間較小時候,一般認爲有13個元素以下,這個時候,快速排序就沒有直接插入的性能好了,因爲當區間比較小的時候,區間劃分的就比較多,快排就像一顆二叉樹一樣,每一次遞歸都相當於增加一層高度,當區間比較小的時候,就會快速增加二叉樹高度,降低了效率,因此我們在劃分到小區間的時候就改爲插入排序。
代碼實現:

void InsertSort(int *array,int size)
 {
    for (int i=1;i<size;i++)
    {
        int end = array[i];
        int j = i - 1;
        while (j >= 0 && array[j] > end)
        {
            array[j + 1] = array[j];
            j--;
        }
        array[j + 1] = end;

    }

3.非遞歸實現
基本思想:我們前面講的快排都是基於遞歸子區間來實現的,而當數據元素比較多的時候,難免遞歸的次數會非常多,而每次函數的遞歸都是一個函數的棧幀過程,十分消耗時間,因此可以藉助棧這種方式,來實現遞歸轉非遞歸。
代碼實現:

void QuickSortNoeR(int*array, int left, int right)
{
    stack<int> s;
    s.push(left);
    s.push(right);
    while (!s.empty())
    {
        int start = s.top();
        s.pop();
        int finish = s.top();
        s.pop();
        int div = PartSort1(array, start, finish);
        if (start < div - 1)
        {
            s.push(div - 1);
            s.push(start);
        }
        if (finish > div + 1)
        {
            s.push(finish);
            s.push(div + 1);
    }

    }

}

這就是對於交換排序,本人的學習之談。

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