1. 程式人生 > >都2020年了,聽說你還不會歸併排序?手把手教你手寫歸併排序演算法

都2020年了,聽說你還不會歸併排序?手把手教你手寫歸併排序演算法

本文介紹了歸併排序的基本思想,遞迴方法的一般寫法,最後一步步手寫歸併排序,並對其效能進行了分析。

基本思想

歸併排序是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法的一個非常典型的應用。即先使每個子序列有序,再將已有序的子序列合併,得到完全有序的序列。這裡給出一種遞迴形式的歸併排序實現。

遞迴方法的一般寫法

遞迴方法的書寫主要有三步:

  1. 明確遞迴方法的功能邊界;
  2. 得到遞迴的遞推關係;
  3. 給定遞迴的終止條件。

遞迴方法均可按照這三步進行,切忌不要陷入遞迴實現的細節中。下面以歸併排序演算法的書寫為例,來談一下遞迴方法的具體寫法。

手寫歸併排序

首先,明確遞迴方法的功能,這裡我們定義方法的功能為,給定一個數組及左右邊界,方法完成陣列邊界內元素的排序,如下:

private static void mergeSort(int[] arr,int left,int right);

先假設我們已經有了這麼一個方法,不用管具體的實現。
接著,尋找遞推關係,什麼是遞推關係呢?就是如何由子問題的求解,來得到原問題的求解,還是舉例說明,有如下的陣列

我們將其拆分為左右兩部分,如下

遞推關係就是,假如左右兩部分都已經有序了,如何使整個陣列有序?這個問題其實就是給定了一個數組,陣列的左半部分有序,右半部分也有序,如何使整個陣列有序?
首先,定義兩個指標,分別指向左側部分起始位置和右側部分起始位置,同時建立一個輔助陣列和指向其初始位置的輔助指標

接著比較,左指標和右指標所對應的元素的大小,較小的元素填充至輔助陣列,同時其對應的指標和輔助指標均加1,如下:


依次進行,直至某左指標指向中間位置或者右指標指向陣列的末尾,此時要將將剩餘的元素填充至輔助陣列。所有的元素填充完成後,再將輔助陣列中的元素填充回原陣列即可。具體的程式碼如下:

/**
     *
     * @param arr 要合併的陣列
     * @param left 左邊界
     * @param mid 中間的分界
     * @param right 右邊界
     */
    private static void merge(int[] arr,int left,int mid,int right){
        int[] helpArr = new int[right - left + 1];//首先定義一個輔助陣列
        int lPoint = left;//左指標
        int rPoint = mid  + 1;//右指標
        int i = 0;//輔助指標
        while(lPoint <= mid && rPoint <= right){//比較並填充輔助陣列
            if(arr[lPoint] <=  arr[rPoint])
                helpArr[i++] =  arr[lPoint++];
            else
                helpArr[i++] =  arr[rPoint++];
        }
        while(lPoint <= mid){//將剩餘元素填充至輔助陣列
            helpArr[i++] =  arr[lPoint++];
        }
        while(rPoint <= right){
            helpArr[i++] =  arr[rPoint++];
        }
        for(int j = 0;j < helpArr.length;j ++){//將輔助陣列中的元素回填至原陣列
            arr[left + j] = helpArr[j];
        }
    }

最後,確定終止條件,一般是陣列為空或者陣列中只有一個元素,返回即可。
現在我們可以寫出整個歸併排序的程式碼了,如下:

private static void mergeSort(int[] arr,int left,int right){
        if(arr == null || right == left)//終止條件
            return ;
        int mid = left + (right - left) / 2;//確定分割的邊界
        mergeSort(arr,left,mid);//對左半部分呼叫遞迴方法,使其有序
        mergeSort(arr,mid + 1,right);//對右半部分呼叫遞迴方法,使其有序
        merge(arr,left,mid,right);//合併左右兩部分,使整個陣列有序
    }

為了保證形式的統一,再對函式進行一下封裝,如下,這就是我們的歸併排序了。

    /**
     * 歸併排序演算法
     * @param arr
     */
    public static void mergeSort(int[] arr){
        mergeSort(arr,0,arr.length - 1);//呼叫寫好的遞迴版歸併排序方法
    }

至此,我們便完成了歸併排序演算法的程式碼實現。

效能分析

