【數據結構】八大排序之快速排序:分而治之的藝術

在這里插入圖片描述

文章目錄

  • 快速排序
    • 1.hoare版本
      • 算法優化
        • 三數取中法
        • 小區間優化
      • 完整代碼如下
      • 算法分析
      • 時間復雜度
      • 空間復雜度
    • 2.前后指針法
      • 排序過程
    • 3.非遞歸(棧模擬)
      • 實現思路
  • 總結

快速排序

快速排序是Hoare于1962年提出的一種二叉樹結構的交換排序方法,其基本思想為:任取待排序元素序列中 的某元素作為基準值,按照該排序碼將待排序集合分割成兩子序列,左子序列中所有元素均小于基準值,右 子序列中所有元素均大于基準值,然后最左右子序列重復該過程,直到所有元素都排列在相應位置上為止。

1.hoare版本

在這里插入圖片描述

簡單來說就是選某個元素為基準值(這里先默認第一個),然后把比基準值小的都放到基準值的左邊,比基準值大的都放到基準值的右邊

以下圖為例

在這里插入圖片描述

先以6為基準

然后左邊找大,右邊找小,之后互換

進行這么一趟后,6左邊就都比它小,右邊都比它大

然后以6為分界線,再分成兩個區間,類似于二叉樹

在這里插入圖片描述

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[keyi], &a[end]);// 遞歸排序子數組QuickSort(a, left, end - 1);QuickSort(a, end + 1, right);}
}

算法優化

雖然基本快速排序已經很快,但在某些情況下性能會下降,特別是當輸入數組已經有序或接近有序時。以下是兩種常見的優化策略:

三數取中法

當數組已經有序或接近有序時,選擇第一個元素作為基準會導致分區極度不平衡,從而使算法退化為 O(n2) 的時間復雜度。三數取中法通過選擇左端、中間和右端三個元素的中值作為基準,可以有效避免這種最壞情況。

三數取中代碼如下

