[數據結構1.0]快速排序

最近學習了快速排序,鼠鼠俺來做筆記了!

本篇博客用排升序為例介紹快速排序!

1.快速排序

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

如果對上面的介紹蒙圈的話,沒關系,我們繼續看下面的內容,會仔細介紹的!

1.1.快速排序的”單趟“

快速排序的”單趟“簡單來說就是在需排序亂序數組中任取一個元素作為基準值,經過”單趟“過后,大于或者等于基準值的元素都排在基準值的后面,小于或者等于基準值的元素都排在基準值的前面,也就是說基準值所在的位置就是它應該出現的位置,這個基準值就排好了!

對于”單趟“的實現方法有但不限于下面三種:

1.1.1.hoare版本

這個動圖就是hoare版本的”單趟“實現方法!這里取第一個元素6為基準值;R從最”右邊“開始找比基準值小的元素,L從最”左邊“開始找比基準值大的元素,?然后交換下標為R和L的元素;R繼續找比基準值小的元素,L繼續找比基準值大的元素,再交換下標為R和L的元素…………直到R和L相遇,將基準值和相遇位置的元素即可!

其實這個本質就是將比基準值大的元素”甩“到”后面“,將比基準值小的元素”甩“到”前面“。

也許會有疑問,怎么保證相遇位置的元素一定不大于基準值呢?

因為只要是R先動,L后動的話必然能保證相遇的元素一定不大于基準值!

相遇無非兩種情況:

1.R遇L:R在去找小于基準值的元素的過程中,下標為L的元素必然是不大于基準值的元素。當R去找小于基準值的元素沒有找到卻遇到L時, 那么相遇位置的值就是不大于基準值的元素。

2.L遇R:由于R先動,那么L在找大于基準值的元素的過程中,下標為R的元素必然是不大于基準值的元素。當L去找大于基準值的元素沒有找到卻遇到R時,那么相遇位置的值就是不大于基準值的元素。

?hoare版本的“單趟”代碼如下,需排序亂序數組下標為begin—end:

//hoare版本int keyi = begin;int left = begin, right = end;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[right]);keyi = right;

由于hoare版本寫起來很容易出錯誤,所以我們一般寫下面兩種版本!

1.1.2.挖坑法版本

也是取第一個元素6為基準值。初始坑位設置為基準值下標。讓R從最“右邊”開始找比基準值小的元素,找到后將下標為R的元素填入坑位,那么新的坑位就變成了R;讓L從最“左邊”開始找比基準值大的元素,找到后將下標為L的元素填入坑位,那么新的坑位就變成了L;R再找比基準值小的元素……直到R和L相遇,將基準值填入坑位即可。

本質就是R找小填入“左邊”坑位,L找大填入“右邊”坑位,最后一個坑位必定是R和L相遇位置,填入基準值就好。

挖坑法版本“單趟”代碼如下,需排序亂序數組下標為begin—end:

//挖坑法版本int  key=a[begin];int left = begin, right = end;int hole = begin;while (left < right){while (left < right && a[right] >= key){right--;;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;int keyi = hole;
1.1.3.前后指針版本

取第一個元素6為基準值。prev初始指向基準值,cur初始指向基準值的下一個元素。cur遍歷數組:如果cur遇到大于基準值的元素,++cur;否則++prev、cur指向的元素和prev指向的元素交換、++cur。

前后指針版本“單趟”代碼如下, 需排序亂序數組下標為begin—end:

//前后指針版本int cur = begin + 1, prev = begin;int keyi = begin;while (cur <= end){if (a[cur] > a[keyi]){++cur;}else{++prev;Swap(&a[prev], &a[cur]);++cur;}}Swap(&a[keyi], &a[prev]);keyi = prev;

這里指針寫成了下標的形式,思想沒變,本質上還是一樣的!

1.2.快速排序的遞歸寫法?

經過“單趟”過后,被選中的基準值就排在了它該出現的位置,就是說當數組排好變得有序后,基準值就在“單趟”過后出現的位置!

既然基準值已經拍好了,如果基準值的“左邊”的元素集合能有序并且基準值的“右邊”的元素集合能有序,那么需排序的亂序數組就排好了,變得有序了。舉個例子:

如圖, 所以說快速排序一種二叉樹結構的交換排序方法。進行“單趟”排好基準值,遞歸“左邊”元素集合…………“左邊”元素集合排好后,遞歸“右邊”元素集合…………“右邊”元素集合排好后就搞定了!

遞歸結束條件:當元素集合只有1個值或為空。

看看快速排序的遞歸寫法代碼:

void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}//hoare版本/*int keyi = begin;int left = begin, right = end;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[right]);keyi = right;*///挖坑法版本int  key=a[begin];int left = begin, right = end;int hole = begin;while (left < right){while (left < right && a[right] >= key){right--;;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;int keyi = hole;//前后指針版本/*int cur = begin + 1, prev = begin;int keyi = begin;while (cur <= end){if (a[cur] > a[keyi]){++cur;}else{++prev;Swap(&a[prev], &a[cur]);++cur;}}Swap(&a[keyi], &a[prev]);keyi = prev;*/QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

我們試試這個快速排序:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>void PrintArrar(int* a, int n)
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}//hoare版本/*int keyi = begin;int left = begin, right = end;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[right]);keyi = right;*///挖坑法版本/*int  key=a[begin];int left = begin, right = end;int hole = begin;while (left < right){while (left < right && a[right] >= key){right--;;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;int keyi = hole;*///前后指針版本int cur = begin + 1, prev = begin;int keyi = begin;while (cur <= end){if (a[cur] > a[keyi]){++cur;}else{++prev;Swap(&a[prev], &a[cur]);++cur;}}Swap(&a[keyi], &a[prev]);keyi = prev;QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}int main()
{int a[] = { 9,8,5,47,6,3,2,10 };PrintArrar(a, sizeof(a) / sizeof(a[0]));QuickSort(a, 0, sizeof(a) / sizeof(a[0]) - 1);PrintArrar(a, sizeof(a) / sizeof(a[0]));return 0;
}

沒問題的:

1.3.快速排序的非遞歸寫法

鼠鼠這里介紹一種快速排序的非遞歸寫法。

需要用到鼠鼠前面博客?介紹的棧,利用到棧寫的快速排序沒有用到遞歸但思想卻很像遞歸!

?非遞歸寫法思想如上圖。我們看快速排序非遞歸代碼如下,其中變量s是棧。鼠鼠這里“單趟”選擇挖坑法版本,當然老爺們可以用其他版本:

void QuickSortNonr(int* a, int begin, int end)
{Stack s;StackInit(&s);StackPush(&s, end);StackPush(&s, begin);while (!StackEmpty(&s)){int start = StackTop(&s);StackPop(&s);int finish = StackTop(&s);StackPop(&s);int  key = a[start];int left = start, right = finish;int hole = start;while (left < right){while (left < right && a[right] >= key){right--;;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;if (start < left - 1){StackPush(&s, left - 1);StackPush(&s, start);}if (left + 1 < finish){StackPush(&s, finish);StackPush(&s, left + 1);}}StackDestroy(&s);
}

我們要用到棧,所以記得將我們自己寫的棧的頭文件和源文件拷貝一份到快速排序的工程目錄下?,再包棧的頭文件就可以用了。我們試試:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include"Stack.h" //注意包含棧的頭文件void PrintArrar(int* a, int n)
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void QuickSortNonr(int* a, int begin, int end)
{Stack s;StackInit(&s);StackPush(&s, end);StackPush(&s, begin);while (!StackEmpty(&s)){int start = StackTop(&s);StackPop(&s);int finish = StackTop(&s);StackPop(&s);int  key = a[start];int left = start, right = finish;int hole = start;while (left < right){while (left < right && a[right] >= key){right--;;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;if (start < left - 1){StackPush(&s, left - 1);StackPush(&s, start);}if (left + 1 < finish){StackPush(&s, finish);StackPush(&s, left + 1);}}StackDestroy(&s);
}int main()
{int a[] = { 9,8,5,47,6,3,2,10 };PrintArrar(a, sizeof(a) / sizeof(a[0]));QuickSortNonr(a, 0, sizeof(a) / sizeof(a[0]) - 1);PrintArrar(a, sizeof(a) / sizeof(a[0]));return 0;
}

?沒問題吧!

2.快速排序遞歸寫法優化?

2.1.三數取中法選基準值

對于遞歸寫法來說,對于需排序數組本身就是升序或者降序的情況適應的不是很好,因為:

1.固定了選擇元素集合第一個元素為基準值,每次“單趟”過后都會導致某一邊元素集合為空的情況。這樣的話如果本身就是升序或者降序的需排序數組個數有n個的話,就要遞歸n層,很容易棧溢出!

2.每次“單趟”時間復雜度是O(N),遞歸n層的話快速排序時間復雜度是O(N^2),時間效率不劃算!

所以我們有三數取中選基準值,我們取元素集合第一個元素、最后一個元素和中間那個元素,這三個元素比較得出第二大的元素,將這個元素與元素集合第一個元素交換再進行“單趟”。

這樣的話就能很好適應需排序數組本身就是升序或者降序的情況,因為這樣經過“單趟”之后,基準值一定會出現在“中間”。這樣子去遞歸的話,每一層遞歸都會被“二分”,遞歸層數大大減少,遞歸log(N)層就行!

加入了三數取中選基準值的遞歸寫法的快速排序時間復雜度是O(N*logN)。

而且加入了三數取中選基準值的遞歸寫法的快速排序對于需排序數組本身不是升序或者降序的情況一樣有幫助,可以讓每層遞歸盡量“二分”,從而減少遞歸層數!

三數取中法代碼:

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

當沒有加入三數取中選基準值的遞歸寫法的快速排序排10000個升序元素組成的數組時,在Debug環境下就會崩潰,棧溢出了:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>int GetMidi(int* a, int begin, int end)
{int midi = begin + (end - begin) / 2;if (a[begin] > a[midi]){if (a[begin] > a[end]){if (a[midi] > a[end]){return midi;}elsereturn end;}else{return begin;}}else{if (a[midi] > a[end]){if (a[end] > a[begin]){return end;}else{return begin;}}else{return midi;}}
}void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}//三數取中選基準值/*int midi = GetMidi(a, begin, end);Swap(&a[begin], &a[midi]);*///前后指針版本int cur = begin + 1, prev = begin;int keyi = begin;while (cur <= end){if (a[cur] > a[keyi]){++cur;}else{++prev;Swap(&a[prev], &a[cur]);++cur;}}Swap(&a[keyi], &a[prev]);keyi = prev;QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}int main()
{int n = 10000;int* a = (int*)malloc(sizeof(int) * n);srand((unsigned int)time(0));a[0] = rand();for (int i = 1; i < n ; i++){a[i] = a[i-1] + 1;}int begin = clock();QuickSort(a, 0, n - 1);int end = clock();printf("%d\n", end - begin);return 0;
}

結果:

當加入三數取中選基準值的遞歸寫法的快速排序,排100w個升序元素組成的數組都沒問題,Debug環境下:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>int GetMidi(int* a, int begin, int end)
{int midi = begin + (end - begin) / 2;if (a[begin] > a[midi]){if (a[begin] > a[end]){if (a[midi] > a[end]){return midi;}elsereturn end;}else{return begin;}}else{if (a[midi] > a[end]){if (a[end] > a[begin]){return end;}else{return begin;}}else{return midi;}}
}void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}void QuickSort(int* a, int begin, int end)
{if (begin >= end){return;}//三數取中選基準值int midi = GetMidi(a, begin, end);Swap(&a[begin], &a[midi]);//前后指針版本int cur = begin + 1, prev = begin;int keyi = begin;while (cur <= end){if (a[cur] > a[keyi]){++cur;}else{++prev;Swap(&a[prev], &a[cur]);++cur;}}Swap(&a[keyi], &a[prev]);keyi = prev;QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}int main()
{int n = 1000000;int* a = (int*)malloc(sizeof(int) * n);srand((unsigned int)time(0));a[0] = rand();for (int i = 1; i < n ; i++){a[i] = a[i-1] + 1;}int begin = clock();QuickSort(a, 0, n - 1);int end = clock();printf("%d\n", end - begin);return 0;
}

?結果:

2.2.小區間優化

遞歸到小的子區間(數量少的元素集合)時,可以考慮使用插入排序。這樣子可以減少大部分的遞歸,因為大部分的遞歸都是由小的子區間產生的。不過由于編譯器優化的厲害,小區間優化效果不是很明顯,鼠鼠就在這里順便提一提算了!

感謝閱讀!

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

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

相關文章

202103青少年軟件編程(Python)等級考試試卷(一級)

一、單選題&#xff08;共25題&#xff0c;每題2分&#xff0c;共50分&#xff09; 下列哪個操作不能退出IDLE環境&#xff1f;&#xff08; &#xff09; A、AltF4 B、CtrlQ C、按ESC鍵 D、exit() 試題編號&#xff1a;20210124-yfj-003 題型&#xff1a;單選題 答案&#xf…

Java面試八股之一個char類型變量能不能存儲一個中文字符

Java中一個char類型變量能不能存儲一個中文字符&#xff1f;為什么&#xff1f; Java中一個char類型變量可以存儲一個中文字符。原因如下&#xff1a; Unicode編碼支持&#xff1a;Java語言采用Unicode字符集作為其內建字符編碼方式。Unicode是一種廣泛接受的字符編碼標準&am…

兩小時看完花書(深度學習入門篇)

1.深度學習花書前言 機器學習早期的時候十分依賴于已有的知識庫和人為的邏輯規則&#xff0c;需要人們花大量的時間去制定合理的邏輯判定&#xff0c;可以說是有多少人工&#xff0c;就有多少智能。后來逐漸發展出一些簡單的機器學習方法例如logistic regression、naive bayes等…

mybatisplus查詢練習代碼

mybatisplus查詢練習代碼 package com.yase;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.yase.entity.Student; import com.yase.entity.Teacher; import com.yase…

什么是CCRC?做什么用的?

CCRC&#xff08;中國網絡安全審查認證和市場監管大數據中心&#xff09;原名為中國網絡安全審查技術與認證中心&#xff0c;也被稱為中國信息安全認證中心&#xff08;ISCCC&#xff09;。 該中心是經中央機構編制委員會辦公室批準成立的&#xff0c;其主要職責是依據國家法律…

kafka集群傳統部署(raft模式)—— 筑夢之路

kafka二進制包&#xff1a;https://dlcdn.apache.org/kafka/3.7.0/kafka_2.13-3.7.0.tgz 集群規劃 主機名IP地址節點ID角色分配kafka1192.168.100.1001broker,controllerkafka2192.168.100.1012broker,controllerkafka3192.168.100.1023broker,controller 編輯配置文件 con…

代碼隨想錄算法訓練營第36天|● 738.單調遞增的數字 ● 968.監控二叉樹

738. 單調遞增的數字 發現第一位變小了其他的迅速變9 class Solution:def monotoneIncreasingDigits(self, n: int) -> int:strnlist(str(n))for i in range(len(strn)-1,0,-1):if strn[i-1]>strn[i]:strn[i-1]str(int(strn[i-1])-1)for j in range(i,len(strn)):strn[…

超級簡單的地圖操作工具開發可疑應急,地圖畫點,畫線,畫區域,獲取地圖經緯度等

使用echars的地圖畫點,畫線,畫區域,獲取地圖經緯度等 解壓密碼:10086007 地圖也是用臨時的bmap.js和china.js純離線二選一 一共就這么多文件 畫點,畫線,畫區域 點擊地圖獲取經緯度-打印到控制臺,這樣就能渲染航跡,多變形,結合其他算法算圓等等操作 下載資源:https://download…

JSON-server 服務的搭建

1、全局安裝&#xff1a; pnpm i -g json-server2、創建db.json文件 {"posts": [{"id": 1,"title": "json-server","author": "typicode"}],"comments":[{"id": 1,"body": "…

什么情況下會造成索引失效?

2.3.4. 索引失效 對索引使用左或者左右模糊匹配 使用左或者左右模糊匹配的時候&#xff0c;也就是 like %xx 或者 like %xx% 這兩種方式都會造成索引失效。但是如果前綴是確定的那么就可以使用到索引&#xff0c;例如 name like 許%。 因為索引 B 樹是按照「索引值」有序排列…

SpringBoot 中 zip 文件解壓工具類

SpringBoot 中 zip 文件解壓工具類 zip 文件解壓&#xff08;不支持密碼&#xff09; 相關 Maven 依賴 <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.6</version>…

練習題(2024/5/14)

1四數相加 II 給你四個整數數組 nums1、nums2、nums3 和 nums4 &#xff0c;數組長度都是 n &#xff0c;請你計算有多少個元組 (i, j, k, l) 能滿足&#xff1a; 0 < i, j, k, l < nnums1[i] nums2[j] nums3[k] nums4[l] 0 示例 1&#xff1a; 輸入&#xff1a;n…

代碼隨想錄訓練營Day28:貪心算法06

1.738單調遞增的數字 貪心策略&#xff1a;如果strNum[i]<strNum[i-1]那么strNum[i] 9,strNum[i-1]--;//比如87對應的最大的單調遞增的就是79. 具體實現&#xff1a; 對于遇到小于的情況&#xff1a;如果strNum[i]<strNum[i-1]那么strNum[i] 9,strNum[i-1]--;遍歷順…

linux phpstudy 重啟命令

[rootLinuxWeb phpstudy]# ./system/phpstudyctl restart 查看命令 1) phpstudy -start 啟動小皮面板 2) phpstudy -stop 停止小皮面板 3) phpstudy -restart 重啟小皮面板 4) phpstudy -status 查詢面板狀態 5) phpstudy -in…

OFDM802.11a的FPGA實現(十五)短訓練序列:STS(含Matlab和verilog代碼)

原文鏈接&#xff08;相關文章合集&#xff09;&#xff1a;OFDM 802.11a的xilinx FPGA實現 1.前言 在之前已經完成了data域數據的處理&#xff0c;在構建整個802.11a OFDM數據幀的時候&#xff0c;還剩下前導碼和signal域的數據幀&#xff0c;這兩部分的內容。 PLCP的前導部分…

Nodejs筆記2

模塊化 模塊化初體驗 模塊暴露數據 導入模塊 fs 寫絕對路徑 require寫相對路徑不會受到影響 ./../不能省略 js 和json文件后綴可以省略 如果存在 命名相同的js和json文件&#xff0c;優先導入js文件 導入文件夾時的情況 require導入模塊的基本流程 commonJS模塊…

其它高階數據結構①_并查集(概念+代碼+兩道OJ)

目錄 1. 并查集的概念 2. 并查集的實現 3. 并查集的應用 3.1 力扣LCR 116. 省份數量 解析代碼1 解析代碼2 3.2 力扣990. 等式方程的可滿足性 解析代碼 本篇完。 寫在前面&#xff1a; 此高階數據結構系列&#xff0c;雖然放在⑤數據結構與算法專欄&#xff0c;但還是作…

【數據可視化01】matplotlib實例介紹4之六邊形分箱圖

目錄 一、引言二、實例介紹 一、引言 hexbin是一個二維直方圖&#xff0c;其中箱子是六邊形&#xff0c;顏色表示每個箱子內的數據點數。 二、實例介紹 import matplotlib.pyplot as plt import numpy as np# Fixing random state for reproducibility np.random.seed(19680…

服務器利用率的神器腳本

在服務器管理的過程中&#xff0c;了解服務器的各項性能指標是至關重要的。無論是CPU的負載情況&#xff0c;內存使用情況&#xff0c;還是硬盤的存儲空間以及TCP連接狀態&#xff0c;這些都是我們判斷服務器健康狀態和性能的重要依據。然而&#xff0c;手動一項項去檢查這些指…

【MySQL】Mysql——安裝指南(Linux)

MySQL8.0.26-Linux版安裝 1. 準備一臺Linux服務器 云服務器或者虛擬機都可以; Linux的版本為 CentOS7; 2. 下載Linux版MySQL安裝包 3. 上傳MySQL安裝包 4. 創建目錄,并解壓 mkdir mysqltar -xvf mysql-8.0.26-1.el7.x86_64.rpm-bundle.tar -C mysql5. 安裝mysql的安裝包 …