排序算法之六:快速排序(遞歸)

快速排序的基本思想

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

其基本思想為:

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

// 假設按照升序對array數組中[left, right)區間中的元素進行排序
void QuickSort(int array[], int left, int right)
{if(right - left <= 1)return;// 按照基準值對array數組的 [left, right)區間中的元素進行劃分int div = partion(array, left, right);// 劃分成功后以div為邊界形成了左右兩部分 [left, div) 和 [div+1, right)// 遞歸排[left, div)QuickSort(array, left, div);// 遞歸排[div+1, right)QuickSort(array, div+1, right);
}

上述為快速排序遞歸實現的主框架,發現與二叉樹前序遍歷規則非常像,同學們在寫遞歸框架時可想想二叉樹前序遍歷規則即可快速寫出來,后序只需分析如何按照基準值來對區間中數據進行劃分的方式即可。

劃分區間的常見方式

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

  1. hoare方法
  2. 挖坑法
  3. 前后指針法?

三種方法是排key左右區間的不同,整體快排的思想是遞歸?

1.hoare方法

https://img-blog.csdnimg.cn/07ddcfdc56874b2a9d12f585534ac87e.gif#pic_center

1.1圖示

定義left和right來找大和找小

right先走找大,left再走找小,找到交換

繼續找大找小

相遇停下來,和key交換

1.2為什么相遇位置一定比key小

這里我們有一個問題:為什么相遇位置一定比key小?

因為右邊先走

相遇有兩種情況:

  1. right遇left -> left先走,right沒有遇到比key小的,一直走,直到遇到left停下來,left存的是比key小的值
  2. left遇right?-> right先走,left沒有遇到比key大的,一直走,直到遇到right停下來,right存的是比key大的值
  3. 所以我們得出一個結論,左邊做key,右邊先走;右邊做key,左邊先走

如果左邊有序,右邊也有序,整體就有序了

那么如何讓左右有序呢?

類似二叉樹,遞歸左樹和右樹

第一遍排序后的left和right的范圍是:[begin,keyi-1],keyi,[keyi+1,end]

然后繼續遞歸,直到這個區間只有一個值或者不存在

1.3代碼示例

//hoare方法
int PartSort1(int*a,int begin,int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int left = begin, right = end;int keyi = begin;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[left], &a[keyi]);keyi = left;return keyi;
}

2.挖坑法?

https://img-blog.csdnimg.cn/c2dde0e21f32461fb43db524559ca36d.gif#pic_center

2.1圖示

right找小,left找大,right先走,找到小和坑位交換,然后left走,left找到大之后和坑位交換,交替進行直到相遇

他們一定會相遇到坑的位置

相遇之后將key的值放到坑位中,這時候key左邊就是比key小的,key右邊就是比key大的

2.2代碼示例

我們寫一個挖坑法的函數來排keyi左右的數據

先用三數取中方法得到keyi,定義一個key保存keyi的值,定義一個坑位holei先放到begin

  • 右邊找小,填到左邊的坑里,右邊成為新的坑
  • 左邊找大,填到右邊的坑里,左邊成為新的坑?
  • 相遇后將key放到坑里,返回坑的下標?
