數據結構之 【排序】(歸并排序)

目錄

1.遞歸實現歸并排序的思想及圖解

2.遞歸實現歸并排序的代碼邏輯

2.1嵌套子函數

2.2遞歸過程

2.3遞歸結束條件

2.4歸并及拷貝過程?

3.非遞歸實現歸并排序的思想及圖解

4.非遞歸實現歸并排序的代碼邏輯

4.1邊歸并邊拷貝

4.2某一gap下歸并完成才進行拷貝

5.歸并排序的時間復雜度與空間復雜度


升序為例

1.遞歸實現歸并排序的思想及圖解

兩兩數組有序,借助一個臨時數組就可將有序兩數組合并成一個新的有序數組,這就叫歸并

對于給定的待排序數組,我們運用遞歸策略,不斷地將其對半拆分,直到被拆分的子數組只有一個元素時(此時認為子數組有序),我們開始進行歸并操作,......最終使數組有序

2.遞歸實現歸并排序的代碼邏輯

2.1嵌套子函數

為了避免遞歸頻繁創造臨時數組而造成空間消耗,此時需要嵌套子函數

void _MergeSort(int* a, int left, int right, int* tmp);
void MergeSort(int* a, int n);void MergeSort(int* a, int n){int* tmp = (int*)malloc(sizeof(int) * n);if (!tmp){perror("malloc fail");return;}//對[0, n - 1]這段區間進行排序_MergeSort(a, 0, n - 1, tmp);free(tmp);tmp = NULL;
}

歸并排序的遞歸操作類似于二叉樹的后序遍歷(左子樹、右子樹、根)

因為只有當原數組的兩部分數組都有序后,才能通過歸并使整個數組有序

void _MergeSort(int* a, int left, int right, int* tmp)
{//結束條件//遞歸過程//歸并區間//歸并到臨時數組的對應位置//拷貝}

2.2遞歸過程

遞歸過程類似于后序遍歷

int midi = left + (right - left) / 2;
//[left, midi][midi + 1, right]
_MergeSort(a, left, midi, tmp);
_MergeSort(a, midi + 1, right, tmp);
//開始歸并

此時中間值midi的取法是向下取整,且具有防溢出的特性,使得 left <= midi < right

所以,終止條件 left >= right 合乎情理

標準的左右區間的劃分[left, midi][midi + 1, right]

使子區間不重疊,遞歸范圍嚴格縮小,合并時覆蓋了整個區間

當數組只有兩個數,下標從0到1并進行遞歸劃分時,midi = 0,

此時左區間[left, midi]?遞歸可以停止,右區間同理

int mid = left + (right - left + 1) / 2; // 向上取整
//[left, midi - 1][midi, right]mergeSort(arr, left, mid - 1); // 左子區間mergeSort(arr, mid, right);    // 右子區間
//開始歸并

向上取整有風險

此時區間需要做調整

當數組只有兩個數,下標從0到1并進行遞歸劃分時,midi = 1,

此時左區間如果是[left, midi]?將造成無限遞歸,所以更改為[left, midi - 1][midi, right]

2.3遞歸結束條件

根據思想,我們可以知道區間長度等于1時就停止遞歸,那么

//結束條件
if (left >= right)return;

且向下取整得到 midi 的做法使得 left 不會大于 right

2.4歸并及拷貝過程?

升序為例

遞歸返回后區間所對應的數組就有序了,此時就可以進行歸并操作

1.先確定兩有序數組的邊界? ?begin.? end.是對應數組的首元素下標、尾元素下標

2.從前往后順次比較兩有序數組,數組元素小的先放到臨時數組中,一個數組結束后,再歸并另一數組

3.圖解

//開始歸并
int begin1 = left, end1 = midi;
int begin2 = midi + 1, end2 = right;
//歸并到臨時數組的對應位置
int i = left;
while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2])tmp[i++] = a[begin1++];else    tmp[i++] = a[begin2++];
}while (begin1 <= end1)tmp[i++] = a[begin1++];while (begin2 <= end2)tmp[i++] = a[begin2++];//拷貝
memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));

歸并區間不一定從0開始,而是從 left 開始的

兩個begin下標都未到尾才繼續比較存值,否則就跳出循環

此時常規思路就是是由if else語句判斷到底哪一個begin下標未到尾然后再存值,

但上述做法直接利用while循環進行判斷,這種做法更簡潔

最后再將臨時數組的歸并內容拷貝會原數組,原數組對應位置就有序啦

通過遞歸使得數組最終都有序了

3.非遞歸實現歸并排序的思想及圖解