int GetMidi(int* a, int left, int right)//左邊作key 右邊先走
{int midi = (left + right) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){//說明midi是中間值return midi;}//走到這說明midi最大,所以剩下兩個數中大的就是中間值else if(a[left]>a[right]){return left;}else//剩下最后一種情況 不必多說{return right;}}else//走到這說明 a[left]>a[midi]{if (a[midi] > a[right]){return midi;}//到這說明midi最小 所以找剩下兩個小的else if (a[left] < a[right]){return left;}else{return right;}}
小區間優化

區間比較小時,遞歸代價比較大,所以要進行小區間優化

對于遞歸來說

最后一層遞歸次數占總體的50%,倒數第二層25% ,所以進行小區間優化可以減少遞歸次數

在這里插入圖片描述

	//區間比較小時。進行小區間優化,不再遞歸分割排序。減少遞歸次數if ((right - left + 1) < 10){InsertSort(a + left, right - left + 1);}

完整代碼如下

#include<stdio.h>void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}
void InsertSort(int* a, int n) 
{for (int i = 1; i < n; i++) {int key = a[i];int j = i - 1;while (j >= 0 && a[j] > key) {a[j + 1] = a[j];j--;}a[j + 1] = key;}
}
//面試手撕快排 不用寫小區間優化和三數取中 后續講一下思路即可 不然會覺得你寫的慢
//避免有序情況下效率降低
//1.隨機數
//2。三數取中
int GetMidi(int* a, int left, int right)//左邊作key 右邊先走
{int midi = (left + right) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){//說明midi是中間值return midi;}//走到這說明midi最大,所以剩下兩個數中大的就是中間值else if(a[left]>a[right]){return left;}else//剩下最后一種情況 不必多說{return right;}}else//走到這說明 a[left]>a[midi]{if (a[midi] > a[right]){return midi;}//到這說明midi最小 所以找剩下兩個小的else if (a[left] < a[right]){return left;}else{return right;}}
}
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 + 1;  // 從基準下一個開始int end = right;while (begin <= end) {// 從右向左找小于基準的值while (begin <= end && a[end] >= a[keyi])end--;// 從左向右找大于基準的值while (begin <= end && a[begin] <= a[keyi])begin++;// 交換找到的不符合元素if (begin < end)Swap(&a[begin], &a[end]);}// 將基準放到正確位置Swap(&a[keyi], &a[end]);// 遞歸排序子數組QuickSort(a, left, end - 1);QuickSort(a, end + 1, right);}
}int main() {int arr[3] = { 2, 1, 3 };QuickSort(arr, 0, 2);for (int i = 0; i < 3; i++) {printf("%d ", arr[i]);  // 輸出:1 2 3}return 0;
}

為什么要右邊先走呢

分析如下圖

在這里插入圖片描述

算法分析

時間復雜度

O(n log n):一共有logn層遞歸 每一層都是所有子數組加起來都是n

空間復雜度

  • 最佳情況:O(log n) - 遞歸調用的棧空間
  • 最壞情況:O(n) - 當分區極度不平衡時

2.前后指針法

前后指針法是快速排序的另一種常見實現方式,通過兩個指針prev和cur來遍歷數組,將小于基準值的元素移動到前面。
在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

排序過程

  • 定義兩個指針prev和cur,prev指向left,cur指向prev+1。
  • cur指針向右移動,如果a[cur]小于基準值,則prev++并交換a[prev]和a[cur]。
  • 最后將基準值交換到prev位置。

排序一趟的代碼如下

int prev = left;
int cur = prev+1;
while(cur<=right)
{if(a[cur]<a[keyi]&&++prev!=cur)//cur比基準小就交換Swap(&a[prev],&a[cur])//cur不管交換還是不交換,都要繼續走cur++
}
Swap(&a[prev],&a[keyi]);
return 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[prev], &a[cur]);cur++;}Swap(&a[prev], &a[keyi]);return prev;
}
void QuickSort(int* a, int left, int right)
{if (left >= right)return;int keyi = PartSort1(a, left, right);// [left, keyi-1] keyi [keyi+1, right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

3.非遞歸(棧模擬)

遞歸實現雖然簡潔,但在處理大規模數據時可能導致棧溢出。非遞歸實現通過顯式棧來模擬遞歸過程,避免遞歸帶來的棧開銷。

實現思路

  1. 初始化一個棧,將初始左右下標入棧(先右后左)。
  2. 循環取出棧頂的左右下標,進行分區操作。
  3. 將分區后的左右子數組下標入棧,繼續處理直到棧為空。

在這里插入圖片描述

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 = PartSort2(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);}}STDestroy(&st);
}

總結

快速排序是一種高效且應用廣泛的排序算法,通過分治策略將問題分解為子問題處理。其性能取決于基準值的選擇是否合理,因此在實際應用中常采用三數取中等優化策略來避免最壞情況的發生。

無論是遞歸還是非遞歸實現,快速排序都能在平均情況下達到O(n log n)的時間復雜度,使其成為處理大規模數據的理想選擇之一

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

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

相關文章

在ROS中獲取并發布UBS式傳感器的溫濕度

哈嘍大家好&#xff0c;我是鋼板獸&#xff01; 今天更新一篇和ROS相關的文章&#xff0c;有個項目需求是在ROS中獲取并發布UBS式傳感器的溫濕度&#xff0c;我使用的溫濕度傳感器簡介如下&#xff1a;DL11- MC-S1 溫濕度傳感器通過USB 接口采用標準MODBUS RTU 協議通信&#x…

【圖論】 Graph.jl 操作匯總

文章目錄圖論的集合類操作Base.getindexBase.intersectBase.joinBase.reverseBase.reverse!Base.sizeBase.sumBase.sumBase.union圖生成與轉換Graphs.cartesian_productGraphs.complementGraphs.compute_shiftsGraphs.crosspathGraphs.differenceGraphs.egonetGraphs.induced_s…

