快速排序(Quick Sort)(C語言) 超詳細解析!!!

生活的本質是什么呢? 無非就是你要什么就不給你什么. 而生活的智慧是什么呢? 是給你什么就用好什么. ---馬斯克

索引

  • 一. 前言
  • 二. 快速排序的概念
  • 三. 快速排序的實現
    • 1. hoare
    • 2. 挖坑法
    • 3. 前后指針法
  • 總結


正文開始

一. 前言

接上文, 前面我們了解了插入排序, 與優化版本希爾排序, 那么本篇博客將詳細介紹另外一種排序, 快速排序.

博客主頁: 酷酷學!!!

感謝關注~

二. 快速排序的概念

快速排序是Hoare于1962年提出的一種二叉樹結構的交換排序方法,

其基本思想為:

任取待排序元素序列中的某元素作為基準值,按照該排序碼將待排序集合分割成兩子序列,左子序列中所有元素均小于基準值,右子序列中所有元素均大于基準值,然后最左右子序列重復該過程,直到所有元素都排列在相應位置上為止。

三. 快速排序的實現

將區間按照基準值劃分為左右兩半部分的常見方式有三種版本

1. hoare

在這里插入圖片描述
如圖所示, 排序的思想為

第一步:
兩個下標, 一個從后往前走, 一個從前往后走, 然后定義一個基準值key, 下表為keyi,這里要注意要保證end先走, 才能讓最后相遇的位置小于keyi的位置, 因為end先走無非就兩種情況, 第一種, end找到了比key小的值, 停下來, 最后一次begin相遇end,交換相遇結點與key結點, 第二種情況, end相遇begin, 此時end沒有找到比keyi小的值, 然后最后一次與begin相遇, 此時begin的值為上一次交換后的值, 所以比keyi小.然后相遇與keyi交換位置, 此時6這個位置就排好了

在這里插入圖片描述
第二步:

因為6這個位置排好了, 所以進行下一次的排序,以6劃分為兩個區域,然后再次遞歸調用函數, 如果最后的區間不存在或者只有一個值那么就結束函數.當left==right時此時區間只有一個值就不需要排序, 區間不存在也不需要排序.

void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int keyi = left;int begin = left,end = right;while (begin < end){while (begin < end && a[end] >= a[keyi]){end--;}while (begin < end && a[begin] <= a[keyi]){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[begin], &a[keyi]);keyi = begin;QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

我們來分析一下快速排序的時間復雜度, 目前來看, 每一層的時間復雜度為O(N), 高度為logn, 所以時間復雜度為O(NlogN), 但是這是在較好的情況下, 也就是相當于二叉樹, 左右區間差不多大, 那么如果是有序的一系列數字, 那么這個排序就很不友好, 很容易棧溢出,遞歸層次很深, 就不是logn, 那么如何進行優化呢, 就需要我們修改它的key值, 不大不小為最好的情況, 為了避免有序情況下,效率退化, 可以選擇 隨機選key法 或者 三數取中法

在這里插入圖片描述

這里我們來介紹一下三數取中法的優化.

int GetMidi(int* a, int left, int right)
{int midi = (right - left) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){return midi;}else if(a[left] < a[right]){return right;}else{return left;}}else//a[left] > a[midi]{if (a[left] < a[right]){return left;}else if (a[midi] > a[right]){return midi;}else{return right;}}
}

我們可以定義一個函數, 然后選取區間內最左邊的值, 最右邊的值, 中間的值,那個不大又不小的值作為key, 利用上述函數, 我們的代碼可以改寫為

