數據結構(17)排序(下)

一、計數排序

計數排序又稱為鴿巢原理,是對哈希直接定址法的變形應用。操作步驟如下:①統計相同元素出現的次數? ②根據統計的結果將序列回收到原來的序列中

比如,現在有一個數組{6,1,2,9,4,2,4,1,4}。該數組中,元素1出現兩次,元素2出現兩次,元素4出現三次,元素6出現一次,元素9出現一次。我們這時就可以創建一個新數組,把原數組里的數據作為新數組的下標,原數組數據出現的次數作為新數組里下標對應的值,得到的新數組如下圖所示:

這時我們定義一個變量 i 遍歷 count 數組,如果count[i] 里的數據不為0,我們就循環向arr數組中插入count[i] 次,就可以得到排好序的數組,如下圖所示:

思路很簡單,但是新創建的count數組的大小如何確定呢?

在上面的示例中,我們似乎是找到了原數組中的最大值9,然后給count數組開辟了0~9共10個空間,那么是不是這樣開辟空間大小就行呢?假如現在有一個數組{100,101,109,105,101,105},如果我們還像剛才那樣開辟0~109共110個空間,那么count數組中下標為0~99這100個空間就嚴重浪費了。所以原數組中找最大值,再讓最大值+1這個方法是錯誤的,可能會造成空間浪費。那該怎么做呢?我們還拿{100,101,109,105,101,105}這個數組為例,最大值max = 109,最小值min =100,100~109之間最多有10個數據,也就是說count數組所需的最大空間就是10,所以我們用max - min + 1就可以得到count數組的空間大小了。

注:計數排序在數據范圍集中時效率很高,但是適用范圍及場景有限!

代碼如下:

//計數排序
void CountSort(int* arr, int n)
{//找最大和最小值int min = arr[0], max = arr[0];for (int i = 1;i < n;i++){if (arr[i] < min){min = arr[i];}if (arr[i] > max){max = arr[i];}}// max - min + 1 確定count數組大小int range = max - min + 1;int* count = (int*)malloc(range * sizeof(int));if (count == NULL){perror("malloc fail");exit(1);}//初始化memset(count, 0, range * sizeof(int));for (int i = 0;i < n;i++){count[arr[i] - min]++;}//將count數組還原到原數組中,使其有序int index = 0;for (int i = 0;i < range;i++){while (count[i]--){arr[index++] = min + i;}}
}

計數排序時間復雜度為O(N + range)

穩定性:穩定

二、排序算法的穩定性分析

穩定性:假設在待排序的序列中,存在多個具有相同關鍵字的記錄,經過排序,這些記錄的相對次序保持不變。即在原序列中,r[i] == r[j],且r[i] 在 r[j] 之前,而排序后的序列,r[i] 仍在 r[j] 之前,則稱這種排序算法是穩定的。

下面給出七種排序算法的時間復雜度和穩定性:

排序方法時間復雜度穩定性
冒泡排序O(n^2)穩定
直接選擇排序O(n^2)不穩定
直接插入排序O(n^2)穩定
希爾排序O(nlogn)~O(n^2)不穩定
堆排序O(nlogn)不穩定
歸并排序O(nlogn)穩定
快速排序O(nlogn)不穩定

三、快速排序 拓展

快速排序最核心的一點在于基準值的確定。關于如何找基準值,我們在上一章講了三種方法。但是,當待排序數組中含有大量重復數據時,再使用之前的方法會讓快速排序的時間復雜度接近于O(n^2)。因此,這里分享兩種優化方法。

1、三路劃分

三路劃分,顧名思義,就是把數組劃分成三個區間。把重復的相等的數據集中在中間,左邊就是較小值,右邊就是較大值。方法如下圖所示:

根據上述的方法思路,我們來模擬實現一下該流程:

初始情況
第一步
第二步
第三步
第四步
cur > right,結束
單次循環結束狀態

單次循環劃分完畢之后,只需對左右兩塊區間遞歸即可。

void QuickSort(int* arr, int left, int right)
{if(left >= right){return;}int begin = left; int end = right;int key = arr[left];int cur = left + 1;while(cur <= right){if(arr[cur] < key){Swap(&arr[cur], &arr[left]);cur++;left++;}else if(arr[cur] > key){Swap(&arr[cur], &arr[right]);right--;}else{cur++;}}//[begin, left-1]  [left, right]  [right+1, end]QuickSort(arr, begin, left - 1);QuickSort(arr, right + 1, end);
}

2、自省排序(introsort)