在分析歸併排序演算法效能之前,先介紹幾個基礎的概念。
時間複雜度:一個演算法執行所消耗的時間;
空間複雜度:執行完一個演算法所需的記憶體大小;
原地排序:在排序過程中不申請多餘的儲存空間,只利用原來儲存待排資料的儲存空間進行比較和交換的資料排序。
非原地排序:需要利用額外的陣列來輔助排序。
穩定排序:如果 a 原本在 b 的前面,且 a == b,排序之後 a 仍然在 b 的前面,則為穩定排序。
非穩定排序:如果 a 原本在 b 的前面,且 a == b,排序之後 a 可能不在 b 的前面,則為非穩定排序。
下面我們分析下歸併排序演算法的效能。
首先是時間複雜度。歸併排序演算法在排序時首先將問題進行分解,然後解決子問題,再合併,所以總時間=分解時間+解決子問題時間+合併時間。分解時間就是把一個數組分解為左右兩部分,時間為一常數,即O(1);解決子問題時間是兩個遞迴方法,把一個規模為n的問題分成兩個規模分別為n/2的子問題,時間為2T(n/2);合併時間複雜度為O(n)。所以總時間T(n)=2T(n/2)+O(n)。這個遞迴問題的時間複雜度可以用下面的公式來計算

這個公式可針對形如:T(n) = aT(n/b) + f(n)的遞迴方程進行時間複雜度求解。帶入可知,歸併排序的時間複雜度為O(nlogn)。此外在最壞、最佳、平均情況下歸併排序時間複雜度均為O(nlogn)。
空間複雜度分析:在排序過程中使用了一個與原陣列等長的輔助陣列,估空間複雜度為O(n)。
穩定性分析:由排序過程可以知道,歸併排序是一種穩定排序。
是否原地排序:排序過程中用到了輔助陣列,所以是非原地排序。
本文原始碼已同步至github,地址:https://github.com/zhanglianchao/AllForJava/tree/master/src/algorithm/sort

覺得文章有用的話,點贊+關注唄,好讓更多的人看到這篇文章,也激勵博主寫出更多的好文章。
更多關於演算法、資料結構和計算機基礎知識的內容,歡迎掃碼關注我的原創公眾號「超悅程式設計」。

相關推薦

2020聽說歸併排序手把手歸併排序演算法

本文介紹了歸併排序的基本思想,遞迴方法的一般寫法,最後一步步手寫歸併排序,並對其效能進行了分析。 基本思想 歸併排序是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法的一個非常典型的應用。即先使每個子序列有序,再將已有序的子序列合併,得到完全有序的序列。這裡給出一種遞迴形式的歸併排序實現。 遞迴方

2020簡潔的Java程式碼!

# 都2020年了,你還不會寫簡潔的Java程式碼! ## 使用Google Guava依賴 ```xml com.google.guava guava 29.0-jre ``` ## 建立一個list集合並賦值 最原始的辦法: ```java List stringList =

9102Docker?10分鐘帶從入門操作到實戰上手

Docker簡述 Docker是一種OS虛擬化技術,是一個開源的應用容器引擎。它可以讓開發者將應用打包到一個可移植的容器中,並且該容器可以執行在幾乎所有linux系統中(Windows10目前也原生支援,Win10前需要內建虛擬機器),正所謂“一次打包,到處執行”。 Docker容器的執行是完全的沙箱機制,相

9102在做“資料搬運工”嗎?

幾年前,英國某企業創始人Alex寫過一篇部落格《這才是我所謂的黑客》。故事主人公幾乎從不自己幹活,任何需要佔用他90秒以上的工作,

0202知道javascript有幾種繼承方式?

前言     當面試官問你:你瞭解js哪些繼承方式?es6的class繼承是如何實現的?你心中有很清晰的答案嗎?如果沒有的話,可以通過閱讀本文,幫助你更深刻地理解js的所有繼承方式。       js繼承總共分成5種,包括建構函式式繼承、原型鏈式繼承、組

活動 | Siri開放做“聲控”APP?

【攜程技術微分享】是由攜程技術中心推出的線上公開分享課程,每月1-2期,邀請攜程技術人,面向廣大程式設計師和技術愛好者,一起探討最新的技術熱點,分享一線實戰經驗。在6月中剛剛結束的2016蘋果全球開發者大會上, 人工智慧助手Siri又一次成為焦點。Siri In

9102在給磁盤分區?

工具制作 隱藏文件 產生 question 筆記本 高訪問 碎片整理 mac 地方 經常聽見這樣的話“都2019年了,你還在給電腦分區?” 那今天就來探討一下,電腦分區的目的到底是什麽,以及這樣做究竟有沒有意義 一.方便文件管理 實際上,對

CentOS 8 發布用 nftables?

原文連結:CentOS 8 都發布了,你還不會用 nftables? 如果你沒有生活在上個世紀,並且是雲端計算或相關領域的一名搬磚者,那你應該聽說最近 CentOS 8 官方正式版已經發布了,CentOS 完全遵守 Red Hat 的再發行政策,並且致力與上游產品在功能上完全相容。CentOS 8 主要改

