【數據結構 · 初階】- 快速排序

目錄

一.?Hoare 版本

1. 單趟

2. 整體

3. 時間復雜度

4. 優化(搶救一下)

4.1 隨機選 key

4.2 三數取中

二. 挖坑法

格式優化

三. 前后指針(最好)

四. 小區間優化

五. 改非遞歸


快速排序是 Hoare 提出的一種基于二叉樹結構的交換排序方法。統一排升序

一.?Hoare 版本

1. 單趟

目的:選出一個關鍵字/關鍵值/基準值 key把他放到排好序后,最終在的位置
key 都喜歡在最左/右邊,其他位置不好排

例如這樣的數組:

單趟結束后要達成這樣的效果:(選擇,插入,冒泡排序的單趟沒有這種附加效果)
此時6就在排好序后,所在的位置


實現:
R 往左走,找比 key 小的
;L 往右走,找比 key 大的,相等無所謂。都找到之后,交換。直至相遇
結論:key 在左,讓 R 先走,能保證相遇位置一定比 key 小。? ? ? ? key 在右,讓 L 先走。
相遇位置既然比 key 小,就把 key 換到左邊

? ??
? ??

void QuickSort(int* a, int left, int right)
{int begin = left, end = right;int keyi = left;while (left < right){while (left < right && a[right] >= a[keyi]) // 右邊找小right--; // 且要防止本來就有序,right 飄出去while (left < right && a[left] <= a[keyi]) // 左邊找大left++;Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);keyi = left;
}

易錯:
? ? ? ? 1. 不能認為外面的 while 判斷過了,里面就不用判斷。里面的 while 會多走幾次,left 和 right 的相對位置變了,所以要再加判斷。?
? ? ? ? 2. 一定是 >= 否則可能出現死循環

2. 整體

遞歸:
上面排好單趟,被分成三段區間,[begin, keyi-1] keyi [keyi+1, end]。左右區間都無序,遞歸左區間。
選出 key 分成左右區間 …… 左區間有序,遞歸右區間。右區間有序,整體有序
遞歸返回條件:區間只剩一個值或區間不存在

遞歸的過程雖然圖上像是分出來了,其實都是在原數組上走的

和二叉樹的前序很像。單趟排是處理根(key),再處理左子樹(左區間),右子樹(右區間)

void QuickSort(int* a, int left, int right)
{// 遞歸返回條件if (left >= right) // = 是只剩一個值。> 是沒有值return;int begin = left, end = right;int keyi = left;while (left < right){while (left < right && a[right] >= a[keyi]) // 右邊找小right--; // 且要防止本來就有序,right 飄出去while (left < right && a[left] <= a[keyi]) // 左邊找大left++;Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);keyi = left;// 遞歸 [begin, keyi-1] keyi [keyi+1, end]QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

3. 時間復雜度

理想情況下:(小于)N * logN

N 個數,最終接近滿二叉樹 ==》logN

當 N = 100W,只需遞歸 20 層????????N = 10億,遞歸 30 層? ? ? ? 空間消耗不大,每層減的數也不大
最終每一層也還是 N 的量級


最壞:O( N^2 ) 搶救后忽略? ? ? ? 已經順/逆序

遞歸 N 層,建立 N 個棧幀,會棧溢出

4. 優化(搶救一下)

影響快排性能的是 keyi
keyi 越接近中間的位置,越二分,越接近滿二叉樹,深度越均勻,效率越高

不是讓左邊的值做 key ,而是讓 key 在最左邊的位置

4.1 隨機選 key

(生成位置% 區間大小)+ 左邊

void QuickSort(int* a, int left, int right)
{// 遞歸返回條件 ......int begin = left, end = right;// 優化1.隨機選 keyint randi = left + (rand() % (left - right));Swap(&a[left], &a[randi]); // 還是讓最左邊做 keyint keyi = left;while (left < right){ ...... }
}

管你有序無序,都把你變成無序

? ? ?

4.2 三數取中

有序 / 接近有序的情況下,選中間位置做 key 最好。但不一定是有序 / 接近有序
三數取中:選 左右中 3個位置,不是最小,也不是最大的數的位置? ? ? ? 兩兩比較

int GetMidNumi(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] > a[right]) // mid 不是中間,是最大的。{return left; // 剩下兩個:left 和 right 大的就是中間}else{return right;}}else // a[left] > a[mid]{if (a[mid] > a[right]){return mid;}else if (a[left] < a[right]) // mid 是最小的{return left; // 剩下兩個:left 和 right 小的就是中間}else{return right;}}
}// 快速排序
void QuickSort(int* a, int left, int right)
{// 遞歸返回條件 ......int begin = left, end = right;// 優化2.三數取中int midi = GetMidNumi(a, left, right);Swap(&a[midi], &a[left]);int keyi = left;while (left < right){ ...... }
}