void QuickSort(int* a, int left, int right)
{if (left >= right){return;}//三數取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int begin = left,end = right;while (begin < end){while (begin < end && a[end] >= a[keyi]){end--;}while (begin < end && a[begin] <= a[keyi]){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[begin], &a[keyi]);keyi = begin;QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

有了上述的優化, 我們的代碼效率就算在有序的情況下也能大大的提升效率, 但是還有一個問題, 我們知道, 二叉樹中遞歸調用, 最后一層遞歸調用的次數占據了總調用次數的一半, 我們可以把最后幾層的遞歸調用優化掉, 如果區間為十個數那么我們選擇其他的排序方法, 如果選堆排序, 十個數還需要建堆比較麻煩, 如果使用希爾排序, 那么就是多此一舉, 數據量不大選擇希爾排序屬實有點浪費了, 那我們就在時間復雜度為O(N^2)的排序中選擇一個相對來說效率比較高的插入排序.優化后的代碼如下:

void QuickSort(int* a, int left, int right)
{if (left >= right){return;}if ((right - left + 1) < 10){InsertSort(a + left, right - left + 1);}else{//三數取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int begin = left, end = right;while (begin < end){while (begin < end && a[end] >= a[keyi]){end--;}while (begin < end && a[begin] <= a[keyi]){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[begin], &a[keyi]);keyi = begin;QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}}

此優化在release版本下較為突出.

2. 挖坑法

前面單趟排序的方法由hoare發明之后又有另外的一種排序方法, 挖坑法, 它的思路比hoare排序的思想更容易理解

在這里插入圖片描述

//挖坑法
int PartSort3(int* a, int left, int right)
{//三數取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int begin = left, end = right;int tmp = a[keyi];while (begin < end){while (begin < end && a[end] >= a[keyi]){end--;}a[keyi] = a[end];keyi = end;while (begin < end && a[begin] <= a[end]){begin++;}a[keyi] = a[begin];keyi = begin;}a[keyi] = tmp;return keyi;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}if (right - left + 1 < 10){InsertSort(a + left, right - left + 1);}int keyi = PartSort3(a,left,right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

我們可以直接把單趟排序的算法拿出來封裝成一個函數, 如圖所示挖坑法特別好理解, 先讓keyi的值存放在一個臨時變量中, 挖一個坑,先讓對面的先走, 如果遇到比end小的就把它拿出來填入坑中, 然后begin在開始走, 遇到大的填入坑中, 這樣依次輪換,相遇時將keyi的值填入坑中.

3. 前后指針法

在這里插入圖片描述

如圖所示, 初始時, prev指針指向序列開頭, cur指針指向prev指針的后一個位置, 然后判斷cur指針指向的數據是否小于key, 則prev指針后移一位, 并于cur指針指向的內容交換, 然后cur++,cur指針指向的數據仍然小于key,步驟相同, 如果cur指針指向的數據大于key, 則繼續++,最后如果cur指針已經越界, 這時我們將prev指向的內容與key進行交換.

可以看到也就是一個不斷輪換的過程, 將大的數依次往后翻轉, 小的數往前挪動, 這種方法代碼寫起來簡潔, 當然我們也可以優化一下, 讓cur和prev相等時就沒必要進行交換

//前后指針版
int PartSort2(int* a, int left, int right)
{//三數取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && prev++ != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[keyi]);return prev;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}if (right - left + 1 < 10){InsertSort(a + left, right - left + 1);}int keyi = PartSort2(a,left,right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

以上是遞歸版本的實現, 那么我們知道, 遞歸層次太深會容易棧溢出, 那么能不能采用非遞歸的實現呢, 答案肯定是可以的, 我們可以使用棧這個數據結果, 然后將區間入棧中, 棧不為空循環對區間進行排序, 一般linux系統下函數棧幀的空間的大小為8M, 而堆空間大小為2G, 所以這個空間是非常大的, 而且棧也是動態開辟的空間, 在堆區申請, 所以幾乎不需要考慮空間不夠, 我們可以將區間壓棧, 先壓入右區間, 在壓入左區間, 然后排序的時候先排完左區間再排右區間, 這是一種深度優先算法(DFS), 當然也可以創建隊列, 那么走的就是一層一層的排序, 每一層的左右區間都排序, 是一種廣度優先算法(BFS).
代碼實現:

這里可以創建一個結構體變量存放區間, 也可以直接壓入兩次, 分別把左值和右值壓入

//非遞歸版
#include"stack.h"
void QuickSortNonR(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st)){int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);int keyi = PartSort1(a, begin, end);//[begin,keyi-1] keyi [keyi+1,end]//先壓入右區間if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi + 1);}if (begin < keyi - 1){STPush(&st, keyi - 1);STPush(&st, begin);}}Destroy(&st);
}

總結

快速排序是一種常用的排序算法,其時間復雜度為O(nlogn)。它的基本思想是通過一趟排序將待排序序列分割成獨立的兩部分,其中一部分的所有元素都比另一部分的所有元素小,然后再對這兩部分分別進行排序,最終得到一個有序序列。


期待支持, 感謝關注~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/23339.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/23339.shtml
英文地址,請注明出處:http://en.pswp.cn/web/23339.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ROS IMU慣性測量單元消息包

ROS IMU慣性測量單元消息包 IMU工作原理與作用 IMU&#xff08;Inertial Measurement Unit&#xff0c;慣性測量單元&#xff09;是一種重要的傳感器&#xff0c;用于測量和報告一個物體的特定物理量&#xff0c;包括加速度、角速度和&#xff08;在某些情況下&#xff09;磁…

100道面試必會算法-31-字母異位詞分組

100道面試必會算法-31-字母異位詞分組 給你一個字符串數組&#xff0c;請你將 字母異位詞 組合在一起。可以按任意順序返回結果列表。 字母異位詞 是由重新排列源單詞的所有字母得到的一個新單詞。 示例 1: 輸入: strs ["eat", "tea", "tan"…

HQL面試題練習 —— 向用戶推薦好友喜歡的音樂

目錄 1 題目2 建表語句3 題解 題目來源&#xff1a;騰訊。 1 題目 現有三張表分別為&#xff1a; 用戶關注表 t_follow(user_id,follower_id)記錄用戶ID及其關注的人ID&#xff0c;請給用戶1 推薦他關注的用戶喜歡的音樂名稱 ------------------------ | user_id | follower…

六月可以閉眼入的寵物空氣凈化器:希喂、安德邁、霍尼韋爾真實PK

俗話說得好&#xff0c;貓咪一年到頭都在掉毛&#xff0c;仿佛它們是四季常在的"蒲公英"&#xff0c;隨時隨地都在播撒毛發。貓毛不僅遍布它們自己的身體&#xff0c;還可能飄到你的床鋪、沙發、衣物上……面對這樣的狀況&#xff0c;既要應對無處不在的貓毛&#xf…

基于卷積神經網絡(CNN)的垃圾分類模型研究

摘要&#xff1a; 隨著城市化進程的加快&#xff0c;垃圾問題日益嚴重。傳統的垃圾分類方法存在效率低下、準確率不高等問題。本文提出了一種基于卷積神經網絡&#xff08;CNN&#xff09;的垃圾分類模型&#xff0c;該模型能夠自動識別并分類不同類型的垃圾。實驗表明&#xf…

Kruskal算法求最小生成樹

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #define MAX 100 #define NO INT_MAX//NO表示沒有邊&#xff0c;相當于INFtypedef struct Graph {int arcnum;int vexnum;char vextex[MAX][20];int martrix[MAX][MA…

什么無線領夾麥克風音質最好?領夾麥克風品牌排行榜前十名推薦

?在當今的數字化浪潮中&#xff0c;個人聲音的傳播和記錄變得尤為重要。無論是會議中心、教室講臺還是戶外探險&#xff0c;無線領夾麥克風以其卓越的便攜性和連接穩定性&#xff0c;成為了人們溝通和表達的首選工具。面對市場上琳瑯滿目的無線麥克風選擇&#xff0c;為了幫助…

【Python】使用 SQLObject orm 庫快速將接口數據存入數據庫

使用 SQLObject orm 庫快速將接口數據存入數據庫 文章目錄 使用 SQLObject orm 庫快速將接口數據存入數據庫背景orm python 版本都有哪些&#xff1f; SQLObject 簡單的使用 背景 因為測試需要&#xff0c;要將百萬條數據接口查詢數據存入數據庫中&#xff0c;為了減少 mysql …

Doris insert into 插入語句執行成功,且select查詢成功,返回結果不報錯,但查不到該插入數據

問題&#xff1a;Doris insert into 正常執行成功&#xff0c;select 查詢也執行成功&#xff0c;但查不到該寫入數據 原因&#xff1a;由于有其他 insert commit 事務待提交且該任務處于鎖的狀態&#xff0c;導致不斷在回滾&#xff0c;進而造成其他的insert into 語句也執行成…

26 - 超過5名學生的課(高頻 SQL 50 題基礎版)

26 - 超過5名學生的課 select class fromCourses group byclass havingcount(*)>5;

Seed-TTS語音編輯有多強?對比實測結果讓你驚嘆!

GLM-4-9B 開源系列模型 前言 就在最近&#xff0c;ByteDance的研究人員最近推出了一系列名為Seed-TTS的大規模自回歸文本轉語音(TTS)模型,能夠合成幾乎與人類語音無法區分的高質量語音。那么Seed-TTS的表現究竟有多強呢?讓我們一起來感受下Seed-TTS帶來的驚喜吧! 介紹Seed-TTS…

Java并發包中的鎖升級

在Java中&#xff0c;特別是ReentrantLock和synchronized關鍵字的實現中&#xff0c;鎖的升級通常涉及到從無鎖狀態到偏向鎖、再升級到輕量級鎖&#xff0c;最后可能升級到重量級鎖的過程。這一系列過程是為了減少鎖帶來的開銷&#xff0c;提高并發效率。 偏向鎖&#xff08;Bi…

如何用手寫代碼實現JavaScript中的reduce函數?

在JavaScript中&#xff0c;Array.prototype.reduce() 是一個內置方法&#xff0c;它遍歷數組中的每個元素&#xff0c;并將它們累積成一個單一的返回值。我們可以自己編寫一個類似的函數來模擬這個過程。 下面是一個簡單的手寫實現例子&#xff1a; function myReduce(arr, …

組裝服務器重裝linux系統【idrac集成戴爾遠程控制卡】

&#x1f341;博主簡介&#xff1a; &#x1f3c5;云計算領域優質創作者 &#x1f3c5;2022年CSDN新星計劃python賽道第一名 &#x1f3c5;2022年CSDN原力計劃優質作者 &#x1f3c5;阿里云ACE認證高級工程師 &#x1f3c5;阿里云開發者社區專…

Vue 跨平臺性能優化十法

Vue.js 開發能夠同時運行在不同平臺&#xff08;如 Web、移動平臺和桌面平臺&#xff09;的應用程序。以下是一些常見的跨平臺解決方案&#xff1a; 1. 使用 Vue.js 官方發布的框架&#xff1a; Vue.js&#xff1a;主要用于 Web 開發。 Vue Native&#xff1a;使用 Vue 語法開…

數據結構 | 超詳細講解七大排序(C語言實現,含動圖,多方法!)

目錄 ?編輯 排序的概念 常見排序算法 ?編輯 1.冒泡排序 &#x1f379;圖解 &#x1f973;代碼實現 &#x1f914;時間復雜度 2.插入排序 &#x1f379;圖解 &#x1f334;深度剖析 &#x1f34e;代碼思路 &#x1f973;代碼實現 &#x1f914;時間復雜度 3.希爾…

2024 年適用于 Linux 的 5 個微軟 Word 替代品

對于那些最近由于隱私問題或其他原因而轉向 Linux 的用戶來說&#xff0c;可能很難替換他們最喜歡的、不在 Linux 操作系統上運行的應用程序。 尋找流行程序的合適替代品可能會成為一項挑戰&#xff0c;而且并不是每個人都準備好花費大量時間來嘗試弄清楚什么可以與他們在 Win…

讀書筆記|《把自己變成稀缺資產》:我們都擁有100分的欲望,卻只有1分的耐心。

哈嘍&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 最近在讀一本書《把自己變成稀缺資產》&#xff0c;其中一章講到耐心的重要性&#xff0c;很有共鳴。 當今社會&#xff0c;生活節奏越來越快&#xff0c;我們都在急于求成的追求結果&#xff0c;對過程越來越缺乏耐…

C++核心編程友元的應用

文章目錄 1.友元1.什么是友元2.全局函數做友元2.類做友元3.成員函數做友元 1.友元 1.什么是友元 在C中&#xff0c;友元&#xff08;friend&#xff09;是一種允許一個類或函數訪問另一個類的非公有&#xff08;private 或 protected&#xff09;成員的機制。這種機制打破了類…

系統研發安全漏洞

軟件安全漏洞指的是軟件中存在的具體缺陷或疏忽&#xff0c;這些缺陷或疏忽能夠被攻擊者利用并執行一些惡意行為。這些行為包括但不限于泄露或修改敏感信息、干擾或銷毀系統、接管計算機系統或程序權限等。與大眾熟悉的軟件缺陷&#xff08;Bug&#xff09;相比&#xff0c;安全…