遞歸實現歸并排序總是先遞歸到小規模數組,再從底層向上開始歸并,即先是一個元素與一個元素進行歸并,再是兩個元素與兩個元素進行歸并.....這顯然也可以是一種雙重循環

外層循環是每組元素的個數,內存循環就是數組歸并的過程

4.非遞歸實現歸并排序的代碼邏輯

4.1邊歸并邊拷貝

(1)歸并顯然要借助臨時數組

void MergeSortNonR1(int* a, int n)
{//臨時數組int* tmp = (int*)malloc(sizeof(int) * n);if (!tmp){perror("malloc fail");return;}//歸并過程free(tmp);tmp = NULL;
}

(2)特定gap(確定的每組個數)下的歸并過程

int gap;
for (int i = 0; i < n; i += 2 * gap){//表示區間int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2*gap - 1;//修正邊界//開始歸并int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2])tmp[j++] = a[begin1++];else    tmp[j++] = a[begin2++];}while (begin1 <= end1)tmp[j++] = a[begin1++];while (begin2 <= end2)tmp[j++] = a[begin2++];//邊歸并邊拷貝memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
}

(3) 區間表示

上述圖解的gap與數組個數成倍數關系,萬一有奇數個元素,是否會越界呢?

(4)修正邊界

printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);

打印確定gap下的區間幫助我們觀察越界情況

從打印結果來看,end1、begin2、end2都存在越界的可能

而for循環中的i控制了小于n的情況,所以begin1不存在越界訪問的說法

邊歸并邊拷貝過程中當end1越界之后,begin2、end2肯定也越界了,此時第一部分沒有存入臨時數組再拷貝回原數組的必要,而當begin2越界而end1沒越界時,第一部分也不需要歸并拷貝

	//修正邊界if (end1 >= n || begin2 >= n)break;else if (end2 >= n)end2 = n - 1;

只有end2越界時,第二部分的部分需要與第一部分進行歸并拷貝,此時將end2修正為 n - 1

再次打印邊界,對比發現不合理的區間都已跳過

最后控制gap逐漸增大即可

void MergeSortNonR1(int* a, int n){int* tmp = (int*)malloc(sizeof(int) * n);if (!tmp){perror("malloc fail");return;}//每組中的個數int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap){//表示區間int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2*gap - 1;//修正邊界if (end1 >= n || begin2 >= n)break;else if (end2 >= n)end2 = n - 1;//查看邊界越界情況需要//printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);//開始歸并int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2])tmp[j++] = a[begin1++];else    tmp[j++] = a[begin2++];}while (begin1 <= end1)tmp[j++] = a[begin1++];while (begin2 <= end2)tmp[j++] = a[begin2++];//邊歸并邊拷貝memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));}//printf("\n");//查看邊界越界情況需要gap *= 2;}free(tmp);tmp = NULL;
}

4.2某一gap下歸并完成才進行拷貝

void MergeSortNonR2(int* a, int n){int* tmp = (int*)malloc(sizeof(int) * n);if (!tmp){perror("malloc fail");return;}//每組中的個數int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap){//表示區間int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//修正邊界//查看越界情況才需要printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);//開始歸并int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2])tmp[j++] = a[begin1++];else	tmp[j++] = a[begin2++];while (begin1 <= end1)tmp[j++] = a[begin1++];while (begin2 <= end2)tmp[j++] = a[begin2++];}//查看越界情況才需要printf("\n");//全部歸并完成后一次性拷貝memcpy(a, tmp, sizeof(int) * n);gap *= 2;}free(tmp);tmp = NULL;
}

打印邊界

同樣只有begin1不會越界,但是因為我們最終是將臨時數組全部拷貝到原數組中去,所以

我們要確保不進行歸并的部分也存入了臨時數組,防止臨時數組全部拷貝到原數組中去的過程中對原數據造成覆蓋

if (end1 >= n)
{end1 = n - 1;begin2 = n;end2 = n - 1;
}
else if (begin2 >= n)
{begin2 = n;end2 = n - 1;
}
else if (end2 >= n)end2 = n - 1;

end1越界要將end1修改為 n - 1,使得第一部分能夠存入臨時數組,并且將第二部分的區間改為不存在,即 begin2 > end2 ,這樣才能避免第二部分的越界存值

end1未越界,begin2越界,此時將第二部分的區間改為不存在,即 begin2 > end2 ,避免第二部分的越界存值即可

只有end2越界,此時將其修改為 n - 1 即可,這樣可以保證一二部分進行歸并存值操作

最終區間合理

5.歸并排序的時間復雜度與空間復雜度

遞歸實現歸并排序需要遞歸 logN 層,每層進行遍歷歸并,即每一層遍歷N次