? ? ?

二. 挖坑法

先將第一個數據存放在臨時變量 key 中,形成一個坑位

piti 在左,不用想,肯定讓 right 先走


right 找到比 key 小的后,把 a[right] 扔到坑里,自己變成坑。left?走。


left?找到比 key 小的后,把 a[left] 扔到坑里,自己變成坑。right 走。

重復以上過程,直到 left 和 right 相遇。相遇點一定是坑,再把 key 扔到坑里

? ?
? ?

void QuickSort2(int* a, int left, int right)
{// 遞歸返回條件if (left >= right)return;int begin = left, end = right;// 優化2.三數取中int midi = GetMidNumi(a, left, right);Swap(&a[midi], &a[left]);int piti = left;int key = a[left];while (left < right){while (left < right && a[right] >= key) // 右邊找小right--;a[piti] = a[right]; // 扔到左邊的坑piti = right; // 自己成新的坑,坑到右邊去了while (left < right && a[left] <= key) // 左邊找大left++;a[piti] = a[left]; // 扔到右邊的坑piti = left; // 自己成新的坑,坑到左邊去了}a[piti] = key;// 遞歸 [begin, piti-1] piti [piti+1, end]QuickSort2(a, begin, piti - 1);QuickSort2(a, piti + 1, end);
}

格式優化

如果寫單趟,上面的寫法就可以

快排的遞歸框架是不變的,變的是單趟