//挖坑法
int PartSort2(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int key = a[begin];int holei = begin;while (begin < end){//右邊找小while (begin < end && a[end] <= key)--end;a[holei] = a[end];holei = end;//左邊找大while (begin < end && a[begin] >= key)++begin;a[holei] = a[begin];holei = begin;}//相遇a[holei] = key;return holei;
}

3.前后指針法

https://img-blog.csdnimg.cn/8baec430614e47dfa382926553830c14.gif#pic_center

3.1圖示

prev要不就是緊跟cur,要不prev和cur之間就是比key大的值

3.2代碼示例

//前后指針法
int PartSort3(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int keyi = begin;int prev = begin, cur = begin + 1;while (cur <= end){//if (a[cur] < a[keyi])//{//	++prev;//	Swap(&a[prev], &a[cur]);//	++cur;//}//else//	++cur;if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);++cur;}Swap(&a[keyi], &a[prev]);keyi = prev;return keyi;
}

4.快速排序優化

  1. 三數取中法選key
  2. 遞歸到小的子區間時,可以考慮使用插入排序

4.1三數取中方法

這里我們的key默認取的是第一個數,但是這種情況有個弊端,不能保證key一定是那個中間值,可能是最小的,也可能是最大的

但是理想情況下,key選中間值是效率最高的,每次都是二分?

這里就有一個方法能很好的解決這個問題:三數取中

我們寫一個取中函數,將中間值與begin交換,還是將key給到begin

int GetMidi(int* a, int begin, int end)
{int midi = (begin + end) / 2;if (a[begin] < a[midi]){if (a[midi] < a[end])return midi;else if (a[begin] > a[end])return begin;elsereturn end;}else{if (a[midi] > a[end])return midi;else if (a[end] > a[begin])return begin;elsereturn end;}
}

三數取中可以排除掉最壞的情況,相對而言可以提高效率

4.2小區間優化

如果是滿二叉樹,最后一層占50%的節點,倒數第二層占25%,倒數第三層占12.5%

假設我們要對這五個數排序,就需要調用六次遞歸,這代價是非常大的

我們可以使用插入排序,插入排序對局部的適應性是很好的,所以我們在這個區間縮小的一定范圍的時候就可以使用插入排序

一般選擇最后三到四層,因為最后三層就占據了將就90%的遞歸,將最后三層的遞歸消除是能夠明顯提高效率的

剩下的區間不一定是從0開始的,也有可能是后半段,所以這里插入排序從begin開始

總代碼

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}elsebreak;}a[end + 1] = tmp;}
}
int GetMidi(int* a, int begin, int end)
{int midi = (begin + end) / 2;if (a[begin] < a[midi]){if (a[midi] < a[end])return midi;else if (a[begin] > a[end])return begin;elsereturn end;}else{if (a[midi] > a[end])return midi;else if (a[end] > a[begin])return begin;elsereturn end;}
}
//hoare方法
int PartSort1(int*a,int begin,int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int left = begin, right = end;int keyi = begin;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[left], &a[keyi]);keyi = left;return keyi;
}
//挖坑法
int PartSort2(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int key = a[begin];int holei = begin;while (begin < end){//右邊找小while (begin < end && a[end] <= key)--end;a[holei] = a[end];holei = end;//左邊找大while (begin < end && a[begin] >= key)++begin;a[holei] = a[begin];holei = begin;}//相遇a[holei] = key;return holei;
}
//前后指針法
int PartSort3(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[midi], &a[begin]);int keyi = begin;int prev = begin, cur = begin + 1;while (cur <= end){//if (a[cur] < a[keyi])//{//	++prev;//	Swap(&a[prev], &a[cur]);//	++cur;//}//else//	++cur;if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);++cur;}Swap(&a[keyi], &a[prev]);keyi = prev;return keyi;
}
//快排
void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;if (end - begin + 1 <= 10)InsertSort(a + begin, end - begin + 1);else{//hoare法//int keyi = PratSort1(a, begin, end);//int keyi = PartSort2(a, begin, end);int keyi = PartSort3(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);}
}
int main()
{int a[] = { 9,8,7,6,6,5,4,3,2,1,10,14,12,11,15 };int n = sizeof(a) / sizeof(a[0]);QuickSort(a, 0, n - 1);for (int i = 0; i < n; i++){printf("%d ", a[i]);}return 0;
}

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

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

相關文章

《深入理解計算機系統》學習筆記 - 第四課 - 浮點數

Floating Point 浮點數 文章目錄 Floating Point 浮點數分數二進制示例能代表的數浮點數的表示方式浮點數編碼規格化值規格化值編碼示例 非規格化的值特殊值 示例IEEE 編碼的一些特殊屬性四舍五入&#xff0c;相加&#xff0c;相乘四舍五入四舍五入的模式二進制數的四舍五入 浮…

【Qt5】setWindowFlags的標志有哪些?

2023年12月9日&#xff0c;周六晚上 窗口類型&#xff1a; Widget&#xff08;0x00000000&#xff09;&#xff1a;普通窗口部件。Window&#xff08;0x00000001&#xff09;&#xff1a;標準窗口。Dialog&#xff08;0x00000002 | Window&#xff09;&#xff1a;對話框&#…

UI自動化Selenium 鼠標滑動懸停到指定元素

ActionChains執行原理 他是按照設計好的動作順序鏈式執行&#xff1b; 當調用ActionChains的方法時&#xff0c;不會立即執行&#xff0c;只是將要做的動作安裝順序存放在隊列中&#xff1b;當調用perform()方法時&#xff0c;隊列中的方法會依次執行&#xff1b; from sele…

西南科技大學數字電子技術實驗三(MSI邏輯器件設計組合邏輯電路及FPGA的實現)預習報告

一、計算/設計過程 說明:本實驗是驗證性實驗,計算預測驗證結果。是設計性實驗一定要從系統指標計算出元件參數過程,越詳細越好。用公式輸入法完成相關公式內容,不得貼手寫圖片。(注意:從抽象公式直接得出結果,不得分,頁數可根據內容調整) 1、4位奇偶校驗器 真值表 …

C++ Qt開發:使用關聯容器類

當我們談論編程中的數據結構時&#xff0c;順序容器是不可忽視的一個重要概念。順序容器是一種能夠按照元素添加的順序來存儲和檢索數據的數據結構。它們提供了簡單而直觀的方式來組織和管理數據&#xff0c;為程序員提供了靈活性和性能的平衡。 Qt 中提供了豐富的容器類&…

AI:大模型技術

Prompt Prompt&#xff08;提示&#xff09;是一種在人工智能領域&#xff0c;特別是在自然語言處理和聊天機器人中常用的技術。它是一種輸入&#xff0c;用于激發人工智能模型生成相應的輸出。在聊天機器人中&#xff0c;用戶輸入的問題或請求就是提示&#xff0c;而聊天機器…

基于AidLux的工業視覺少樣本缺陷檢測實戰應用

1. 模型轉換 AIMO網站&#xff1a; http://aimo.aidlux.com/ 試用賬號和密碼&#xff1a; 賬號&#xff1a;AIMOTC001 &#xff0c;密碼&#xff1a;AIMOTC001 上傳模型選擇目標平臺參數設置選擇自動轉換轉換結果并下載 2. 基于AidLux的語義分割模型部署 dataset2aidlux文件…

期待一下elasticsearch還未發布的8.12版本,由lucene底層帶來的大幅度提升

現在是北京時間23年12月10日。當前es最新版本還是es8.11版本。我們可以期待一下不久的將來&#xff0c;es的8.12版本看到大幅度的檢索性能提升。受益于 Lucene 9.9版本&#xff0c;內核帶來的大幅提升&#xff01; 此次向量檢索利用底層指令fma會性能提升5%。并且還提供了向量點…

在Spring Cloud使用Hystrix核心組件,并注冊到Eureka注冊中心去

其實吧&#xff0c;寫Spring Cloud系列&#xff0c;我有時候覺得也挺難受的&#xff0c;因為Spring Cloud的微服務啟動都需要一個一個來&#xff0c;并且在IDea中也需要占用比較大的內存&#xff0c;并且我本來可以一篇寫完5大核心組件的&#xff0c;但是我卻分了三篇&#xff…

簡單的圖像分類任務全流程示例(內含代碼)

以下是一個簡單的示例&#xff0c;展示了如何使用 PyTorch 處理自定義圖像分類數據集&#xff1a; import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoad…

erlang實現用ets做一級緩存

一、Erlang中的ETS表和DETS表 ETS表是Erlang中的一種數據結構&#xff0c;它允許我們在內存中存儲數據。ETS表有許多用途&#xff0c;其中包括作為緩存的一種實現方式。ETS表的特點是它們在內存中以表的形式存儲數據&#xff0c;這使得訪問和操作數據非常快。 DETS表是Erlang…

【求職】外企德科-網易游戲測試面試記錄

前面的話&#xff1a;本來沒想寫&#xff0c;但是竟然收到了一面通過的通知&#xff0c;那就來回顧一下一面&#xff0c;為終面做做準備。 這次面試基本沒有做什么準備&#xff0c;本來也就是抱著試一試的心態做的筆試&#xff0c;結果筆試通過了&#xff0c;由于筆試的內容很…

LINUX-ROS集成安裝MQTT庫步驟注意事項

環境信息 roottitan-ubuntu1:/home/mogo/data/jp/paho.mqtt.cpp# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.5 LTS Release: 18.04 Codename: bionic 步驟 安裝doxygen sudo apt install doxygen 構…

Fcopy: 基于Coke實現內網大文件分發

在工作中&#xff0c;我曾與小伙伴討論過這樣一個實際問題&#xff1a;數據制作流程產生了一份需要上線的文件&#xff0c;而線上有數十臺甚至上百臺機器&#xff0c;有什么樸素的辦法以盡可能快的速度將文件分發到指定的機器上嗎&#xff1f;根據作者已有的知識&#xff0c;分…

普冉(PUYA)單片機開發筆記(5): 配置定時器PWM輸出

概述 定時器的輸出通道作為 PWM 驅動是 MCU 的常用功能。 PY32F003 有一個高級定時器 TIM1 和一個通用定時器 TIM3&#xff0c;這兩個定時器都可以驅動4個輸出通道。現在我們就利用 TIM1 的某一個通道實現可控占空比的 PWM 輸出。 原理簡介 看數據手冊&#xff0c;簡單摘錄…

激活函數數學詳解以及應用場景解釋

文章目錄 激活函數1. Sigmoid 激活函數例子及推導過程代碼 2. ReLU 激活函數例子及推導過程 3. Tanh 激活函數例子及推導過程代碼 4. Softmax 激活函數例子及推導過程代碼 CNN 中的卷積層工作原理卷積計算過程卷積后的輸出及 ReLU 應用 激活函數 激活函數在神經網絡中扮演著至…

IPSec 協議

在 TCP/IP 協議中&#xff0c;對 IP 數據包沒有提供任何安全保護&#xff0c;攻擊者可以通過網絡嗅探、 IP 欺騙、連接截獲等方法來攻擊正常的 TCP/IP 通信。因此&#xff0c;通信過程中會存在以下危險&#xff1a;數據并非來自合法的發送者、數據在傳輸過程中被非法篡改、信息…

前端知識(十七)——入口函數和特定函數的區別

入口函數和特定函數是編程中常見的兩種函數類型&#xff0c;它們在功能和使用場景上有所不同。下面我將通過Python代碼示例來解釋它們的區別。 1.入口函數&#xff1a;入口函數通常是一個程序或模塊的起始點&#xff0c;它負責接收用戶輸入或外部數據&#xff0c;并啟動程序的…

DM8/達夢 數據庫管理員使用手冊詳解

1.1DM客戶端存放位置 Windows&#xff1a;DM數據庫安裝目錄中tool文件夾和bin文件夾中。 Linux&#xff1a;DM數據庫安裝目錄中tool目錄和bin目錄中。 1.2DM數據庫配置助手 1.2.1Windows創建數據庫 打開數據庫配置助手dbca 點擊創建數據庫實例 選擇一般用途 瀏覽選擇數據庫…

圖中的最長環

說在前面 &#x1f388;不知道大家對于算法的學習是一個怎樣的心態呢&#xff1f;為了面試還是因為興趣&#xff1f;不管是處于什么原因&#xff0c;算法學習需要持續保持&#xff0c;今天讓我們一起來看看這一道題目————圖中的最長環&#xff0c;圖論題目中比較常見的環路…