快速排序及歸并排序的實現與排序的穩定性

目錄

快速排序

一. 快速排序遞歸的實現方法

1. 左右指針法

步驟思路

為什么要讓end先走?

2. 挖坑法

步驟思路

3. 前后指針法

步驟思路

二. 快速排序的時間和空間復雜度

1. 時間復雜度

2. 空間復雜度

三. 快速排序的優化方法

1. 三數取中優化

2. 小區間優化

四. 使用棧來實現非遞歸快排

步驟思路

歸并排序

?編輯

一. 歸并排序的遞歸實現

步驟思路

二. 時間復雜度與空間復雜度

1. 時間復雜度

2. 空間復雜度

三. 非遞歸實現歸并排序

步驟思路

排序算法的穩定性


快速排序

一. 快速排序遞歸的實現方法

1. 左右指針法
步驟思路

(假設排升序)將數組a最左邊的下標用begin記錄下來,最右邊用end記錄下來,定義一個key為begin或end

(假設key定義為begin)end向左查找找到<a[key]的數停下begin再向右查找找到>a[key]的值停下,此時將begin指向的值與end指向的值交換,以此類推直到end的值<=begin,將此時的a[key]與begin與end相遇坐標的值交換,我們發現此時的a[key],左邊的值都比其小,右邊的值都比其大,那就說明key所指向的值在數組中已經排好位置了