introsort是introspective sort的縮寫,顧名思義,該方法的思路就是進行自我的偵測與反省,快排遞歸深度太深(sgi STL中使用的是深度為2倍排序元素數量的對數值)那就說明在這種數據序列下,選key出現了問題,性能在快速退化,那么就不要再進行快排分割遞歸了,改換為堆排序進行排序。

了解即可,不做代碼要求。

四、歸并排序 拓展

1、非遞歸版本歸并排序

//非遞歸版本歸并排序
void MergeSortNonR(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");exit(1);}int gap = 1;while (gap < n){//根據gap劃分組,兩兩一合并for (int i = 0;i < n;i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + gap + gap - 1;if (begin2 >= n){break;}if (end2 >= n){end2 = n - 1;}int index = begin1;//兩個有序序列進行合并while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] < arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2++];}}while (begin1 <= end1){tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}//導入到原數組中memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));}gap *= 2;}
}

2、文件歸并排序

2.1 外排序

外排序(External sorting)是指能夠處理極大量數據的排序算法。通常來說,外排序處理的數據不能能一次裝入內存,只能放在讀寫較慢的外存儲器(通常是硬盤)上。外排序通常采用的是一種“排序---歸并”的策略。在排序階段,先讀入能放在內存中的數據量,將其排序輸出到一個臨時文件,依次進行,將待排序數據組織為多個有序的臨時文件。然后在歸并階段將這些臨時文件組合為一個大的有序文件,即排序結果。

跟外排序對應的就是內排序,我們之間講的常見的排序都是內排序。它們的排序思想適應的是數據在內存中,支持隨機訪問。歸并排序的思想是不需要隨機訪問數據,只需要依次按序列讀取數據,所以歸并排序既是一個內排序,也是一個外排序。

2.2 思路分析

2.3 參考代碼

