1. 程式人生 > >常用排序演算法--合併排序和快速排序

常用排序演算法--合併排序和快速排序

常用排序演算法——合併排序

分治

分治(Divide and Conquer)是一場常見的演算法策略。分治策略的基本思想就是對於一個問題規模為N的問題,將其劃分為規模足夠小的K個子問題,子問題由於規模足夠小可以直接求解,最後將規模足夠小的K的問題的解合併得出原問題的解。
分治策略的問題的求解過程的一般套路就是:

  1. 判斷問題規模,足夠小進入步驟2,否者進入步驟3
  2. 直接對問題進行求解,返回問題的解。
  3. 將問題拆分成K個小規模的問題,進入步驟2。並將解合併,得到原問題的解。

由於分治策略的步驟描述自帶遞迴屬性,因此分治策略的演算法實現常採用遞迴來實現。遞迴的演算法通常會含有三部分,第一部分是個基準情況(Base Case)

,第二部分是問題拆分,並呼叫自身,第三部分是合併並返回結果。

分治策略中主要有兩個關鍵過程:

  • 將原問題拆分Division
  • 將各子問題合併Combination

分治策略的主要時間開銷發生在這兩個過程,通常分治策略有兩類

  1. 拆分容易,合併困難
  2. 拆分困難,合併簡單

下文中的合併排序快速排序分別屬於上面的第1和第2類。

合併排序和快速排序

在常見排序演算法中,合併排序和快速排序是典型的分治演算法,其時間複雜度的平均效能均為nlgn。合併排序的最壞時間複雜度也能保持nlgn,而快速排序的最壞時間複雜度卻為n*n合併排序是穩定的,而快速排序不是穩定的。另外合併排序是拆分容易,合併困難,而快速排序則是拆分困難,合併容易。

合併排序又稱歸併排序,主要的思想是:將待排序列拆分至數個足夠小的子序列,然後將相鄰子序列合併為一個有序子序列,重複合併相鄰有序子序列直到整個序列有序。

快速排序的主要思想則是,將待排序列拆分為左右兩個子序列A,B,使得子序列A的元素都小於子序列B,然後對子序列A,B繼續進行這種拆分,直到待拆分的序列足夠小,最後整個序列變成有序。

由於合併排序和快速排序每次元素的移動都不只移動了一個位置,因此每次元素的比較都不只消除了一個逆序對,因此對於插入排序這種每次比較只移動一個位置的演算法,時間複雜度會得到改善。

快速排序的遞迴實現

