主頁 > 軟體設計 > 演算法導論之經典演算法:快速排序全面決議

演算法導論之經典演算法:快速排序全面決議

2021-04-29 14:06:01 軟體設計

寫在前面:博主是一只經過實戰開發歷練后投身培訓事業的“小山豬”,昵稱取自影片片《獅子王》中的“彭彭”,總是以樂觀、積極的心態對待周邊的事物,本人的技術路線從Java全堆疊工程師一路奔向大資料開發、資料挖掘領域,如今終有小成,愿將昔日所獲與大家交流一二,希望對學習路上的你有所助益,同時,博主也想通過此次嘗試打造一個完善的技術圖書館,任何與文章技術點有關的例外、錯誤、注意事項均會在末尾列出,歡迎大家通過各種方式提供素材,

  • 對于文章中出現的任何錯誤請大家批評指出,一定及時修改,
  • 有任何想要討論和學習的問題可聯系我:zhuyc@vip.163.com,
  • 發布文章的風格因專欄而異,均自成體系,不足之處請大家指正,

演算法導論之經典演算法:快速排序全面決議

本文關鍵字:演算法導論、經典演算法、交換排序、快速排序、演算法實踐

文章目錄

  • 演算法導論之經典演算法:快速排序全面決議
    • 一、什么是演算法
      • 1. 演算法的定義
      • 2. 補充的概念
    • 二、交換排序
      • 1. 交換排序介紹
      • 2. 快速排序
      • 3. 偽代碼
    • 三、演算法實踐
      • 1. 演算法實作
      • 2. 時間復雜度
      • 3. 空間復雜度

一、什么是演算法

本專欄為《手撕演算法》欄目的子專欄:《演算法導論》,會講述一些經典演算法,并進行分析,在此之前我們要先了解什么是演算法,能夠解決什么樣的問題,

1. 演算法的定義

以下為經典教材《Introduction.to.Algorithms》開篇中的內容,

Informally, an algorithm is any well-de?ned computational procedure that takes some value, or set of values, as input and produces some value, or set of values, as output. An algorithm is thus a sequence of computational steps that transform the input into the output.

可以看到,任何被明確定義的計算程序都可以稱作演算法,它將某個值或一組值作為輸入,并產生某個值或一組值作為輸出,所以演算法可以被稱作將輸入轉為輸出的一系列的計算步驟,
這樣的概括是比較標準和抽象的,其實說白了就是步驟明確的解決問題的方法,由于是在計算機中執行,所以通常先用偽代碼來表示,清晰的表達出思路和步驟,這樣在真正執行的時候,就可以使用不同的語言來實作出相同的效果,
概括的說,演算法就是解決問題的工具,在描述一個演算法時,我們關注的是輸入輸出,也就是說只要把原始資料和結果資料描述清楚了,那么演算法所做的事情也就清楚了,我們在設計一個演算法時也是需要先明確我們有什么和我們要什么,這一點相信大家在后面的文章中會慢慢體會到,

2. 補充的概念

  • 資料結構

演算法經常會和資料結構一起出現,這是因為對于同一個問題(如:排序),使用不同的資料結構來存盤資料,對應的演算法可能千差萬別,所以在整個學習程序中,也會涉及到各種資料結構的使用,
常見的資料結構包括:陣列、堆、堆疊、佇列、鏈表、樹等等,

  • 演算法的效率

在一個演算法設計完成后,還需要對演算法的執行情況做一個評估,一個好的演算法,可以大幅度的節省運行的資源消耗和時間,在進行評估時不需要太具體,畢竟資料量是不確定的,通常是以資料量為基準來確定一個量級,通常會使用到時間復雜度空間復雜度這兩個概念,

  • 時間復雜度

通常把演算法中的基本操作重復執行的頻度稱為演算法的時間復雜度,演算法中的基本操作一般是指演算法中最深層回圈內的陳述句(賦值、判斷、四則運算等基礎操作),我們可以把時間頻度記為T(n),它與演算法中陳述句的執行次數成正比,其中的n被稱為問題的規模,大多數情況下為輸入的資料量,
對于每一段代碼,都可以轉化為常數或與n相關的函式運算式,記做f(n),如果我們把每一段代碼的花費的時間加起來就能夠得到一個刻畫時間復雜度的運算式,在合并后保留量級最大的部分即可確定時間復雜度,記做O(f(n)),其中的O就是代表數量級
常見的時間復雜度有(由低到高):O(1)、O( log ? 2 n \log _{2} n log2?n)、O(n)、O( n log ? 2 n n\log _{2} n nlog2?n)、O( n 2 n^{2} n2)、O( n 3 n^{3} n3)、O( 2 n 2^{n} 2n)、O(n!),

  • 空間復雜度

程式從開始執行到結束所需要的記憶體容量,也就是整個程序中最大需要占用多少的空間,為了評估演算法本身,輸入資料所占用的空間不會考慮,通常更關注演算法運行時需要額外定義多少臨時變數或多少存盤結構,如:如果需要借助一個臨時變數來進行兩個元素的交換,則空間復雜度為O(1),

  • 偽代碼約定