#include <stdio.h>
#include <stdlib.h>
#include <time.h>//創建N個隨機數,寫到文件中
void CreateNData()
{//造數據int n = 100000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if(fin == NULL){perror("fopen fail");return;}for(int i = 0;i < n;i++){int x = rand() + i;fprintf(fin, "%d\n", x);}fclose(fin);
}int compare(const void* a, const void* b)
{return (*(int*)a - *(int*)b);
}//返回實際讀到的數據個數,沒有數據就返回0
int ReadNDataSortToFile(FILE* fout, int n, const char* file1)
{FILE* fin = fopen(file1, "w");if(fin == NULL){perror("fopen fail");return -1;}int x = 0;int* arr = (int*)malloc(sizeof(int) * n);if(arr == NULL){perror("malloc fail");return -1;}//讀取n個數據,如果遇到文件結束,應該讀j個int j = 0;for(int i = 0;i < n;i++){if(fscanf(fout, "%d", &x) == EOF){break;}arr[j++] = x;}if(j == 0){return 0;}//排序qsort(arr, j, sizeof(int), compare);//寫回file1文件for(int i = 0;i < j;i++){fprintf(fin, "%d\n", arr[i]);}fclose(fin);return j;
}void MergeFile(const char* file1, const char* file2, const char* mfile)
{FILE* fout1 = fopen(file1, "r");if(fout1 == NULL){perror("fopen fail");return;}FILE* fout2 = fopen(file2, "r");if(fout2 == NULL){perror("fopen fail");return;}FILE* mfin = fopen(mfile, "r");if(mfin == NULL){perror("fopen fail");return;}int x1 = 0;int x2 = 0;int ret1 = fscanf(fout1, "%d", &x1);int ret2 = fscanf(fout2, "%d", &x2);while(ret1 != EOF && ret2 != EOF){if(x1 < x2){fprintf(mfin, "%d\n", x1);ret1 = fscanf(fout1, "%d", &x1);}else{fprintf(mfin, "%d\n", x2);ret2 = fscanf(fout2, "%d", &x2);}}while(ret1 != EOF){fprintf(mfin, "%d\n", x1);ret1 = fscanf(fout1, "%d", &x1);}while(ret2 != EOF){fprintf(mfin, "%d\n", x2);ret2 = fscanf(fout2, "%d", &x2);}
}int main()
{CreateNData();const char* file1 = "file1.txt";const char* file2 = "file2.txt";const char* mfile = "mfile.txt";FILE* fout = fopen("data.txt", "r");if(fout == NULL){perror("fopen fail");return;}ReadNDataSortToFile(fout, 100, file1);ReadNDataSortToFile(fout, 100, file2);while(1){MergeFile(file1, file2, mfile);//刪除file1和file2remove(file1);remove(file2);//重命名mfile為file1rename(mfile, file1);//當再次讀取數據,一個都讀不到,說明已經沒有數據了//已經歸并完成,歸并結果在file1if(ReadNDataSortToFile(fout, 100, file2) == 0){break;}}return 0;
}

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

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

相關文章

深度解析 Spring Boot 循環依賴:原理、源碼與解決方案

在 Spring Boot 開發中,循環依賴是一個常見且容易被忽視的技術點。當兩個或多個 Bean 相互引用時,就會形成循環依賴(如 A 依賴 B,B 依賴 A)。初學者往往會困惑:Spring 為什么能自動處理這種看似矛盾的依賴關系?本文將從原理、源碼實現到解決方案,全方位剖析 Spring Boo…

數據庫的基本操作(約束與DQL查詢)

一、約束約束是在表上強制執行的數據規則&#xff0c;用于確保數據的完整性和一致性&#xff08;1&#xff09;約束類型MySQL中支持多種約束類型&#xff1a;①主鍵約束&#xff08;PRIMARY KEY&#xff09; ②自增約束&#xff08;AUTO_INCREMENT&#xff09;③非空約束…

HP Pavilion G6 筆記本安裝Ubuntu開機后自動進入飛行模式的問題解決

問題一臺HP Pavilion G6 筆記本 &#xff0c;安裝了Ubuntu24.04版本&#xff0c;開機后&#xff0c;直接進入飛行模式&#xff0c;導致無法使用Wifi,且使用fnf10的組合鍵&#xff0c;也無法關閉飛行模式。使用fnf10鍵&#xff0c;可以看到提示顯示飛行模式&#xff0c;但無法關…

LLM:MoE原理與實現探索

文章目錄前言一、Deepseek Moe二. Moe架構1. Expert2. Gate3. MoE Module三、Auxiliary Loss總結前言 MoE&#xff08;Mixture of Experts) 已經逐漸在LLM中廣泛應用&#xff0c;其工程部署相關目前也有了越來越多的支持&#xff0c;本文主要記錄一下MoE的基本模塊構造與原理。…

基于領域事件驅動的微服務架構設計與實踐

引言&#xff1a;為什么你的微服務總是"牽一發而動全身"&#xff1f; 在復雜的業務系統中&#xff0c;你是否遇到過這樣的困境&#xff1a;修改一個訂單服務&#xff0c;卻導致支付服務異常&#xff1b;調整庫存邏輯&#xff0c;用戶服務開始報錯。這種"蝴蝶效應…

如何使用curl編程來下載文件

libcurl 是一個功能強大的跨平臺網絡傳輸庫&#xff0c;支持多種協議。 本篇來介紹libcul的C語言編程&#xff0c;實現一個文件下載的功能。 1 curl基礎介紹 1.1 核心數據結構 1.1.1 CURL句柄 CURL是libcurl 的核心句柄&#xff0c;每個請求對應一個 CURL 實例&#xff0c;…

大語言模型提示工程與應用:ChatGPT提示工程技術指南

ChatGPT提示工程 學習目標 在本課程中&#xff0c;我們將學習更多關于ChatGPT的最新提示工程技術。 相關知識點 ChatGPT提示工程 學習內容 1 ChatGPT提示工程 ChatGPT是OpenAI研發的新型對話模型&#xff0c;具備多輪對話能力。該模型通過人類反饋強化學習(RLHF)訓練&am…

能力評估:如何系統評估你的技能和經驗

能力評估&#xff1a;如何系統評估你的技能和經驗 作為一名38歲的互聯網研發老兵&#xff0c;你已經積累了豐富的經驗&#xff0c;包括技術深度、項目管理、團隊協作等。但能力評估不是一次性事件&#xff0c;而是持續過程&#xff0c;幫助你識別優勢、短板&#xff0c;并為職業…

鴻蒙開發中所有自定義裝飾器的完整案例解析--涵蓋 16 個核心裝飾器的詳細用法和實戰場景

以下是鴻蒙開發中 所有自定義裝飾器的完整案例解析 和 終極總結指南&#xff0c;涵蓋 16 個核心裝飾器的詳細用法和實戰場景&#xff1a; 一、終極總結表&#xff1a;16大裝飾器全景圖 裝飾器類別V1V2核心作用典型場景Component組件定義??創建標準組件業務UI組件ComponentV2…

【C++】哈希表的實現(unordered_map和unordered_set的底層)

文章目錄 目錄 文章目錄 前言 一、unordered_set和unordered_map介紹 二、哈希表的介紹 三、哈希沖突的解決方法 1.開放定址法 2.鏈地址法 四、兩種哈希表代碼實現 總結 前言 前面我們學習了紅黑樹&#xff0c;紅黑樹就是map和set的底層&#xff0c;本篇文章帶來的是unordered…

歐拉公式的意義

歐拉公式的意義 歐拉公式&#xff08;Euler’s Formula&#xff09;是數學中最重要的公式之一&#xff0c;它將復數、指數函數和三角函數緊密聯系在一起。其基本形式為&#xff1a; eiθcos?θisin?θ e^{i\theta} \cos \theta i \sin \theta eiθcosθisinθ 當 θπ\thet…

Linux Docker 運行SQL Server

在Linux操作系統&#xff0c;已安裝docker&#xff0c;現在以docker compose方式&#xff0c;安裝一個最新版SQL Server 2022的數據庫。 # 建個目錄&#xff08;請不要照抄&#xff0c;我的數據盤在/data&#xff0c;你可以改為/opt&#xff09; mkdir /data/sqlserver# 進入目…

C++:stack_queue(2)實現底層

文章目錄一.容器適配器1. 本質&#xff1a;2. 接口&#xff1a;3. 迭代器&#xff1a;4. 功能&#xff1a;二.deque的簡單介紹1.概念與特性2.結構與底層邏輯2.1 雙端隊列&#xff08;deque&#xff09;結構&#xff1a;2.2 deque的內部結構2.3 deque的插入與刪除操作&#xff1…

Lightroom 安卓版 + Windows 版 + Mac 版全適配,編輯管理一站式,專業攝影后期教程

軟件是啥樣的? Adobe Lightroom 這軟件&#xff0c;在安卓手機、Windows 電腦和 Mac 電腦上都能用。不管是喜歡拍照的人&#xff0c;還是專門搞攝影的&#xff0c;用它都挺方便&#xff0c;能一站式搞定照片編輯、整理和分享這些事兒。 ****下載地址 分享文件&#xff1a;【Li…

office卸載不干凈?Office356卸載不干凈,office強力卸載軟件下載

微軟官方認可的卸載工具&#xff0c;支持徹底清除Office組件及注冊表殘留。需要以管理員身份運行&#xff0c;選擇“移除Office”功能并確認操作。 Office Tool Plus安裝地址獲取 點擊這里獲取&#xff1a;Office Tool Plus 1、雙擊打開軟件 image 2、選擇左右的工具箱&…

互聯網企業慢性死亡的招聘視角分析:從崗位割裂看戰略短視

內容簡介&#xff1a; 一個獵頭和HR的簡單拒絕&#xff0c;揭示了中國互聯網企業人才觀念的深層問題。通過分析崗位過度細分現象&#xff0c;本文探討了戰略短視、內斗文化和核心競爭力缺失如何導致企業慢性死亡&#xff0c;并提出了系統性的解決方案。#互聯網企業 #人才招聘 #…

OpenBMC中phosphor-dbus-interfaces深度解析:架構、原理與應用實踐

引言 在OpenBMC生態系統中&#xff0c;phosphor-dbus-interfaces作為D-Bus接口定義的核心組件&#xff0c;扮演著系統各模塊間通信"契約"的關鍵角色。本文將基于OpenBMC源碼&#xff0c;從架構設計、實現原理到實際應用三個維度&#xff0c;全面剖析這一基礎組件的技…

駕駛場景玩手機識別準確率↑32%:陌訊動態特征融合算法實戰解析

原創聲明本文為原創技術解析文章&#xff0c;核心技術參數與架構設計參考自《陌訊技術白皮書》&#xff0c;轉載請注明出處。一、行業痛點&#xff1a;駕駛場景行為識別的現實挑戰根據交通運輸部道路運輸司發布的《駕駛員不安全行為研究報告》顯示&#xff0c;駕駛過程中使用手…

Mysql——單表最多數據量多少需要分表

目錄 一、MySql單表最多數據量多少需要分表 1.1、阿里開發公約 1.2、一個三層的B+樹,它最多可以存儲多少數據量 1.3、示例 1.3.1、示例表中一行的數據占多少字節數 1.3.2、示例表中一頁里面最多可以存多少條記錄 1.3.3、按示例表計算,一個三層的B+樹,可以放多少條100字節的數…

scikit-learn/sklearn學習|嶺回歸解讀

【1】引言 前序學習進程中&#xff0c;對用scikit-learn表達線性回歸進行了初步解讀。 線性回歸能夠將因變量yyy表達成由自變量xxx、線性系數矩陣www和截距bbb組成的線性函數式&#xff1a; y∑i1nwi?xibwTxby\sum_{i1}^{n}w_{i}\cdot x_{i}bw^T{x}byi1∑n?wi??xi?bwTxb實…