所以時間復雜度為O(N*logN)

int gap = 1;
while (gap < n){for (int i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//........}gap *= 2;
}

非遞歸實現歸并排序時,外層while循環要循環 logN 次,內層for循環中需要遍歷數組中的每個元素,所以時間復雜度為O(N*logN)

遞歸實現歸并排序需要遞歸 logN 層,所以遞歸棧的空間復雜度為?O(logN)

又預先在最外層創建了臨時數組tmp,臨時數組的空間復雜度為?O(N)

由于?O(N)?的增長速度遠快于?O(logN),因此?O(N)?是主導項

所以? ?空間復雜度為 O(logN) + O(N) 約等于 O(N)??

非遞歸實現歸并排序創建了一個臨時數組tmp,空間復雜度為O(N)

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

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

相關文章

企業如何選擇適合的高防服務器?

高防服務器租用哪家好&#xff1f;這個問題困擾著許多站長&#xff0c;建立的網站經常受到各種網絡攻擊&#xff0c;雖然高防服務器有著較高的防御性能&#xff0c;十分適合經常被攻擊的行業網站&#xff0c;但是如何租到滿意的高防服務器呢&#xff01;徐州高防服務器是部署在…

告別重復勞動:Ansible 自動化運維超詳細學習路線圖

在運維的世界里&#xff0c;我們總是在與重復性任務作斗爭&#xff1a;部署同一套環境 N 次、在幾十臺服務器上修改同一個配置文件、一遍又一遍地執行相同的發布流程……這些工作不僅枯燥&#xff0c;還極易出錯。 如果你也為此感到煩惱&#xff0c;那么 Ansible 就是為你量身打…

UDS 0x29 身份驗證服務 Authentication service

背景 0x29服務的目的是為客戶端提供一種證明其身份的方法&#xff0c;在ECU端&#xff0c;有些服務或者數據因信息安全、排放或功能安全原因而受到嚴格限制。 只有身份驗證通過之后&#xff0c;才能夠允許其訪問數據和/或診斷服務。 例如&#xff0c;用于將數據下載/上傳到ECU以…

【python高階】-1- python工程和線程并發

一、項目工程守則1.pdm新建一個項目命令行終端&#xff1a;pip install pdmpdm init版本號&#xff1a;x.y.zx:兼容版本y:新增功能z:補丁版本pdm add pytest requests (添加依賴)pdm是協助管理我們的項目 2. black就是規范我們的代碼風格的&#xff1a;pdm add blackblackblack…

YOLOv8 剪枝模型加載踩坑記:解決 YAML 覆蓋剪枝結構的問題

1. 問題背景模型剪枝是實現模型輕量化、加速推理的關鍵步驟。然而&#xff0c;在 Ultralytics YOLOv8 的生態中&#xff0c;在成功剪枝后&#xff0c;進行微調&#xff08;Fine-tuning&#xff09;時會遇到一個令人困惑的現象&#xff1a;明明加載的是剪枝后的模型&#xff08;…

js的學習1

1.數組 數組方法 push()數組尾部添加unshift()數組頭部添加pop()數組尾部刪除shift()數組頭部刪除splice(起始位置&#xff0c;刪除幾個元素&#xff0c;要替換的元素)刪除指定的元素&#xff0c;改變了原數組&#xff0c;返回值是被刪除的元素indexOf()第一次查到的索引&#…

LeetCode 2563.統計公平數對的數目

給你一個下標從 0 開始、長度為 n 的整數數組 nums &#xff0c;和兩個整數 lower 和 upper &#xff0c;返回 公平數對的數目 。 如果 (i, j) 數對滿足以下情況&#xff0c;則認為它是一個 公平數對 &#xff1a; 0 < i < j < n&#xff0c;且 lower < nums[i] n…

ZABBIX配置自動發現與自動注冊,網易郵箱告警和釘釘告警

一、自動發現zabbix server 主動的去發現所有的客戶端&#xff0c;然后將客戶端的信息登記在服務端上。缺點是如果定義的網段中的主機數量多&#xff0c;zabbix server 登記耗時較久&#xff0c;且壓力會較大。1、部署準備準備三臺虛擬機192.168.80.151&#xff1b;192.168.80.…

QT(五)常用類

1. QString字符串類(掌握) QString是Qt的字符串類&#xff0c;與C的string相比&#xff0c;不再使用ASCII編碼&#xff0c;QString使用的是Unicode編碼。 QString中每個字符都是一個16位的QChar&#xff0c;而不是8位的char。 QString完全支持中文&#xff0c;但是由于不同的技…

EXCEL怎么提取表名

錯誤的方法&#xff1a;使用以下方法提取表名的時候&#xff0c;會存在1個問題&#xff0c;公式只在當前工作表生效&#xff0c;換工作表會出現表名覆蓋的情況。RIGHT(CELL("filename"),LEN(CELL("filename"))-FIND("]",CELL("filename&quo…

springboot校園外賣配送系統

目 錄 第一章 緒 論 1.1背景及意義 1.2國內外研究概況 1.3 研究的內容 第二章 關鍵技術的研究 2.1開發技術 2.2 Springboot框架介紹 2.3 Vue.js 主要功能 2.4 MVVM模式介紹 2.4 B/S體系工作原理 2.5 MySQL數據庫 第三章 系統分析 3.1 系統設計目標 3.2 系統可行性…

【智慧物聯網平臺】安裝部署教程——仙盟創夢IDE

一、部署前準備1. 環境要求基礎環境&#xff1a;JDK 1.8、MySQL 5.7/8.0、Maven 3.6、Redis&#xff08;用于緩存&#xff09;、Node.js&#xff08;用于前端構建&#xff0c;可選&#xff09;。依賴服務&#xff1a;若需對接門禁、道閘等硬件設備&#xff0c;需確保設備網絡可…

【安全漏洞】防范未然:如何有效關閉不必要的HTTP請求方法,保護你的Web應用

在構建和維護Web應用的過程中&#xff0c;安全問題總是我們最關心的話題之一。今天&#xff0c;我們要探討的是一個經常被忽視的Web漏洞——未關閉或限制不必要的HTTP請求方法。 雖然我們在日常開發中主要使用 GET 和 POST 這兩種請求方法&#xff0c;但像 PUT、DELETE、HEAD、…

嵌入式Linux裸機開發筆記8(IMX6ULL)主頻和時鐘配置實驗(1)

引言在前幾章實驗中我們都沒有涉及到 I.MX6U 的時鐘和主頻配置操作&#xff0c;全部使用的默認配置&#xff0c; 默認配置下 I.MX6U 工作頻率為 396MHz。但是 I.MX6U 系列標準的工作頻率為 528MHz&#xff0c;有些 型號甚至可以工作到 696MHz。本章學習 I.MX6U 的時鐘系統&…

設計模式(四)創建型:生成器模式詳解

設計模式&#xff08;四&#xff09;創建型&#xff1a;生成器模式詳解生成器模式&#xff08;Builder Pattern&#xff09;是 GoF 23 種設計模式中的核心創建型模式之一&#xff0c;其核心價值在于將一個復雜對象的構建過程與其表示分離&#xff0c;使得同樣的構建過程可以創建…

《Angular+Spring Boot:ERP前端采購銷售庫存協同架構解析》

基于Angular與Spring Boot構建的全棧ERP前端&#xff0c;絕非技術的簡單疊加&#xff0c;而是通過深度融合兩者特性&#xff0c;打造出兼具穩定性與靈活性的業務載體。Angular的組件化架構將復雜界面拆解為可復用的獨立單元&#xff0c;依賴注入機制則讓服務調用與數據流轉條理…

Java 排序

文章目錄排序插入排序分析希爾排序分析選擇排序分析堆排序分析冒泡排序分析快速排序霍爾法分析挖坑法找基準前后指針法題目快排的優化三數取中法非遞歸實現快排歸并排序分析非遞歸實現歸并排序海量數據的排序非比較的排序計數排序分析基數排序桶排序排序 穩定的排序&#xff1…

日本IT就職面試|儀容禮儀篇分享建議

日系企業で好印象を與える「身だしなみ」と「面接マナー」ガイドこんにちは。 日系企業への就職?転職活動をされている方にとって、「第一印象」は合否を左右する大切なポイントですよね。実は、面接の評価は入室の瞬間から始まっていると言っても過言ではありません。 今回は…

英語聽力口語詞匯-8.美食類

1.crispy,crisp adj.酥脆的&#xff0c;易碎的 2.sweet adj.甜的 比如說chocolate is so sweet and delicious 3.chewy adj.難嚼的&#xff0c;難咽的 4.oatmeal n.燕麥粉 5.pickle n.泡菜 7.stir-fry v.炒菜 8.bacon n.咸肉&#xff0c;熏肉 9.yummy adj.美味可口的 1…

力扣7:整數反轉

力扣7:整數反轉題目思路代碼題目 給你一個 32 位的有符號整數 x &#xff0c;返回將 x 中的數字部分反轉后的結果。 如果反轉后整數超過 32 位的有符號整數的范圍 [?2^31, 2^31 ? 1] &#xff0c;就返回 0。 思路 這道題我們可以分成兩部分來做&#xff0c;一是完成反轉二…