// Hoare 單趟
int PartSort1(int* a, int left, int right)
{// 三數取中int midi = GetMidNumi(a, left, right);Swap(&a[midi], &a[left]);int keyi = left;while (left < right){while (left < right && a[right] >= a[keyi]) // 右邊找小right--;while (left < right && a[left] <= a[keyi]) // 左邊找大left++;Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);keyi = left;return keyi;
}// 挖坑 單趟
int PartSort2(int* a, int left, int right)
{// 三數取中int midi = GetMidNumi(a, left, right);Swap(&a[midi], &a[left]);int piti = left;int key = a[left];while (left < right){while (left < right && a[right] >= key) // 右邊找小right--;a[piti] = a[right]; // 扔到左邊的坑piti = right; // 自己成新的坑,坑到右邊去了while (left < right && a[left] <= key) // 左邊找大left++;a[piti] = a[left]; // 扔到右邊的坑piti = left; // 自己成新的坑,坑到左邊去了}a[piti] = key;return piti;
}void QuickSort(int* a, int left, int right)
{if (left >= right)return;int keyi = PartSort2(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

三. 前后指針(最好)

1. cur 找到比 key 小的值,++prev,交換 cur 和 prev 位置的數據,++cur
2. cur?找到比 key 大的值,++cur

把比 key 大的值往右翻,比 key 小的值往左翻

? ?
? ?
? ?
? ?
? ?

1. prev 要么緊跟著 cur(prev 下一個就是 cur)
2. prev 跟 cur 中間隔著比 key 大的一段值

int PartSort3(int* a, int left, int right)
{// 三數取中int midi = GetMidNumi(a, left, right);Swap(&a[midi], &a[left]);int keyi = left;int prev = left, cur = left + 1;while (cur <= right) // [left, right],所以是 <={if (a[cur] < a[keyi]){++prev;Swap(&a[cur], &a[prev]);++cur;}else{++cur;}}Swap(&a[prev], &a[keyi]);keyi = prev;return keyi;
}void QuickSort(int* a, int left, int right)
{if (left >= right)return;int keyi = PartSort3(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}
while (cur <= right)
{if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[cur], &a[prev]);++cur;
}

四. 小區間優化

小區間直接使用直接插入排序

希爾是當數據量特別大時,為了讓大數快速往后跳才用
堆排還要建堆,很麻煩
冒泡只有教學意義,現實中幾乎沒用
選擇排序,最好最壞都是 N^2,也沒用

上面說遞歸圖看著像二叉樹

當區間特別小時,遞歸的次數會非常多。
光最后一層的遞歸數,就是總遞歸數的1/2。倒數第二次占1/4。倒數第三層占1/8

如果小區間直接使用直接插入排序,遞歸數量會少很多。現實中遞歸的不均勻,但怎么說也減少了50%的遞歸數量

void QuickSort(int* a, int left, int right)
{if (left >= right)return;// 小區間優化 - 小區間直接使用插入排序if (right - left + 1 > 10) // [left, right]左閉右閉區間,要 +1{int keyi = PartSort3(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}else{InsertSort(a + left, right - left + 1);}
}

不能寫成:InsertSort(a, right - left + 1)

正確。但如果是這樣就出錯了:

[ left , right ] 左閉右閉區間,要 +1

五. 改非遞歸

遞歸的問題:1. 效率(影響不大)? ? ? ? 2. 遞歸太深,棧溢出。不能調試

遞歸改非遞歸:
? ? ? ? 1. 直接改循環。原來正著走,遞歸逆著來(簡單)。eg:斐波那契數列。
? ? ? ? 2. 用棧輔助改循環。(難)eg:二叉樹

遞歸里,實際是用下標來 分割子區間
遞歸里參數條件變化的是什么,棧里面存的就是什么。具體情況具體分析


思路:
? ? ? ? 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 = PartSort3(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);
}

2 個 if 相當于遞歸的返回條件

本篇的分享就到這里了,感謝觀看,如果對你有幫助,別忘了點贊+收藏+關注
小編會以自己學習過程中遇到的問題為素材,持續為您推送文章

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

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

相關文章

第2周 PINN核心技術揭秘: 如何用神經網絡求解偏微分方程

1. PDEs與傳統數值方法回顧 (Review of PDEs & Traditional Numerical Methods) 1.1 什么是偏微分方程 (Partial Differential Equations, PDEs)? 偏微分方程是描述自然界和工程領域中各種物理現象(如熱量傳播、流體流動、波的振動、電磁場分布等)的基本數學語言。 1.…

Neo4j(二) - 使用Cypher操作Neo4j

文章目錄 前言一、Cypher簡介二、數據庫操作1. 創建數據庫2. 查看數據庫3. 刪除數據庫4. 切換數據庫 三、節點、關系及屬性操作1. 創建節點與關系1.1 語法1.2 示例 2. 查詢數據2.1 語法2.2 示例 3. 更新數據3.1 語法3.2 示例 4. 刪除節點與關系4.1 語法4.2 示例 5. 合并數據5.1…

RabbitMQ的Web管理頁面給我看懵了,這都什么意思啊

文章目錄 OverviewTotalsMessage RatesQueued Messages NodesChurn StatisticsPorts and ContextsExport DefinitionsImport Definitions ConnectionsChannelsExchangesQueuesAdmin他們之間的關聯 在上一篇文章中我們講到了如何在Windows中安裝Rabbitmq&#xff0c; 小白也能搞…

安全基礎與協議分析

5.1 Web安全基礎 5.1.1 Web安全基礎概覽&#xff08;一、二&#xff09; Web安全的核心目標是保護Web應用及其數據免受攻擊&#xff0c;涵蓋以下關鍵領域&#xff1a; 攻擊面&#xff1a; 前端漏洞&#xff08;XSS、CSRF&#xff09;。 后端漏洞&#xff08;SQL注入、RCE&a…

STM32項目實戰:ADC采集

STM32F103C8T6的ADC配置。PB0對應的是ADC1的通道8。在標準庫中&#xff0c;需要初始化ADC&#xff0c;設置通道&#xff0c;時鐘&#xff0c;轉換模式等。需要配置GPIOB的第0腳為模擬輸入模式&#xff0c;然后配置ADC1的通道8&#xff0c;設置轉換周期和觸發方式。 接下來是I2C…

第十四章:數據治理之數據源:數據源的數據接入、業務屬性梳理及監控

本章開始&#xff0c;將進入9大模塊的介紹。第一個模塊我們先介紹&#xff1a;數據源。數據源是整個數據中臺數據的來源&#xff0c;是一個起點。更好的管理好數據源這個起點&#xff0c;是數據治理的一個好的開始。 在【數據&#xff1a;業務生數據&#xff0c;數據生“萬物”…

【C/C++】多線程開發:wait、sleep、yield全解析

文章目錄 多線程開發&#xff1a;wait、sleep、yield全解析1 What簡要介紹詳細介紹wait() — 條件等待&#xff08;用于線程同步&#xff09;sleep() — 睡覺&#xff0c;定時掛起yield() — 自愿讓出 CPU 2 區別以及建議區別應用場景建議 3 三者協作使用示例 多線程開發&#…

阿里云CDN刷新預熱--刷新URL

文章目錄 一、全英文URL刷新預熱二、摻雜中文的URL刷新預熱2.1 對帶中文URL進行編碼2.2 預熱刷新 三、CDN刷新-核心作用與價值3.1 核心作用3.2 核心價值3.3 典型使用場景 *最后我想說&#xff1a;請你不要相信我說的每一句話&#xff0c;這只是我的個人經驗* 一、全英文URL刷新…

Oracle 19c DG備庫報錯ORA-00313、ORA-00312、ORA-27037

Oracle 19c DG備庫報錯ORA-00313、ORA-00312、ORA-27037 錯誤排查問題處理錯誤排查 DG同步完成后,DG Broker show database發現以下告警信息: Database Warning(s):ORA-16826: apply service state is inconsistent with the DelayMins propertyORA-16789: standby redo log…

開源與閉源之爭:AI時代的創新博弈與未來抉擇

在人工智能技術狂飆突進的今天&#xff0c;開源與閉源之爭已不再局限于技術圈的討論&#xff0c;而是演變為一場關乎技術倫理、商業格局乃至人類文明走向的深度博弈。當Meta的Llama 3開源模型下載量突破百萬&#xff0c;當OpenAI的GPT-5繼續加固技術壁壘&#xff0c;這場沒有硝…

NIFI的處理器:JSLTTransformJSON 2.4.0

該處理器使用JSLT轉換FlowFile JSON有效負載的格式。使用轉換后的內容創建新的FlowFile&#xff0c;并將其路由到“成功”關系。如果JSLT轉換失敗&#xff0c;則將原始FlowFile路由到“失敗”關系。 需要注意的是&#xff0c;編譯JSLT轉換可能相當昂貴。理想情況下&#xff0c…

MySQL 索引失效及其解決辦法

一、前言 在數據庫優化中,索引(Index)是一項至關重要的技術手段,可以顯著提升查詢性能。然而,在實際開發過程中,MySQL 索引并不總是如預期生效。本文將從原理出發,系統地介紹索引失效的常見場景及其解決方案,幫助開發者有效規避性能陷阱。 二、索引基礎回顧 MySQL 支…

趨勢觸發策略

趨勢觸發策略(TS版)是一種基于TrendTriggerFactor(TTF)的交易策略,通過柱狀圖顏色變化指示市場趨勢的強度,并根據TTF的穿越信號進行買賣操作。 TTF是通過計算買方力量和賣方力量的差值除以兩者之和的一半再乘以100得到的。 當TTF大于100時,柱狀圖顯示為綠色,表示市場…

DeepSeek-R1 模型現已在亞馬遜云科技上推出

亞馬遜云科技提供眾多免費云產品&#xff0c;可以訪問&#xff1a;亞馬遜云科技 在剛剛過去的 Amazon re&#xff1a;Invent 期間&#xff0c;Amazon 首席執行官 Andy Jassy 分享了從 Amazon 自己在全公司開發近 1000 個生成式 AI 應用程序的經驗中汲取的寶貴經驗。從這種廣泛…

中臺項目-微前端qiankun-umimax

學習視頻&#x1f50a; 基礎&#xff1a; 黑馬前端基于qiankun搭建微前端項目實戰教程_嗶哩嗶哩_bilibili 路由、部署配置注意&#xff1a;qiankunvite微前端上線注意事項&#xff0c;base公共路徑設置_嗶哩嗶哩_bilibili 微前端 什么是微前端&#xff1f; 微前端是將前端應…

【Java學習筆記】代碼塊

代碼塊 介紹&#xff1a;代碼塊又稱為初始化塊&#xff0c;屬于類中的成員&#xff08;即是類的一部分&#xff09;&#xff0c;類似于方法&#xff0c;將邏輯語句封裝在方法體中&#xff0c;通過{}包圍起來 與類方法的不同點 無方法名 無返回類型 無參數 只有方法體&#…

spring boot 2.7集成舊的springfox-boot-starter swagger oas 3.0

舊版本目前已經不維護推薦使用 springdoc-openapi-ui&#xff0c;這里為了演示使用舊的最新依賴 <dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version> </dep…

Linux按鍵驅動測試方式詳細介紹

Linux按鍵驅動測試可采用以下分層方法&#xff1a; 基礎事件檢測 使用輸入子系統調試工具&#xff1a; sudo apt install evtest # 安裝事件測試工具 evtest # 選擇對應設備編號觸發按鍵后觀察終端輸出&#xff0c;正常情況應顯示&#xff1a; Event:…

USB學習【13】STM32+USB接收數據過程詳解

目錄 1.官方的描述2.HAL的流程把接收到的數據從PMA拷貝到用戶自己定義的空間中 3.處理接收到的數據4.最后再次開啟準備接收工作 1.官方的描述 2.HAL的流程 以上的官方說法我們暫時按下不表。 如果接收到數據&#xff0c;會激活中斷進入到USB_LP_CAN1_RX0_IRQHandler&#xff0…

上海內推 | 上海算法創新研究院-上海交大聯合招收空間智能/具身智能算法實習生

最近這一兩周不少公司已開啟春招和實習招聘。 不同以往的是&#xff0c;當前職場環境已不再是那個雙向奔赴時代了。求職者在變多&#xff0c;HC 在變少&#xff0c;崗位要求還更高了。 最近&#xff0c;我們又陸續整理了很多大廠的面試題&#xff0c;幫助一些球友解惑答疑&am…