int partition(int* a, int l, int
h) { int pivot = a[l]; int i,j; i = l - 1; j = h + 1; while(true) { while(a[--j] > pivot); while(a[++i] < pivot) if(i >= h) break; //i 、j cross if(i >= j) break; int tmp = a[i]; a[i] = a[j]; a[j] = tmp; } a[j] = pivot; return j; } void quickSort(int* a, int l, int h) { if(l >= h) return; int p = partition(a, l, h); quickSort(a, l, p); quickSort(a, p+1, h); }

合併排序的遞迴實現

void merge(int* a, int l, int m, int h) {
    if(l == h)
        return;

    int* b = (int*)malloc(sizeof(int) * (h - l + 1));
    int i = l, j = m + 1;
    int k = 0;

    while(i <= m && j <= h)
        b[k++] = a[i] > a[j] ? a[j++] : a[i++];

    if(i <= m)
        while(i <= m)
            b[k++] = a[i++];
    if(j <= h)
        while(j <= h)
            b[k++] = a[j++];
    //回寫
    i = l;
    k = 0;

    for(; i <= h;)
        a[i++] = b[k++];
}

void mergeSort(int* a, int l, int h) {
    if(l >= h)
        return;
    int m = (h + l) / 2;
    mergeSort(a, l, m);
    mergeSort(a, m+1, h);
    merge(a, l, m, h);
}

合併排序的非遞迴實現

由於遞迴會導致函式呼叫棧的暴增,會引入額外的時間開銷,例如現場保護和恢復之類的。通遞迴版本的演算法都可以改寫為迭代版本的演算法。遞迴演算法的優點是實現簡單直觀,易於理解,缺點是如果有時候呼叫深度過深,會帶來棧記憶體溢位和額外的執行時間開銷。迭代演算法的有點是不會有額外的函式呼叫棧的增長, 缺點是難於理解且難於實現。

合併排序的迭代版本的實現相對來講是比較簡單直觀的,大致思路就是:第1輪,待排序列S中相鄰的長度為1的子序列進行合併,第2輪,序列S中相鄰的長度為2的子序列進行合併,第i輪,待排序列S中相鄰的長度為2的i-1次方的子序列進行合併。直到待合併的子序列數為1。

void merge(int* a, int l, int m, int h) {
    if(l == h)
        return;

    int* b = (int*)malloc(sizeof(int) * (h - l + 1));
    int i = l, j = m + 1;
    int k = 0;

    while(i <= m && j <= h)
        b[k++] = a[i] > a[j] ? a[j++] : a[i++];

    if(i <= m)
        while(i <= m)
            b[k++] = a[i++];
    if(j <= h)
        while(j <= h)
            b[k++] = a[j++];
    //回寫
    i = l;
    k = 0;

    for(; i <= h;)
        a[i++] = b[k++];
}

//非遞迴版本
void mergeIteation(int* a, int n) {
    int offset = 1;
    while(offset <= n) {
        int i = 0;
        for(;i < n;) {
            merge(a,i, i+offset-1,i+offset*2 - 1);
            i = i + offset * 2;
            if(n - 1 - i < offset)
                break;
        }
        offset *= 2;
    }
}

相關推薦

常用排序演算法java程式碼實現---快速排序,氣泡排序,選擇排序

快速排序 public class QuickSort { public void qSort(int[] arr,int left,int right) { if(left>right) { return ; } int i = le

七大排序演算法(5)------快速排序(遞迴非遞迴)

         在本文中使用到的升序,降序,交換函式的程式碼見:這篇部落格 快速排序(遞迴實現) 快速排序的基本思想是在待排序序列中找到一個基準值(一般取待排序序列的最後一個元素),然後將該基準值放置在一個合適的位置,使得在基準值之前的元素都小於等於基準值,基準值之後的

八大排序演算法(五)——快速排序

快速排序可能是應用最廣泛的排序演算法。快速排序流行的原因是因為它實現簡單、適用於各種不同的輸入資料且在一般應用中比其他排序演算法都要快的多。快速排序的特點包括它是原地排序(只需要一個很小的輔助棧),且將長度為n的陣列排序所需的時間和nlogn成正比。快速排序的內迴圈比大多數排序演算法都要短小,這

高階排序演算法【2】--快速排序、歸併排序、堆排序

4、快速排序 從數列中挑出一個元素,稱為基準;  重新排列數列,所有元素比基準小的擺放在基準前面,所有元素比基準大的擺在基準後面;  在這個分割槽結束之後,該基準就位於數列的中間位置;  遞迴地對基準左右兩邊的數列進行排序。 快速排序程式碼——第一步 def qui

排序演算法(5)--快速排序QuickSort

快速排序 時間複雜度: 平均O(nlogn) 最差的情況就是每一次取到的元素就是陣列中最小/最大的,這種情況其實就是氣泡排序了(每一次都排好一個元素的順序) 這種情況時間複雜度,就是氣泡排序的時間複雜度:T[n] = n * (n-1) = n^2 + n; 綜

雙路快速排序演算法及三路快速排序演算法視覺化

雙路快速排序演算法 工具類 import java.awt.*; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.lang.Interrup

java 實現 常見排序演算法(三)快速排序

大家好,我是烤鴨:        今天分享一下基礎排序演算法之快速排序。快速排序是內部排序(基於比較排序)中最好的比較演算法。   1.     快速排序:

五十道程式設計小題目 --- 28 八大排序演算法 java 之 06快速排序

6. 交換排序—快速排序(Quick Sort) 快速排序演算法介紹       快速排序和歸併排序都使用分治法來設計演算法,區別在於歸併排序把陣列分為兩個基本等長的子陣列,分別排好序之後還要進行歸併(Merge)操作,而快速排序拆分子陣列的時候顯得更有藝術,取一

排序演算法(五)快速排序多種版本

快速排序 ,就像它的名稱一樣,是時間複雜度比較低的一種排序演算法。      我們知道,快速排序是通過分治的方法,將一個大的區間劃分成小區間(找一個樞紐,將大的數放置在樞紐的右邊,小的數放置在樞紐左

排序演算法(三)——快速排序

快速排序(英語:Quicksort),簡稱快排,一種排序演算法。在平均狀況下,排序個專案要次比較。在最壞狀況下則需要次比較。 運作方式如下: 1.從陣列中挑出一個元素,作為基準,一般是第一個元素。 2.重新排序數列,所有比基準值小的元素擺放在基準前面,所有比基準值大的元素擺在基準後面(相

Java技術棧(2)排序演算法(冒泡,快速排序

1.氣泡排序 氣泡排序是一種簡單的排序演算法。它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個演算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端

Java排序演算法--建立堆排序(練習)

Java堆排序 堆排序的思想:迴圈建立堆,然後交換角標0的節點和角標最後的節點。即排序由兩部分組成,建立堆的滲透函式,和通過迴圈呼叫滲透函式 堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。 堆的定義如下:具有n個元素的序列(h1,h2,...,hn),當且僅當

排序演算法c語言描述-快速排序隨機化

今天在做資料結構排序實驗的時候,使用的快速排序。按理,我印象中快排是很高效的,不過,這次400w的資料,排了2659秒,有點意想不到,讓我一度懷疑了演算法是否寫錯了。 不過,認真分析,認真想想後, 也就釋然了。 快排其實就是冒泡的升級版。 每次遞迴,把當前序列分成兩部分,

資料結構 8 基礎排序演算法詳解、快速排序的實現、瞭解分治法

## 快速排序 快速排序與氣泡排序一樣,同樣是屬於`交換排序` 叫做快速排序也是有原因的。因為它採用了**分治法的概念** ![image.png](https://file.chaobei.xyz/blogs/image_1588862421197.png_imagess) 其中最重要的一個概念就是

常用排序演算法--合併排序快速排序

常用排序演算法——合併排序 分治 分治(Divide and Conquer)是一場常見的演算法策略。分治策略的基本思想就是對於一個問題規模為N的問題,將其劃分為規模足夠小的K個子問題,子問題由於規模足夠小可以直接求解,最後將規模足夠小的K的問題的

演算法分析與設計實驗 分治策略 兩路合併排序快速排序

實驗目的理解分治法的演算法思想,閱讀實現書上已有的部分程式程式碼並完善程式, 加深對分治法 的演算法原理及實現過程的理解。實驗內容用分治法實現一組無序序列的兩路合併排序和快速排序。要求清楚合併排序及快速排序 的基本原理, 程式設計實現分別用這兩種方法將輸入的一組無序序列排序

排序演算法中——歸併排序快速排序

氣泡排序、插入排序、選擇排序這三種演算法的時間複雜度都為 O (

php四種基礎演算法:冒泡,選擇,插入快速排序法 程式碼練習

function maopao($arr,$len) { for($i=1;$i<$len;$i++) { for($j=0;$j<$len-$i;$j++) { if($arr[$j]>$arr[$j+1])

幾種常用排序演算法的思路複雜度對比

1、插入排序——直接插入排序、希爾排序 (1)直接插入排序思路:從第1號元素開始,每個元素依次與前面的元素做比較,小的排前面,這樣當比較到最後一 個元 素完即完成排序。 (2)希爾排序思路:     

排序演算法總結-選擇排序、插入排序、歸併排序快速排序

  前言:   感覺好久沒寫部落格了,十月份的計劃是:要開始深入攻克資料結構和演算法,耽誤好久了,這都月末了,抓緊時間又學習了一波,趕緊來分享了一下,使用的語言是C++,最開始學資料結構一定要用C,掌握紮實之後,想學演算法,用C++比較好,C封裝沒有那麼好,寫起來沒有那麼容易了。   一、準備工作