各種排序總結

    本文對各種排序做一個總結(這裏不簡述具體的過程),包括冒泡排序、選擇排序、快速排序、歸併排序、堆排序。分別從時間複雜度、空間複雜度、算法的最壞情況以及穩定性方面分析,遞歸的儘量會附帶非遞歸實現。

1. 冒泡排序

    冒泡排序是一種很簡單的排序算法,這裏直接上代碼(從小到大排序):

bool flag = true;//如果在某一趟排序中數列已經有序,則結束排序過程
for(int i = 0;i < n-1 && flag; ++i){
    flag = false;
    for(int j = 0; j < n-i-1; ++j){//減一是爲了實現g[j+1]
        if(g[j] > g[j+1]){
            swap(g[j], g[j+1]);
            flag = true;
        }
    }
}

時間複雜度:時間複雜度是O(n^2),假設排序的數有n個,遍歷一趟的複雜度是O(n),需要遍歷n-1次,所以時間複雜度爲O(n^2)。

空間複雜度:O(1),因爲不需要額外的存儲空間。

最壞情況:要排序的數是逆序排好序的。

穩定性:冒泡排序是穩定的算法,因爲它實現相鄰兩個元素之間的交換,沒有改變元素的相對位置,所以滿足穩定性。

2. 選擇排序

    選擇排序是一種比較好理解的排序,可以說是冒泡排序的兄弟了,只是排序方式不一樣,下面直接給出代碼:

//選擇排序
void Select_Sort(int a[], int n){
    int Min;
    for(int i = 0;i < n-1; ++i){//n-1次選擇就可以了
        Min = i;
        for(int j = i+1; j < n; ++j){//從i - n之間選擇一個最小的放在 i 處。
            if(a[j] < a[Min]){
                Min = j;
            }
        }
        if(Min != i){
            swap(a[i], a[Min]);
        }
    }
}

時間複雜度:O(n^2),因爲每次遍歷的複雜度是O(n)的,一共遍歷n-1次,所以複雜度是O(n^2)。

空間複雜度:O(1)

最壞情況:排序的數和複雜度沒關係,屬於佛系。

穩定性:選擇排序是不穩定的排序,例如有5 8 5 2 9 ,5個元素按從小到大排序,當第一趟排序後形成2 8 5 5 9 ,兩個5的相對位置最終排好序後發生了變化。

3. 快速排序

    快速排序(Quick Sort)使用分治法策略它的基本思想是:選擇一個基準數,通過一趟排序將要排序的數據分割成獨立的兩部分;其中一部分的所有數據都比另外一部分的所有數據都要小。然後,再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。快速排序經常容易被改造用在其它的一些方面,如求數組中第k大的數。快排的基準值的選取會對排序結果有一定影響。快速排序是面試中常考的類型,需要關注非遞歸的方式。

    這裏不對算法進行相應描述,直接看代碼(其中基準值是隨機選取的):

/*快速排序
 * l ----- 序列左邊下標
 * t ----- 序列右邊下標
 * a[] --- 數組
 */
int Partition(int a[], int l, int t){//劃分
    int idx = l + rand()%(t - l + 1);//基準值是在隨機選取的
    swap(a[l], a[idx]);
    int i = l;
    int j = t;
    int x = a[i];
    while(i < j){
        while(i < j && a[j] > x)
            j--;
        if(i < j)
            a[i++] = a[j];
        while(i < j && a[i] < x)
            i++;
        if(i < j)
            a[j--] = a[i];
    }
    a[i] = x;
    return i;
}
void Quick_Sort(int a[], int l, int t){
    if(l < t){
        int mid = Partition(a, l, t);//劃分,返回基準值下標
        Quick_Sort(a, l, mid-1);//遞歸調用
        Quick_Sort(a, mid+1, t);//遞歸調用
    }
}

非遞歸代碼:

typedef pair<int, int> Pair;
int Partition(int a[], int lt, int rt){//劃分
    int i = lt;
    int j = rt;
    int rm = i + rand()%(j - i + 1);//基準值是在隨機選取的
    swap(a[i], a[rm]);
    int x = a[i];
    while(i < j){
        while(i < j && a[j] > x)
            j--;
        if(i < j)
            a[i++] = a[j];
        while(i < j && a[i] < x)
            i++;
        if(i < j)
            a[j--] = a[i];
    }
    a[i] = x;
    return i;
}
void Quick_Sort(int a[], int l, int t){
    stack<Pair>S;
    S.push(Pair(l, t));
    while(!S.empty()){
        int lt = S.top().first;
        int rt = S.top().second;
        S.pop();
        int mid = Partition(a, lt, rt);
        if(mid+1 < rt)
            S.push(Pair(mid+1, rt));
        if(lt < mid-1)
            S.push(Pair(lt, mid-1));
    }
}

這裏增加一種用鏈表實現快速排序(面試中可能會問到):