如以下代碼,即完成了單趟

		int key = left;int begin = left, end = right;while (begin < end){while (a[end] >= a[key] && begin < end){end--;}while (a[begin] <= a[key] && begin < end){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[key], &a[begin]);

我們在end和begin尋找比a[key]大或小的值的時候不要忘記也要判斷循環成立的條件

既然key已經在數組排好位置,我們接下來遞歸就不需要加上key了,只需要遞歸key的左右區間即可,直到遞歸的區間左邊與右邊相等即只有一個數

完整代碼如下

void QuickSort1(int* a, int left,int right)
{if (left >= right)return;int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int key = left;int begin = left, end = right;while (begin < end){while (a[end] >= a[key] && begin < end){end--;}while (a[begin] <= a[key] && begin < end){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[key], &a[begin]);QuickSort1(a, left, begin - 1);QuickSort1(a, begin + 1, right);
}
為什么要讓end先走?

左邊做key右邊先走,可以保證相遇位置比key小
相遇場景分析

begin遇end:end先走,停下來,end停下條件是遇到比key小的值,end停下來的位置一定比key小,begin沒有找到大的遇到end停下了
end遇begin:end先走,找小,沒有找到比key更小的,直接跟begin相遇了。begin停留的位置是上一輪交換的位置(即,上一輪交換,把比key小的值,換到begin的位置了)
同樣道理讓右邊做key,左邊先走,可以保證相遇位置比key要大??

2. 挖坑法

步驟思路

(假設排升序,給數組a)將最左邊的值定義key存儲起來,最左邊的下標用bigen記錄,最右邊的下標用end記錄,定義pivot記錄為最左邊的下標,即將最左邊視為坑位

然后end向左尋找比key小的值放到pivot所指向的位置即坑位中,并將這個地方(end所找到的)視作新的坑(更新pivot的值)。

begin向右尋找比key大的值,放到坑位中,并將這個地方視作新的坑(更新pivot的值)

重復以上步驟直到end<=begin

然后將key填進pivot中,再通過遞歸,即可完成排序

由于與左右指針法類似就不寫單趟,直接上完整代碼

void QuickSort2(int* a, int left, int right)
{if (left >= right)return;int key = a[left];int begin = left, end = right;int pivot = left;while (begin < end){while (a[end] >= key && begin < end){end--;}a[pivot]=a[end];pivot = end;while (a[begin] <= key && begin < end){begin++;}a[pivot] = a[begin];pivot = begin;}a[pivot] = key;QuickSort2(a, left, pivot - 1);QuickSort2(a, pivot + 1, right);
}
3. 前后指針法

步驟思路

(假設排升序)定義key為數組最左邊的下標,并定義,prev=key與after=key+1

after在找到比key指向的值小的值時,prev++,并將after指向的值與現在的prev(即prev++后的值)交換

以此往復,直到after>數組的值

然后將prev所指向的值與key所指向的值交換

代碼如下

我們要注意,當prev++后的值==after就會發生與自身交換

完成一次后,效果依然是a[key]左區間的值比其小,右區間的值比其大

	int key = left;int prev = left, after = left + 1;while (after<=right){while (a[after] < a[key]&&++prev!=after){Swap(&a[prev], &a[after]);}after++;}Swap(&a[prev], &a[key]);

遞歸是和上面兩種方法同樣的道理

完整代碼如下

void QuickSort3(int* a,int left,int right)
{if (left >= right)return;int key = left;int prev = left, after = left + 1;while (after<=right){while (a[after] < a[key]&&++prev!=after){Swap(&a[prev], &a[after]);}after++;}Swap(&a[prev], &a[key]);QuickSort3(a, left, prev - 1);QuickSort3(a, prev + 1, right);
}

二. 快速排序的時間和空間復雜度

1. 時間復雜度

①最好情況

每次的劃分都使得劃分后的子序列長度大致相等,一般在數據已經部分有序或者隨機分布的情況下發生。此時時間復雜度為O(Nlog?N)

②最壞情況

待排序序列有序的情況下,每一次劃分的兩個區間都有一個為0,此時快速排序的時間復雜度退化為O(N2)

③平均情況

實際應用中快速排序的平均情況大概會接近于最好情況,因為待排序序列通常不是有序的,我們還可以通過三數取中來優化,減少最壞情況的可能性,所以快速排序的時間復雜度為O(Nlog?N)

2. 空間復雜度

由于需要遞歸調用,相當于求遞歸樹的深度,

①最壞情況

當數組接近有序時,遞歸深度很深,空間復雜度為O(N)

②最好情況

當數組無序時,遞歸樹基本相當與完全二叉樹,空間復雜度為O(log?N)

③平均情況

實際應用中,平均情況大概會接近最好情況,同樣可以用三數取中優化

所以快速排序空間復雜的為O(log?N)

三. 快速排序的優化方法

1. 三數取中優化

為了讓每次左右區間長度接近,我們可以使用三數取中,即最左邊最右邊與中間的值取不大也不小的一個值并返回

int GetMid(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])//上面if條件不成立可得a[right]<a[mid]return right;else//又可得 a[left] > a[right]return left;}else//a[left]>=a[mid]{if (a[mid] > a[right])return mid;else if (a[left]<  a[right])//上面if條件不成立可得a[right]>a[mid]return left;else//又可得 a[left] < a[right]return right;}}

將返回值接收并將其指向位置與最左邊的值交換,代碼如下

		if (left >= right)return;int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int key = left;
2. 小區間優化

當快速排序要排的數據很長時,越遞歸到后面區間越小遞歸的層數越多,我們可以考慮,當要遞歸區間小于10的時候用別的排序來代替,這樣就可以省去80%到90%的遞歸

代碼如下

void QuickSort1(int* a, int left,int right)
{if ( (right-left+1)<10)//小區間優化{InsertSort(a+left, right - left + 1);//a+left 有可能是后半段區間//減少遞歸層數}else{if (left >= right)return;int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int key = left;int begin = left, end = right;while (begin < end){while (a[end] >= a[key] && begin < end){end--;}while (a[begin] <= a[key] && begin < end){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[key], &a[begin]);QuickSort1(a, left, begin - 1);QuickSort1(a, begin + 1, right);}
}

四. 使用棧來實現非遞歸快排

棧的實現可以看一下我以前的博客

棧的實現詳解-CSDN博客

步驟思路

初始化棧后,將數組的最右邊與最左邊分別放入棧(即將一個區間放入棧中)

進入循環(當棧為空時循環結束),用begin和begin1接收棧頂端的值,再刪除棧的值,再用end和end1接收棧頂端的值,再刪除棧的值,使用左右指針法(挖坑法,前后指針法皆可)(用begin與end來尋找值,begin1與end1不變)進行一趟排序,

如果right1>=begin+1 就往棧里存 right1(當前排序區間的最右邊) 和 begin+1 反之不存

如果left1<=begin-1 就往棧里存? begin-1 和 left1(當前排序區間的最左邊)? 反之不存

最后不要忘記銷毀棧

代碼如下

void StackQuickSort(int* a, int left, int right)
{ST s;StackInit(&s);StackPush(&s, right);StackPush(&s, left);while (!StackEmpty(&s)){int begin = StackTop(&s);int left1 = begin;StackPop(&s);int end = StackTop(&s);int right1= end;StackPop(&s);int key = begin;//int mid = GetMid(a, begin, end);//Swap(&a[mid], &a[begin]);while (begin < end){while (a[end] >= a[key] && begin < end){end--;}while (a[begin] <= a[key] && begin < end){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[key], &a[begin]);if(right1>=begin+1){StackPush(&s,right1);StackPush(&s, begin + 1);}if(left1<=begin-1){StackPush(&s, begin - 1);StackPush(&s, left1);}}StackDestroy(&s);
}

歸并排序

一. 歸并排序的遞歸實現

步驟思路

malloc一個臨時數組進入子函數(創建子函數遞歸會更方便),進行遞歸,子函數利用分治思想一直遞歸直到left>=right 開始執行下面操作

k賦初值為當前區間最左邊begin1 , end1來記錄左數組最左邊和最右邊,定義begin2 ,end2 來記錄右數組的最左邊和最右邊,將兩個數組從頭比較,較小的賦值給臨時數組,直到有一方賦完值,再將沒賦完值的數組給臨時數組賦值。最后給要排序數組left到right賦值為臨時數組left到right

代碼如下

//遞歸
void _MergeSort(int* a,int* tmp, int left, int right)
{if(left>=right){return;}int mid = (left + right) / 2;//如果[left,mid][mid+1,right]有序就可以歸并了_MergeSort(a,tmp, left, mid);_MergeSort(a,tmp, mid + 1, right);int begin1 = left;int end1 = mid;int begin2 = mid + 1;int end2 = right;int k=left;while (begin1 <= end1&&begin2<=end2){if(a[begin1]<a[begin2]){tmp[k++] = a[begin1++];}else{tmp[k++] = a[begin2++];}}while (begin1 <= end1){tmp[k++] = a[begin1++];}while (begin2 <= end2){ tmp[k++] = a[begin2++];}for (int i = left; i <= right; i++){a[i] = tmp[i];}}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}//_MergeSort(a, tmp, 0, n - 1);_MergeSort2(a,tmp,  n);free(tmp); tmp = NULL;
}

二. 時間復雜度與空間復雜度

1. 時間復雜度

歸并排序的時間復雜度是穩定的,不受輸入數組的初始順序影響

?將數組分成兩個子數組的時間復雜度為O(1),遞歸對子數組進行排序,假設每個子數組長度為n

則兩個子數組排序的總時間復雜度為O(NlogN),將兩個有序數組合并為一個有序數組時間復雜度為O(N),所以歸并排序時間復雜度為O(NlogN)

2. 空間復雜度

調用棧所需要的額外空間為O(logN),因為我們需要一個額外數組來存儲數據所以又額外消耗O(N)的空間,我們將較小的O(logN)忽略可以得到歸并排序的空間復雜度為O(N)

三. 非遞歸實現歸并排序

步驟思路

開辟動態空間后定義一個數gap=1來控制區間(gap相當于每組數據個數),(每一次gap*2,使每次區間擴大)gap<數組長度

設計一個for循環i+=gap*=2

每次分兩組[i][i+gap-1]和[i+gap][i+2*gap-1]? (i每次+=正好跳過這些數據)

將兩個區間的值比較放入新開辟的數組,再拷貝到原數組

代碼如下

//非遞歸
void _MergeSort2(int* a,int* tmp,int n)
{int gap = 1;while(gap<n){for (int i = 0; i < n; i += 2 * gap){int begin1 = i;int end1 = i + gap - 1;;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int k = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[k++] = a[begin1++];}else{tmp[k++] = a[begin2++];}}while (begin1 <= end1){tmp[k++] = a[begin1++];}while (begin2 <= end2){tmp[k++] = a[begin2++];}//memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));for (int j = i; j < k; j++){a[j] = tmp[j];}}gap *= 2;}
}

但是我們發現,這樣如果會發生越界的現象

一共三種可能

1. [begin1,end1][begin2,end2] ?end2越界
2. [begin1,end1][begin2,end2] ?begin2,end2越界
3. [begin1,end1][begin2,end2] ?end1,begin2,end2越界

?第2,3種我們可以直接不遞歸了,因為后面區間的不存在前面區間的在上一次已經遞歸好了,

第一種呢我們需要把區間(即end)給修正一下

修正代碼如下

//非遞歸
void _MergeSort2(int* a,int* tmp,int n)
{int gap = 1;while(gap<n){for (int i = 0; i < n; i += 2 * gap){int begin1 = i;int end1 = i + gap - 1;;int begin2 = i + gap;int end2 = i + 2 * gap - 1;int k = i;if (begin2 >= n)//第二種情況,第二組不存在,不需要歸并break;if (end2 >= n)//第一種情況,需要修正一下end2 = n - 1;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[k++] = a[begin1++];}else{tmp[k++] = a[begin2++];}}while (begin1 <= end1){tmp[k++] = a[begin1++];}while (begin2 <= end2){tmp[k++] = a[begin2++];}//memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));for (int j = i; j < k; j++){a[j] = tmp[j];}}gap *= 2;}
}

排序算法的穩定性

假定在待排序的記錄序列中,存在多個具有相同關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變

原序列中 r[i]=r[j],且r[i]在r[j]之前而在排序后的序列中r[i]仍在r[j]前,則稱這種排序算法是穩定的,否則是不穩定的

冒泡選擇穩定
選擇排序不穩定***只會考慮自身,假如找到最小值1下標為3,將其與下標為0(假設此處為6)處交換若下標為1處也是6,就改變了
直接插入排序穩定
希爾排序不穩定(分組)預排序時相同的值可能分到不同的組
堆排序不穩定建堆時可能就亂了
歸并排序穩定當兩個數相等,讓第一個下來就是穩定的(可以控制)
快速排序不穩定end先找到 j 和begin交換了,在找到 i 和bigin交換,顯然改變了

這篇文章就到這里了,感謝大家閱讀

(?′?‵?)I L????????

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

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

相關文章

實驗豐富、原創改進!|多策略改進蜣螂優化算法(MATLAB)

本文內容來源于本人公眾號&#xff1a;KAU的云實驗臺&#xff0c;更新內容&#xff1a;智能優化算法及其改進應用。 本文核心內容&#xff1a; 新穎的多策略改進蜣螂優化算法 對比算法包括&#xff1a;高引用/新發布/經典/其他DBO變體&#xff08;共11種&#xff09; 實驗設計…

用c語言寫一個貪吃蛇游戲

貪吃蛇游戲通常涉及到終端圖形編程和簡單的游戲邏輯。以下是一個基本的實現示例&#xff0c;包括貪吃蛇的移動、食物生成、碰撞檢測等功能。 1. 貪吃蛇游戲的基本結構 貪吃蛇游戲可以分為以下幾個部分&#xff1a; 游戲地圖和終端繪制&#xff1a;使用二維數組表示游戲地圖&am…

SpringBoot結合ip2region實現博客評論顯示IP屬地

你好呀&#xff0c;我是小鄒。 在現代的Web應用中&#xff0c;特別是博客和論壇類網站&#xff0c;為用戶提供地理定位服務&#xff08;如顯示用戶所在地理位置&#xff09;可以極大地增強用戶體驗。本文將詳細探討如何使用Java和相關技術棧來實現在博客評論中顯示用戶的地址信…

Java實驗3

實驗內容 學生信息管理系統 學生成績表Student(Sno 字符串&#xff0c;長度9, Sname 字符串&#xff0c;長度10, Class 字符串&#xff0c;長度10, Age 整型, Sex 字符串&#xff0c;長度2) 實現如下功能&#xff1a; A&#xff0e;輸入若干個學生的信息到Student表&#x…

初學Python必須知道的14個強大單行代碼

引言&#xff1a;Python的魅力與單行代碼的重要性 Python以其簡潔明了的語法、豐富的內置函數和強大的第三方庫深受廣大開發者喜愛。尤其對于編程小白來說&#xff0c;學習Python就像打開了一扇通向編程世界的大門。而單行代碼&#xff0c;作為Python魅力的一部分&#xff0c;…

【NetTopologySuite類庫】合并所有幾何的包圍盒AABB

流程示意圖 示例代碼 using GeoAPI.Geometries; using Microsoft.VisualStudio.TestTools.UnitTesting; using NetTopologySuite.Geometries; using NetTopologySuite.IO; using System.Collections.Generic; using System.Linq;namespace Test472 {[TestClass]public class T…

深度解析:電商訂單API及其技術實現

隨著電子商務的發展&#xff0c;實體企業開拓電商渠道的越來越多&#xff0c;原有的管理系統都需要增加電商業務管理功能&#xff0c;其中&#xff0c;對電商訂單的管理是每一個電商商家都需要的功能&#xff0c;所以對于開發者來說&#xff0c;了解電商API是什么是非常重要的&…

第100+16步 ChatGPT學習:R實現Xgboost分類

基于R 4.2.2版本演示 一、寫在前面 有不少大佬問做機器學習分類能不能用R語言&#xff0c;不想學Python咯。 答曰&#xff1a;可&#xff01;用GPT或者Kimi轉一下就得了唄。 加上最近也沒啥內容寫了&#xff0c;就幫各位搬運一下吧。 二、R代碼實現Xgboost分類 &#xff08…

LeetCode題練習與總結:比較版本號--165

一、題目描述 給你兩個 版本號字符串 version1 和 version2 &#xff0c;請你比較它們。版本號由被點 . 分開的修訂號組成。修訂號的值 是它 轉換為整數 并忽略前導零。 比較版本號時&#xff0c;請按 從左到右的順序 依次比較它們的修訂號。如果其中一個版本字符串的修訂號較…

C++動態內存的管理

今天來分享C動態內存管理相關知識&#xff0c;閑言勿談&#xff0c;直接上干貨。 1. 動態內存的開辟和銷毀(new和delete) (1)前置知識&#xff1a;我們知道c語言有malloc和calloc和realloc三個函數可以進行動態的開辟內存&#xff0c;那么它們有什么區別呢&#xff1f;首先是…

MPS 后端

本文來自&#xff1a; https://pytorch.org/docs/stable/notes/mps.html https://pytorch.ac.cn/docs/stable/notes/mps.html MPS 后端 mps 設備支持 在使用 Metal 編程框架的 MacOS 設備上&#xff0c;進行高性能 GPU 訓練。 它引入了新的設備&#xff0c;將機器學習計算圖和…

【C語言】條件運算符詳解 - 《 A ? B : C 》

目錄 C語言條件運算符詳解1. 條件運算符的語法和使用示例 1&#xff1a;基本用法輸出 2. 嵌套條件運算符示例 2&#xff1a;嵌套條件運算符輸出 3. 條件運算符與 if-else 語句的比較示例 3&#xff1a;使用 if-else 語句示例 4&#xff1a;使用條件運算符 4. 條件運算符的實際應…

PLC_博圖系列?基本指令”TONR:時間累加器“

PLC_博圖系列?基本指令”TONR&#xff1a;時間累加器“ 文章目錄 PLC_博圖系列?基本指令”TONR&#xff1a;時間累加器“背景介紹TONR&#xff1a; 時間累加器說明參數脈沖時序圖示例 關鍵字&#xff1a; PLC、 西門子、 博圖、 Siemens 、 TONR 背景介紹 這是一篇關于P…

ElasticSearch學習之路

前言 為什么學ElasticSearch&#xff1f; 數據一般有如下三種類型&#xff1a; 結構化數據&#xff0c;如&#xff1a;MySQL的表&#xff0c;一般通過索引提高查詢效率非結構化數據&#xff0c;如&#xff1a;圖片、音頻等不能用表結構表示的數據&#xff0c;一般保存到mong…

Linux C++ 054-設計模式之外觀模式

Linux C 054-設計模式之外觀模式 本節關鍵字&#xff1a;Linux、C、設計模式、外觀模式 相關庫函數&#xff1a; 概念 外觀模式&#xff08;Facade&#xff09;&#xff0c;亦稱“過程模式”。主張按照描述和判斷資料來評價課程&#xff0c;關鍵的活動是在課程實施的全過程中…

昇思25天學習打卡營第24天|基于MindSpore的Diffusion擴散模型

Diffusion擴散模型 本文基于Hugging Face&#xff1a;The Annotated Diffusion Model一文翻譯遷移而來&#xff0c;同時參考了由淺入深了解Diffusion Model一文。 關于擴散模型&#xff08;Diffusion Models&#xff09;有很多種理解&#xff0c;本文的介紹是基于denoising di…

基礎動態規劃題目基礎動態規劃題目

目錄 題目1&#xff1a; P1216 [USACO1.5] [IOI1994]數字三角形 Number Triangles 代碼示例&#xff1a; 題目2&#xff1a; Common Subsequence 代碼示例 題目3 &#xff1a;最長上升子序列 最長不下降子序列 最長上升子序列oj答案 題目1&#xff1a; P1216 [USACO1.5]…

SQL面試題練習 —— 查詢每個用戶最大連續登錄天數

目錄 1 題目2 建表語句3 題解 1 題目 查詢每個用戶最大連續登錄天數 樣例數據如下 login_log&#xff1a; 2 建表語句 --建表語句 create table if not exists login_log (user_id int comment 用戶id,login_time date comment 登錄時間 ); --數據插入 INSERT overwrit…

Matlab進階繪圖第63期—帶標記線的三維填充折線圖

三維填充折線圖是在三維折線圖的基礎上&#xff0c;對其與XOY平面之間的部分進行顏色填充&#xff0c;從而能夠更好地刻畫細節變化。 而帶標記線的三維填充折線圖是在其基礎上&#xff0c;添加X相同的一條或多條標記線&#xff0c;以用于進一步討論分析。 由于Matlab中未收錄…

飛睿智能UWB Tag藍牙防丟器標簽,寵物安全新升級,5cm精準定位測距不迷路

寵物早已成為許多家庭不可或缺的一員&#xff0c;它們用無條件的愛溫暖著我們的心房&#xff0c;陪伴我們度過每一個平凡而溫馨的日子。然而&#xff0c;隨著寵物活動范圍的擴大和外界環境的復雜多變&#xff0c;寵物走失的風險也隨之增加。每一次出門遛彎&#xff0c;都像是心…