【數據結構入門】排序算法(4)歸并排序

????????

目錄

1.排序的原理

1.1 保證子數組有序

1.2 時間復雜度

2. 遞歸實現

2.1 思路

2.2 代碼

3. 非遞歸實現

3.1 思路

3.2 代碼

4.面試題

4.1 題目

4.2 思路


? ? ? ?

1.排序的原理

歸并排序是外排序所謂外排序就是說能夠對文件中的數據進行排序

①首先,將待排序數組分成兩部分,這兩部分數組一定是有序的,使用兩個指針分別初始化于這兩部分的首元素;

②需要一個臨時空間,兩個指針指向的較小的元素先放入空數組中。

③如果選擇其中一個指針的元素,那么該指針需要后移一位,如果沒選到該元素,指針不動。

④如果其中一個數組元素被取完了,那么另外一個數組的內容直接填入空數組。

????????上面的操作是建立在兩部分數組是分別有序的情況下,但是我們如何保證兩部分的數組是有序的呢?

1.1 保證子數組有序

? ? ? ? ? ? ? ? 保證當前區間是有序的,如果該區間不是有序的,那么需要將該區間分成子區間,從而保證子區間有序,如果該區間只剩一個數,那么說明這個區間是有序的。

? ? ? ? 將區間細分到只有一個數的時候,這就是遞歸的“歸”,此時返回上一層的函數棧幀,使父區間變得有序。

1.2 時間復雜度

????????這里可以將遞歸的過程當做二叉樹的生成,每一層的節點是N,二叉樹的高度是LogN,那么時間復雜度就可以計算出來是N*logN。

2. 遞歸實現

2.1 思路

①首先寫一個歸并排序的主函數,主函數需要創建一個臨時變量tmp,用于存放排序的數據;

②調用歸并排序的子函數,子函數需要傳入原數組、區間、臨時數組,子函數的目的是為了讓區間內的數組有序

③子函數:首先這是一個遞歸,需要寫遞歸退出條件,即left >= right下標不合法或者就剩一個元素,說明是有序的,就退出遞歸。

④使用遞歸分別讓左右區間分別有序。

⑤開始合并有序數組,begin指針指向的元素更小的存入tmp中。

⑥把tmp數組的元素拷貝回原數組。

2.2 代碼

? ? ? ??

#include<stdio.h>// 使區間變得有序的方法
void _merge_sort(int* arr, int left, int right, int* tmp)
{// 遞歸退出條件if (left >= right){return;}// 求mid,將[left,right]分為[left,mid][mid+1,right]兩部分int mid = (left + right) / 2;// 保證兩區域是有序的才能進行合并有序數組_merge_sort(arr, left, mid, tmp);_merge_sort(arr, mid + 1, right, tmp);// 開始合并有序數組int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;// 記錄tmp的下標int tmp_index = begin1;while (begin1 <= end1 && begin2 <= end2){// 兩個有序數組,哪個小放哪個if (arr[begin1] < arr[begin2]){tmp[tmp_index++] = arr[begin1++];}else{tmp[tmp_index++] = arr[begin2++];}}// 退出的時候是某一數組已經取值完畢了while (begin1 <= end1){tmp[tmp_index++] = arr[begin1++];}while (begin2 <= end2){tmp[tmp_index++] = arr[begin2++];}// 將tmp數組的值全部放回arr去,進行覆蓋for (int i = left; i <= right; ++i){arr[ i] = tmp[i];}
}void merge_sort(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);_merge_sort(arr, 0, n - 1, tmp);free(tmp);	
}

3. 非遞歸實現

3.1 思路

? ? ? ? 使用循環,首先每一個元素作為一個單位進行排序,接下來兩個一組為單位進行排序如下圖所示:

最后按照四個元素一組進行排序,我們只需要控制間距即可。

3.2 代碼

? ? ? ? 首先寫gap為1的時候,該如何操作,當gap為1的的時候,我們只需要將相鄰兩個元素進行合并操作,例如將10和6合并成6和10,下一次合并需要將7和1合并成1和7;以此類推。