偽代碼是用來描述演算法執行的步驟,不會具體到某一種語言,為了表達清晰和標準化,會有一些約定的含義:
縮進:表示塊結構,如回圈結構或選擇結構,使用縮進來表示這一部分都在該結構中,
回圈計數器:對于回圈結構,在回圈終止時,計數器的值應該為第一個超出界限的值,
to:表示回圈計數器的值增加,
downto:表示回圈計數器的值減少,
by:回圈計數器的值默認變化量為1,當大于1時可以使用by,
變數默認是區域定義的
陣列元素訪問:通過"陣列名[下標]"形式,在偽代碼中,下標從1開始("A[1]“代表陣列A的第一個元素),
子陣列:使用”…"來代表陣列中的一個范圍,如"A[i…j]"代表從第i個到第j個元素組成的子陣列,
物件與屬性:復合的資料會被組織成物件,如鏈表包含后繼(next)和存盤的資料(data),使用“物件名 + 點 + 屬性名”,
特殊值NIL:表示指標不指向任何物件,如二叉樹節點無子孩子可認為左右子節點資訊為NIL,
return:回傳到呼叫程序的呼叫點,在偽代碼中允許回傳多個值,
and和or:與運算和或運算默認短路,即如果已經能夠確定運算式結果時,其他條件不會去判斷或執行,

二、交換排序

1. 交換排序介紹

交換排序的核心思想是,每次將元素兩兩比較,如果不滿足正確的相對序列(如:較小的應該在前)則進行交換,不斷的根據某個規律進行比較和交換,直到全部滿足為止,此時也就得到了一個有序的序列,

  • 冒泡排序

也稱氣泡排序,是經典的交換排序方法,整個程序就是在無序區中對相鄰元素進行兩兩比較,將不滿足相對順序的一對兒元素進行交換,再進行下一對兒元素的比較,每一趟冒泡后,就會送一個最小的元素達到最上端,在無序區中重復這個程序,直到所有的元素有序,
文章傳送門:演算法導論之經典演算法:冒泡排序全面決議

  • 快速排序

快速排序是冒泡排序的改進演算法,主要思想是在待排序列中取一個元素(通常為第一個)作為參照,將序列分為兩個子序列,比參照值小的元素比參照值大的元素各自組成一個子序列,每趟排序會使參照元素歸位,并得到兩個子序列,在子序列中繼續執行該步驟,直到子序列的長度為0或1

2. 快速排序

  • 輸入

n個數的序列,通常直接存放在陣列中,可能是任何順序,

  • 輸出

輸入序列的一個新排列,滿足從小到大的順序(默認討論升序,簡單的修改就可以實作降序排列),

  • 演算法說明

快速排序是一種劃分交換排序方法,采用了分治策略,首先將原問題劃分成若干個規模更小、與原問題相似的子問題,然后用遞回方法解決這些子問題,最后再將它們組合成原問題的解,
第一趟排好序列中的一個數,放在它應該在的位置上,同時得到兩個子序列,左側都是比它小的數右側都是比它大的數(升序排序時),接下來在每個子序列中不斷重復歸位一個元素得到子序列這個程序,直到子序列的長度為1或0,此時整體的序列已經有序,

  • 演算法流程

以下為第一趟排序的程序,選定一個待排元素x后,不斷縮小無限制區域,使得得到的兩個子序列(兩個區域)滿足其中一個都比x小,另一個都比x大,最后將待排元素插入到兩個區間中間即完成排序(暫不考慮存在相同元素),

以下圖片取材自《演算法導論》,完成第一趟排序后,不斷的在得到的子序列中重復該步驟:

  1. (a)將待排元素選定為序列的最后一個元素:4,目標是在左側的無序區中劃分出兩個子序列,
  2. p為序列區間左端點,r為序列區間右端點,
  3. i的初始值在區間端點之前,用于劃分出較小元素所在區間,
  4. j為進行掃描時使用的變數,每次取到元素進行判斷,然后進行區間的調整,
  5. pi之間的部分構成淺色區域(較小數區間),ij之間的部分構成深色區域(較大數區間),
  6. (b)2 < 4,應歸入較小數區間,i進行后移(此時i與j指向同一元素),并與j指向的元素交換,淺色區域增加
  7. (c)8 > 4,應歸入較大數區間,j正常后移,深色區域增加
  8. (d)7 > 4,應歸入較大數區間,j正常后移,深色區域增加
  9. (e)1 < 4,應歸入較小數區間,i進行后移,并與j指向的元素交換,淺色區域增加
  10. (f)3 < 4,應歸入較小數區間,i進行后移,并與j指向的元素交換,淺色區域增加
  11. (g)5 > 4,應歸入較大數區間,j正常后移,深色區域增加
  12. (h)6 > 4,應歸入較大數區間,j正常后移,深色區域增加
  13. (i)此時j已達到邊界,最后只需要將**深色區域的首個元素(8)待排元素(4)**交換,

:有關于子序列的劃分方式有多種實作方法,如還可以采用從兩端不斷同時向中間靠近的方式,大家可以自行實作,

3. 偽代碼

在快速排序中使用到了遞回的操作,在撰寫偽代碼時可以使用FUNCTION來宣告定義一個函式名稱,以進行呼叫:

FUNCTION PARTITION(A,p,r)
x =  A[r]
i = p - 1
for j = p to r - 1
    if a[j] < x
        i = i + 1
        exchange A[i] with A[j]
exchange A[i + 1] with A[r]
return i + 1

PARTITION函式的作用是進行第一趟排序,排好一個元素,并得到兩個子序列,回傳值就是接下來劃分子序列的參照,

FUNCTION QUICKSORT(A,p,r)
if p < r
    q = PARTITION(A,p,r)
    QUICKSORT(A,p,q-1)
    QUICKSORT(A,q+1,r)

QUICKSORT函式中進行了遞回的操作,將原問題分解為一系列的子問題,每次都在序列上進行劃分呼叫的操作,每次不斷的改變區間的長度,直到不需要再劃分為止,

三、演算法實踐

1. 演算法實作

  • 輸入資料(input):2,8,7,1,3,5,6,4
  • Java源代碼

演算法中用到了遞回呼叫,對于遞回還不太理解的小伙伴可以進傳送門:Java方法的嵌套與遞回呼叫,

public class QuickSort {

    public static void main(String[] args) {
        // input data
        int[] a = {2,8,7,1,3,5,6,4};
        // 呼叫快速排序,傳入初始的左右端點
        quickSort(a,0,a.length - 1);
        // 查看排序結果
        for (int data : a){
            System.out.print(data + "\t");
        }
    }

    private static int partition(int[] a,int p,int r){
        // 宣告待排元素
        int x = a[r];
        // 初始化較小數區間端點
        int i = p - 1;
        // 回圈結束后,區間已經劃定完畢
        for(int j = p;j < r;j++){
            // 將較小的數向前扔
            if(a[j] < x){
                i++;
                // 交換兩個元素
                int tmp = a[i];
                a[i] = a[j];
                a[j] = tmp;
            }
        }
        // 交換待排元素到指定位置
        int tmp = a[i + 1];
        a[i + 1] = a[r];
        a[r] = tmp;
        return i + 1;
    }

    private static void quickSort(int[] a,int p,int r){
        // 重要:遞回的出口(終止條件)為區間長度小于1
        if(p < r){
            // 劃分后得到已排好元素的位置
            int q = partition(a,p,r);
            // 根據位置得到較小數列區間
            quickSort(a,p,q-1);
            // 根據位置得到較大數序列區間
            quickSort(a,q + 1,r);
        }
    }

}

推薦大家使用debug的方式來具體查看一下演算法的整個執行程序,

  • 執行效果

2. 時間復雜度

對于快速排序有個小小不確定的因素就是每次待排元素的選擇,其實并不是固定的,但是由于序列也是隨機的,所以我們可以忽略這個小問題,
對于快速排序來說,由于無論如何劃分,比較的次數都是固定的,不會超過O(n),那么劃分的次數就尤為重要了,這也是重點分析的方面,

  • 最壞的情況

如果初始序列已經有序時,可以試想一下,每次排好一個元素,并且都要挨個比較一次,重點是劃分得到的子序列只有一個,如果按照上文,每次選取區間右端點作為待排元素,那么每次只會得到一個較小子序列,在這個序列中再次重復劃分的步驟,同樣只能得到一個較小子序列,這種情況其實就和冒泡排序的程序很相似了,都是每次只排好一個元素,對其他元素都要比較一遍,此時的時間復雜度為O( n 2 n^{2} n2)

  • 最好的情況

由于演算法是不斷在子序列上遞回執行的,如果說每次待排元素都恰好處在中間位置,將原有序列分成兩個等長的子序列,每次劃分都是這樣的情況,那么總共的劃分次數就可以用O( l o g 2 n log _{2} n log2?n)表示,這樣時間復雜度可以在O( n log ? 2 n n\log _{2} n nlog2?n)

  • 平均情況

對于快速排序來說,是基于關鍵字比較的內部排序演算法中速度最快的,平均性能可以達到O( n log ? 2 n n\log _{2} n nlog2?n)

3. 空間復雜度

由于使用到了遞回操作,所以在執行程序中需要在堆疊中保存相關資訊,需要的空間就和遞回次數直接相關,可以知道,在平均情況下,需要O( l o g 2 n log _{2} n log2?n),在最壞的情況下,不會超過O(n)

掃描下方二維碼,加入官方粉絲微信群,可以與我直接交流,還有更多福利哦~

在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/281304.html

標籤:其他

上一篇:Vue全家桶:Vuex

下一篇:Java EE資料持久化框架 • 【第5章 MyBatis代碼生成器和快取配置】

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more