Redis高階專案實戰0202Redis?

導讀   大家都聽過1萬小時定律,可事實真的是這樣嗎?做了1萬小時的CRUD,不還只會CRUD嗎,這年頭不適當的更新自身下技術棧,出門和別人聊天吹牛的時候,都沒拿不出手,(⊙o⊙)…Redis沒入門的童鞋不推薦往下看,先去腦補下Redis入門(點我直達),SpringBoot整合Redis的教程

2020Java8新特性?方法引用詳解及Stream 流介紹和操作方式詳解(三)

方法引用詳解 方法引用: method reference 方法引用實際上是Lambda表示式的一種語法糖 我們可以將方法引用看作是一個「函式指標」,function pointer 方法引用共分為4類: 類名::靜態方法名 引用名(物件名)::例項方法名 類名::例項方法名 (比較不好理解,個地方呼叫的方

2020Java8新特性?(五)收集器比較器用法詳解及原始碼剖析

收集器用法詳解與多級分組和分割槽 為什麼在collectors類中定義一個靜態內部類? static class CollectorImpl<T, A, R> implements Collector<T, A, R> 設計上,本身就是一個輔助類,是一個工廠。作用是給開發者提供常見的

2020Java8新特性?(六)Stream原始碼剖析

Stream流原始碼詳解 節前小插曲 AutoCloseable介面: 通過一個例子 舉例自動關閉流的實現。 public interface BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable

2020 Java 日誌框架到底哪個效能好?——技術選型篇

大家好,之前寫(shui)了兩篇其他型別的文章,感覺大家反響不是很好,於是我乖乖的回來更新硬核技術文了。 經過本系列前兩篇文章我們瞭解到日誌框架大戰隨著 SLF4j 的一統天下而落下帷幕,但 SLF4j 僅僅是介面,實現方面, logback 與 log4j2 仍然難分高下,今天我們就來聊一聊,日誌框架

2020跨平臺開發框架現在怎樣

轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。 原文出處:https://dzone.com/articles/cross-platform-mobile-development-2020-trends-and   多年來,跨平臺移動開發已經獲得了最流

2020別再重複學習原型

# 前置 原型是 JavaScript 巧妙的設計,它非常容易理解。都 2020 年了,看完這篇希望你以後不需要再重複學習 JavaScript 原型了。如有不當之處,懇請指點一二! # 單詞 下面是相關單詞及其翻譯,牢牢記住它們就成功一半了。 - constructor 構造器 - proto &

學Python多久?為什麽做爬蟲?

希望 興趣 pass 回憶一下 經驗 imp 更多 提問 差距 學習是個很有意思的事,有的人隨便學學就能很快學會,而有的人明明很努力卻什麽都沒學會,這是為甚呢?有的人學了3個月,甚至更久卻連一個項目或者一個爬蟲都不會做,這究其原因是和你的學習效率有關。對於大家所問的Pyth

2020Java8新特性?(學習過程記錄)

Java8(1)新特性介紹及Lambda表示式 前言: 跟大娃一塊看,把原來的電腦拿出來放中間看視訊用 --- 以後會有的課程 難度 深入Java 8 難度1 併發與netty 難度3 JVM 難度4 node 難度2 spring精髓 難度1 課程中提到的知識: 前後端分離的開發,是靠node當做中間的

【Spring註解驅動開發】使用@Resource和@Inject註解?那就out!!

## 寫在前面 > 我在 **冰河技術** 微信公眾號中發表的《[【Spring註解驅動開發】使用@Autowired@Qualifier@Primary三大註解自動裝配元件,你會了嗎?](https://mp.weixin.qq.com/s?__biz=Mzg3MzE1NTIzNA==&mi

小竈時間-如果用Python虛擬環境

小竈時間 python環境 conda anaconda pip 一個鼓搗電腦多年的程序猿,帶給你的幾點編程套路和幾個靈巧工具,希望為你的編程之路添磚加瓦,加血回藍,一起拼荊斬棘,共同成長。統稱:小竈時間,作者:第8哥。 1. 為什麽用Python虛擬環境 實際工作中,我們接觸的 Pyt

看完這個 插入排序

前言 由於LeetCode上的演算法題很多涉及到一些基礎的資料結構,為了更好的理解後續更新的一些複雜題目的動畫,推出一個新系列 -----《圖解資料結構》,主要使用動畫來描述常見的資料結構和演算法。本系列包括十大排序、堆、佇列、樹、並查集、圖等等大概幾十篇。 插入排序 插入排序的程式碼實現雖然沒有氣泡排