【鏈表 - LeetCode】146. LRU 緩存

146. LRU 緩存 題解&#xff1a; class LRUCache {list<pair<int,int>>v;unordered_map<int,list<pair<int,int>>::iterator>idx;int capacity; public:LRUCache(int capacity):capacity(capacity){}int get(int key) {if(idx.count(key) 0) …

Elasticsearch vs Solr vs OpenSearch:搜索引擎方案對比與索引設計最佳實踐

Elasticsearch vs Solr vs OpenSearch&#xff1a;搜索引擎方案對比與索引設計最佳實踐 隨著大數據和實時分析需求的爆發&#xff0c;搜索引擎已成為許多業務系統中的核心組件。本篇文章將從“技術方案對比分析型”角度切入&#xff0c;重點比較三大主流搜索引擎&#xff1a;El…

光頡科技)Viking)的CS25FTFR009 1225 0.009R/9mR 3W電阻介紹-華年商城

“**華年商城”**小編為您介紹&#xff1a;光頡科技&#xff08;Viking&#xff09;的CS25FTFR009 1225 0.009R/9mR 3W電阻 光頡CS25FTFR009合金電阻&#xff1a;0.009Ω/9mΩ 3W 1%精密采樣電阻 光頡科技&#xff08;Viking&#xff09;的CS25FTFR009是一款高性能的電流檢測電…

港科大開放世界長時域具身導航!LOVON:足式機器人開放詞匯目標導航

作者&#xff1a;Daojie Peng1^{1}1, Jiahang Cao1,2^{1,2}1,2, Qiang Zhang1,2^{1,2}1,2, Jun Ma1,3^{1,3}1,3單位&#xff1a;1^{1}1香港科技大學&#xff08;廣州&#xff09;&#xff0c;2^{2}2北京人形機器人創新中心&#xff0c;3^{3}3香港科技大學論文標題&#xff1a;L…

【前端教程】JavaScript 數組對象遍歷與數據展示實戰

在前端開發中&#xff0c;處理數組和對象是日常工作的基礎。無論是篇文章將通過一個具體案例&#xff0c;詳細講解如何使用JavaScript遍歷包含對象的數組&#xff0c;并將數據以清晰的格式展示在頁面上。我們會從基礎語法開始&#xff0c;逐步優化代碼&#xff0c;最終實現一個…

無重復字符的最長子串,leetCode熱題100,C++實現

題目來源&#xff1a;leetCode 3. 無重復字符的最長子串 - 力扣&#xff08;LeetCode&#xff09; 給定一個字符串 s &#xff0c;請你找出其中不含有重復字符的 最長 子串 的長度。 解法 class Solution { public:int lengthOfLongestSubstring(string s) {unordered_set<…

卷積神經網絡中1×1卷積的作用

part I &#xff1a;來源part II &#xff1a;應用part III &#xff1a;作用&#xff08;降維、升維、跨通道交互、增加非線性&#xff09;part IV &#xff1a;從fully-connected layers的角度理解一、來源&#xff1a;[1312.4400] Network In Network &#xff08;如果11…

VMware設置Ubuntu虛擬機橋接模式完整教程

VMware 設置 Ubuntu 虛擬機橋接模式完整教程 下面是一個詳細的、避免出錯的 VMware Ubuntu 橋接模式設置教程&#xff0c;包含常見問題的解決方案。 準備工作 確保宿主機&#xff08;Windows 11&#xff09;已連接到網絡&#xff08;有線或無線&#xff09;確認您有管理員權限關…

淺析NVMe協議:DIF

文章目錄概述DIF數據格式盤片支持DIFFormatPILPIMSETLBAF協議命令DIF支持PRACTPRACT0PRACT1PRCHK相關參考概述 NVMe協議將DIF信息作為元數據的一部分進行攜帶。 DIF數據格式 DIF的PI由多個字段組成&#xff0c;包括&#xff1a; Guard字段&#xff1a;基于邏輯塊數據計算的C…