????????我們可以將合并有序數組這個方法單獨抽象出來,然后后面直接調用此方法即可,下面的代碼就是當gap=1的時候,書寫的代碼。

// 抽象合并有序數組的函數
void merge_array(int* arr, int begin1, int end1, int begin2, int end2, int* tmp)
{int left = begin1, right = end2;// 記錄tmp的下標int tmp_index = begin1;while (begin1 <= end1 && begin2 <= end2){// 兩個有序數組,哪個小放哪個if (arr[begin1] < arr[begin2]){tmp[tmp_index++] = arr[begin1++];}else{tmp[tmp_index++] = arr[begin2++];}}// 退出的時候是某一數組已經取值完畢了while (begin1 <= end1){tmp[tmp_index++] = arr[begin1++];}while (begin2 <= end2){tmp[tmp_index++] = arr[begin2++];}// 將tmp數組的值全部放回arr去,進行覆蓋for (int i = left; i <= right; ++i){arr[ i] = tmp[i];}
}void merge_sort_non(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);int gap = 1;// 【i,i + gap - 1】和【i + gap,i + 2 * gap - 1】閉區間for (int i = 0; i < n; i+= 2*gap)// 1,2合并之后,i下一次要合并3,4,{merge_array(arr, i, i + gap - 1, i + gap, i + 2 * gap - 1, tmp);// gap=1的時候就是兩個數合并}free(tmp);
}

我們可以驗證一下,當gap=1的時候是否正確:

最后調整gap,外面增加一層循環,gap最多是n/2,每次循環gap需要變為原來的2倍,1,2,4.....

? ? ? ? 這里需要注意的是,當gap不斷變化,第二組有可能會出現越界的情況,下面需要標注第二組兩種越界情況。

void merge_sort_non(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);int gap = 1;while (gap <n){// 【i,i + gap - 1】和【i + gap,i + 2 * gap - 1】閉區間for (int i = 0; i < n; i += 2 * gap)// 1,2合并之后,i下一次要合并3,4,{// 確保不越界int begin1 = i;int end1 = i + gap - 1;int begin2 = i + gap;int end2 = i + 2 * gap - 1;// 確保不越界if (begin2 >= n) break;  // 第二個數組不存在if (end2 >= n) end2 = n - 1;  // 調整第二個數組的結束位置merge_array(arr, begin1, end1, begin2, end2, tmp);}gap *= 2;}free(tmp);
}

gap其實就是決定有多少個數進行合并,gap為1的時候,其實就是1個數和1個數進行合并。

4.面試題

4.1 題目

? ? ? ? ①文件有10億個數據,需要排序,怎么辦?假設內存中最多只能放1000w個數據

首先將數據分成100份,一份就是1000w個數據,將每一份數據兩兩歸并排序成一個文件(磁盤)

假設文件中的數據是按照換行來分割的:

首先需要將數據分成n等份,每一份的大小剛好可以存入內存里,然后對每一份進行排序,生成n份文件。