思想:用鏈表排序主要的困難是如何實現鏈表的基於基準值的劃分,這裏假設傳入的鏈表的頭和尾節點分別爲Head和end(這裏需要同時給出頭節點和尾節點),因爲不能像數組那樣隨機存儲,所以這裏以頭節點Head值爲基準值,設置兩個指針(個人感覺:用到鏈表的一般都是設置2個指針,當然有可能是一個或者三個,四個應該基本不會有吧!)small 和 p。

  1. small 指向Head,p 指向Head->next,p用於向下遍歷,尋找小於Head->data值的節點;
  2. 當p遍歷到一個節點的值小於Head->data的值的時候,small先進行small = small->next,因爲small指向的是小於Head->data(初始時指向Head->data),進行small = small->next後small指向的值就大於等於Head->data的值了,然後將small指向的值與p指向的值進行交換。p = p->next;
  3. 繼續進行(2),知道p遍歷完所有的值;
  4. 將Head->data的值與small節點的值交換就可以了;

建議按照下面代碼模擬一下就很明白了,可能這樣說不是很明白。

struct ListNode{
    int data;
    ListNode *next;
    ListNode(int _data = 0):data(_data),next(NULL){}
};
void Quick_Sort(ListNode *head, ListNode *end){
    if(head == NULL || head == end)             //如果頭指針爲空或者鏈表爲空,直接返回
        return ;
    ListNode *p = head -> next;                  //用來遍歷的指針
    ListNode *small = head;
    while( p != end->next){
        if(p -> data < head -> data){      //對於小於軸的元素放在左邊
            small = small -> next;
            swap(small->data, p->data);
        }
        p = p->next;
    }
    swap(head->data, small->data);         //遍歷完後,對左軸元素與small指向的元素交換
    Quick_Sort(head, small);               //對左右進行遞歸
    Quick_Sort(small->next, end);
}

時間複雜度:快速排序最壞時間複雜度是O(n^2),在(1)數組中的元素全部相同,(2)數組已經排好序或逆序(這要看選取的基準值,如果總是選取最左邊的作爲基準值則是O(n^2))這兩類情況下快速排序會達到最壞的情況。從這裏可以看出選取基準值很重要,如果基準值隨機選取,快排很難達到最壞情況。平均時間複雜度是O(n*log2(n)),可以這樣理解:快排是採用分治的策略,可以把這個過程看成一個二叉樹,則遍歷一層的時間複雜度是O(n),樹的高度爲log2(n+1),則複雜度爲O(n*log2(n))。

空間複雜度:O(logn)~O(n),這裏看的是遞歸的深度。

穩定性:快排是一種不穩定的算法,因爲在選取基準值相互交換的時候,元素的相對位置會變化。

4. 歸併排序

    歸併排序算法是典型的分治算法,其算法思想是先將數組對半分開,然後將得到的兩個數組再對半分開,一直到只有一個元素爲止,然後再進行兩兩合併,最後合併爲一個大的數組,具體思路可看下圖。

代碼實現:

void Merge(int a[], int lt, int rt, int p[]){//p是臨時數組
    int mid = (rt - lt)/2 + lt;
    int i = lt, j = mid + 1;
    int k = 0;
    while(i <= mid && j <= rt){
        if(a[i] <= a[j]){
            p[k++] = a[i++];
        }else {
            p[k++] = a[j++];
        }
    }
    while(i <= mid){
        p[k++] = a[i++];
    }
    while(j <= rt){
        p[k++] = a[j++];
    }
    for(i = 0; i < k; ++i){
        a[lt+i] = p[i];
    }
}
void MergeSort(int a[], int lt, int rt, int p[]){
    if(lt < rt){
        int mid = (rt - lt)/2 + lt;
        MergeSort(a, lt, mid, p);
        MergeSort(a, mid+1, rt, p);
        Merge(a, lt, rt, p);
    }
}

時間複雜度:歸併排序最好、最壞、平均時間複雜度都是O(nlogn),同樣可以把歸併排序的過程看成一個完全二叉樹(這裏要和快速排序區分),劃分的時間複雜度爲O(logn),每層合併的時間複雜度爲O(n),所以總的時間複雜度爲O(nlogn)。

最壞情況:複雜度和排序的數的順序無關,佛系。

空間複雜度:O(n),用到一個n的輔助數組。

穩定性:歸併排序是穩定的。

5. 堆排序

    堆排序(Heap_Sort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,它是選擇排序的一種。可以利數組特點快速定位指定索引的元素。堆分爲大根堆和小根堆,是完全二叉樹。常用於求前1-k的所有數字。

 堆排序的代碼如下:

//向下調整
void Adjust_Heap(int a[], int idx, int Len){
    while(idx*2+1 < Len){
        int temp = idx*2+1;
        if(temp+1 < Len && a[temp+1] > a[temp]){
            temp++;
        }
        if(a[idx] < a[temp]){
            swap(a[idx], a[temp]);
            idx = temp;
        }else break;
    }
}

void Heap_Sort(int a[], int n){
    //建立大頂堆
    for(int i = (n-1)/2; i >= 0; --i){
        Adjust_Heap(a, i, n);
    }
    //排序
    for(int i = 0;i < n; ++i){
        swap(a[0], a[n-i-1]);
        Adjust_Heap(a, 0, n-i-1);
    }

}

時間複雜度:建堆的複雜度是O(n)(這個需要計算一下),排序的複雜度是n * lgn 所以是O(nlog2(n))。

空間複雜度:O(1)

穩定性:堆排序是不穩定的。舉個栗子:1 2 2 的情況就不滿足(從小到大排序),因爲排序後兩個2的相對位置變了。

                 

Reference :

https://blog.csdn.net/jiajing_guo/article/details/69388331

 

 

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