【觀成科技】蔓靈花User下載者加密通信分析

概述2025年5月7日&#xff0c;蔓靈花&#xff08;BITTER&#xff09;組織針對巴基斯坦電信公司工作人員發起釣魚郵件攻擊&#xff0c;投遞偽裝為安全簡報的惡意郵件&#xff0c;附件為IQY類型的Web查詢文件。該文件在用戶執行后通過HTTP協議獲取遠程CMD指令并執行&#xff0c;進…

Redis 保證數據不丟失

Redis 保證數據不丟失&#xff08;或最大限度減少丟失&#xff09;的核心是通過 持久化機制 結合 合理的配置策略 實現的。具體方案如下&#xff1a;一、核心&#xff1a;開啟 Redis 持久化&#xff08;防止進程崩潰丟失數據&#xff09;Redis 提供兩種持久化方式&#xff0c;可…

NUMA/SNC 4種組合下Stream+MLC性能對決:雙路服務器BIOS調優全攻略

關于調整 BIOS NUMA 與 SNC 選項的 Stream / MLC 性能測試總結一、測試背景與目的在現代多路 Intel Xeon 服務器上&#xff0c;NUMA&#xff08;Non-Uniform Memory Access&#xff09;與 SNC&#xff08;Sub-NUMA Clustering&#xff09;是兩項決定內存訪問延遲與帶寬的關鍵 B…

Java-113 深入淺出 MySQL 擴容全攻略:觸發條件、遷移方案與性能優化

點一下關注吧&#xff01;&#xff01;&#xff01;非常感謝&#xff01;&#xff01;持續更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持續更新中&#xff01;&#xff08;長期更新&#xff09; AI煉丹日志-31- 千呼萬喚始出來 GPT-5 發布&#xff01;“快的…

Kafka Connect + Streams 用到極致從 CDC 到流處理的一套落地方案

關鍵目標&#xff1a; 零丟失&#xff1a;端到端 Exactly Once&#xff08;Source 端事務 Streams exactly_once_v2 Sink DLQ&#xff09;。低延遲&#xff1a;Producer 端批量壓縮 Streams 緩存 合理 poll/commit 間隔。可恢復&#xff1a;Connect/Streams 的 rebootstrap…

# `std::basic_istream`總結

std::basic_istream總結 文章目錄std::basic_istream總結概述常用類型定義全局對象核心成員函數1. 格式化輸入2. 非格式化輸入3. 流定位4. 其他功能繼承的功能來自 std::basic_ios狀態檢查狀態管理來自 std::ios_base格式化標志流打開模式特點說明例子std::basic_istream全面用…

人工智能——課程考核

課程考核包括平時測驗&#xff08;75%&#xff09;和討論&#xff08;25%&#xff09;兩個環節&#xff0c;測驗采用線上隨堂考試&#xff08;2-3次&#xff0c;具體會在本課堂發布&#xff09;重點考核&#xff1a;A*算法、極大極小過程&#xff08;α-β剪枝&#xff09;、不…

機器學習-時序預測1

最近面試過程中&#xff0c;Predict-then-Optimize是運籌優化算法工程師未來的發展方向。就像我之前寫過的運籌優化&#xff08;OR&#xff09;-在機器學習&#xff08;ML&#xff09;浪潮中何去何從&#xff1f;-CSDN博客&#xff0c;機器學習適合預測、運籌優化適合決策。我研…

vim-plugin AI插件

文章目錄一、vim 插件管理vim-plug二、如何使用和配置 vim-plug第 1 步&#xff1a;安裝 vim-plug第 2 步&#xff1a;配置你的 .vimrc / init.vim第 3 步&#xff1a;安裝插件常用 vim-plug 命令三、配置vim-aivim-aivim-deepseekvim升級四、配置 AI 插件GitHub Copilot第 1 步…