// 外排序,返回一個文件名
void file_sort(char* file)
{FILE* fout = fopen(file, "r");if (fout == NULL){printf("文件打開失敗\n");exit(-1);}int num = 0;int n = 10;// 將文件內容分成10份int arr[10];// 臨時存10個數int i = 0;char subfile[20];// 子文件名稱int id = 0; // 子文件下標while (fscanf(fout, "%d\n", &num) != EOF){if (i < n){arr[i++] = num;}else{// 滿10個了就排序merge_sort(arr, n);sprintf(subfile, "sub\\subfile_%d.txt", id++);printf("%s", subfile);// 對每一個文件進行寫FILE* fin = fopen(subfile, "w");for (int i = 0; i < n; i++){fprintf(fin, "%d\n", arr[i]);}fclose(fin);i = 0;}}fclose(fout);}int main()
{file_sort("sort.txt");}

然后對這n份文件進行合并,實現整體有序,兩兩合并,可以如下圖也可以兩兩等分合并。

思路就是,有兩個小文件file1、file2作為臨時變量,首先合并形成m_file,然后將fiel2后移一位到下一個文件,file1變成m_file,繼續合并形成新的m_file。

4.2 思路

①每n個數據為一組(這里n=10),讀到n個的時候進行排序創建一個文件,那么100個數據就會有10個文件1-10;每一個文件需要先排序再寫入。

②使用三個變量,file1、file2、m_file用于存儲文件名,file1和file2合并之后寫死文件名為12;后面將合并后的數據給file1,修改m_file的文件名為:之前的文件名+i(當前下標+1,i從2開始),使其命名能夠逐步增加。

③依次迭代1+2的數據稱為新的file1再和迭代后新的file2繼續合并,最后的結果就是10個文件的最終排序結果。

void _merge_file(const char* file1, const char* file2, const char* m_file)
{FILE* fout1 = fopen(file1, "r");if (fout1 == NULL){printf("文件打開失敗\n");exit(-1);}FILE* fout2 = fopen(file2, "r");if (fout2 == NULL){printf("文件打開失敗\n");exit(-1);}FILE* fin = fopen(m_file, "w");if (fin == NULL){printf("文件打開失敗\n");exit(-1);}int num1, num2;// 如果兩個文件都沒有讀完就繼續int ret1 = fscanf(fout1, "%d\n", &num1);int ret2 = fscanf(fout2, "%d\n", &num2);while (ret1 != EOF&& ret2 != EOF){if (num1 < num2){// nums1寫到m_filefprintf(fin, "%d\n", num1);ret1 = fscanf(fout1, "%d\n", &num1);// fiel1的指針后移}else{fprintf(fin, "%d\n", num2);ret2 = fscanf(fout2, "%d\n", &num2);// fiel1的指針后移}}// 其中一個結束,另外一個按序寫入while (ret1 != EOF){fprintf(fin, "%d\n", num1);ret1 = fscanf(fout1, "%d\n", &num1);// fiel1的指針后移}while (ret2 != EOF){fprintf(fin, "%d\n", num2);ret2 = fscanf(fout2, "%d\n", &num2);// fiel1的指針后移}fclose(fin);fclose(fout1);fclose(fout2);
}// 外排序,返回一個文件名
void file_sort(char* file)
{FILE* fout = fopen(file, "r");if (fout == NULL){printf("文件打開失敗\n");exit(-1);}int num = 0;int n = 10;// 將文件內容分成10份int arr[10];// 臨時存10個數int i = 0;char subfile[20];// 子文件名稱int id = 1; // 子文件下標while (fscanf(fout, "%d\n", &num) != EOF){if (i < n - 1) // 前n-1個數據{arr[i++] = num;}else{// 第十個arr[i] = num;// 滿10個了就排序merge_sort(arr, n);sprintf(subfile, "%d", id++);printf("%s", subfile);// 對每一個文件進行寫FILE* fin = fopen(subfile, "w");for (int i = 0; i < n; i++){fprintf(fin, "%d\n", arr[i]);}fclose(fin);i = 0;}}fclose(fout);// 讀取兩個文件char file1[100] = "1";char file2[100];char m_file[100] = "12"; // 合并的文件for (int i = 2; i <= n; i++){sprintf(file2, "%d.txt", i);// 讀取file1和file2,將合并內容放到m_file_merge_file(file1, file2, m_file);// file1變成mfilestrcpy(file1,m_file);sprintf(m_file, "%s%d", m_file,i+1); // 拼接}}int main()
{file_sort("sort.txt");}

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

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

相關文章

FLEXSPI_Init 硬件故障問題

使用官方例程發現FLEXSPI_Init會引起硬件故障&#xff0c;查閱相關帖子發現主要有兩個可能&#xff1a;1、外部閃存配置差異修改 LUT&#xff08;查找表&#xff09;命令&#xff1a;示例中擦除扇區命令為 0xD7&#xff0c;寫狀態寄存器命令為 0x01&#xff0c;需分別改為 閃存…

如何用 Rust 重寫 SQLite 數據庫(一):項目探索

要使用 Rust 重寫 SQLite 數據庫&#xff0c;我們需要實現一個簡化的關系型數據庫核心功能&#xff08;如 SQL 解析、存儲引擎、事務管理&#xff09;。以下是一個分步實踐指南&#xff0c;包含關鍵代碼示例。一、項目規劃 我們將實現一個超簡化數據庫 MiniSQL&#xff0c;支持…

JVM之堆(Heap)

一、堆的核心特性 唯一性與共享性 每個JVM實例僅有一個堆&#xff0c;所有線程共享&#xff0c;但可通過線程私有緩沖區&#xff08;TLAB&#xff09;減少多線程分配沖突。內存結構演變 JDK 7及之前&#xff1a;堆分為新生代&#xff08;Young&#xff09;、老年代&#xff08;…

單片機的RAM與ROM概念

RAM與ROM1、RAM與ROM2、 bss、data、heap、stack、text詳細講解3、詳細探討 TCM、OCRAM 和 HBNRAM 之間的區別及其具體作用。3.1、TCM&#xff08;Tightly Coupled Memory&#xff09;3.2、 OCRAM&#xff08;On Chip RAM&#xff09;3.3、HBNRAM (Hibernate RAM)3.4、總結1、R…

實驗3:事件處理(2學時)

實驗目的&#xff08;1&#xff09;熟練掌握 v-on 指令的用法&#xff0c;學會使用 v-on 指令監聽 DOM 元素的事件&#xff0c;并通過該事件觸發調用事件處理程序。&#xff08;2&#xff09;掌握v-on 指令修飾符的基本用法。實驗內容實現購物車功能的拓展&#xff08;商品數量…

商品庫存扣減方案

文章目錄1. Lua腳本 Redis&#xff08;業界首選&#xff0c;綜合最優&#xff09;2. Redis原子命令&#xff08;DECRBY 結果校驗&#xff09;3. Redis事務&#xff08;MULTI/EXEC&#xff09;4. 分布式鎖&#xff08;基于Redis實現&#xff09;5. Redisson客戶端封裝&#xf…

關于在阿里云DMS誤操作后如何恢復數據的記錄

前言 昨天因客戶員工操作錯誤&#xff0c;導致快遞單號和訂單互換。客戶員工那邊讓筆記修改數據。 于是筆者寫下如下SQL來操作&#xff0c;導致了災難性事故。 update t_order_fed_ex_record set tracking_number 884102170661, master_tracking_number 884102170661, push…

【操作系統核心知識梳理】線程(Thread)重點與易錯點全面總結

在多任務操作系統中&#xff0c;線程是比進程更輕量的執行單元&#xff0c;理解線程的特性和實現方式是掌握并發編程的基礎。本文系統梳理了線程相關的核心知識點和常見誤區&#xff0c;助你夯實操作系統基礎。一、線程的基本概念與引入目的 1.1 什么是線程&#xff1f; 線程是…

深入理解 Python 中的 `__call__` 方法

化身為可調用的對象&#xff1a;深入理解 Python 中的 __call__ 方法 引言&#xff1a;函數與對象的邊界模糊化 在 Python 中&#xff0c;我們最熟悉的概念莫過于函數&#xff08;Function&#xff09; 和對象&#xff08;Object&#xff09;。函數是可調用的&#xff08;calla…

云服務器使用代理穩定與github通信方法

使用SSH反向隧道 (SSH Reverse Tunneling) 利用SSH連接在您的本地電腦和云服務器之間建立一個反向的加密通道。 原理&#xff1a; 從本地電腦發起一個SSH命令到您的云服務器&#xff0c;這個命令會告訴云服務器&#xff1a;“請監聽您自己的某個端口&#xff08;例如&#xff1…

7.k8s四層代理service

Service的基本介紹 Cluster IP&#xff1a;每個 Service 都分配了一個Cluster IP&#xff0c;它是一個虛擬的內部IP地址&#xff0c;用于在集群內部進行訪問。這個虛擬IP是由Kubernetes自動分配的&#xff0c;并且與Service對象一一對應。 端口映射&#xff1a;Service可以映射…

Qt 工程中 UI 文件在 Makefile 中的處理

Qt 工程中 UI 文件在 Makefile 中的處理 在 Qt 工程中&#xff0c;.ui 文件&#xff08;Qt Designer 界面文件&#xff09;需要通過 uic&#xff08;用戶界面編譯器&#xff09;工具轉換為對應的頭文件。以下是幾種情況下如何處理 UI 文件&#xff1a;1. 使用 qmake 自動生成 M…

ZLMediaKit性能測試

一、環境 系統&#xff1a;虛擬機 Ubuntu22.04 64bit配置: 4核8G設置&#xff1a;ulimit -n 102400 二、安裝 依賴安裝sudo apt update sudo apt install ffmpeg sudo apt install nloadzlm服務安裝參考&#xff1a;https://blog.csdn.net/hanbo622/article/details/149064939?…

智能文檔處理業務,應該選擇大模型還是OCR專用小模型?

智能文檔處理業務中&#xff0c;最佳策略不是二選一&#xff0c;而是“大小模型協同”。用專用小模型處理高頻、標準化的核心文檔流&#xff0c;實現極致效率與成本控制&#xff1b;用大模型賦能非標、長尾文檔的靈活處理&#xff0c;加速業務創新。 OCR小模型會被大模型取代嗎…

android 如何判定底部導航欄顯示時 不是鍵盤顯示

在 Android 中判定底部導航欄是否顯示時&#xff0c;核心痛點是 區分 “導航欄的底部 Insets” 和 “軟鍵盤彈出的底部 Insets”—— 兩者都會導致 getSystemWindowInsetBottom() 返回非零值&#xff0c;直接判斷會誤將鍵盤彈出當成導航欄顯示。以下是基于 WindowInsets 類型區…

你知道服務器和電腦主機的區別嗎?

我們都知道服務器和臺式主機有著不同之處&#xff0c;但具體說出個一二三來很多人還是一頭霧水&#xff0c;也就是知其然不知其所以然&#xff0c;都是CPU主板 內存 硬盤 電源&#xff0c;撐死就差一個顯卡不同&#xff0c;但其實服務器和我們正常使用的臺式主機差距很大&#…

什么是包裝類

什么是包裝類 在Java中&#xff0c;包裝類&#xff08;Wrapper Class&#xff09;是為基本數據類型提供的對應的引用類型。Java中的基本數據類型&#xff08;如int、char、boolean等&#xff09;不是對象&#xff0c;為了在需要對象的場景中使用基本數據類型&#xff08;如集合…

用Python打造專業級老照片修復工具:讓時光倒流的數字魔法

在這個數字化時代&#xff0c;我們手中珍藏著許多泛黃、模糊、甚至有劃痕的老照片。這些照片承載著珍貴的回憶&#xff0c;但時間的侵蝕讓它們失去了往日的光彩。今天&#xff0c;我將帶您一起用Python開發一個專業級的老照片修復工具&#xff0c;讓這些珍貴的記憶重現光彩。為…

linux中查找包含xxx內容的文件

linux中怎么查找哪個文件包含xxx內容 在Linux中查找包含特定內容的文件 在Linux系統中&#xff0c;有幾種常用方法來查找包含特定內容的文件。以下是幾種最有效的方法&#xff1a;1. 使用 grep 命令&#xff08;最常用&#xff09; 基本語法&#xff1a;bash grep -r "搜索…

sklearn 加州房價數據集 fetch_california_housing 出錯 403: Forbidden 修復方案

問題 加載加州房價數據時出現 403 錯誤 HTTP Error 403: Forbidden from sklearn.datasets import fetch_california_housingcalifornia fetch_california_housing() print(california.target.shape) 解決方案 運行下述代碼&#xff0c;然后再運行上述的 fetch_california_hou…