數據結構:八大排序(冒泡,堆,插入,選擇,希爾,快排,歸并,計數)詳解

目錄

一.冒泡排序

二.堆排序

三.插入排序

四.選擇排序

五.希爾排序

六.快速排序

1.Lomuto版本(前后指針法)

2.Lomuto版本的非遞歸算法

3.hoare版本(左右指針法)

4.挖坑法找分界值:

七.歸并排序

八.計數排序

九.排序算法復雜度及穩定性分析


一.冒泡排序

冒泡排序即qsort排序,其在幾大排序中屬于那種效率較低的排序方式,相同條件下,qsort排序的時間要比其他排序多幾十乃至幾千倍,但由于其在排序過程中不會輕易改變原來數據的相對位置,因此它也算是一種較為穩定的函數(關于排序函數的穩定性我會在下文帶著慢慢說明),以下是冒泡排序的具體原理與代碼:

在升序條件下,不斷遍歷左邊的數據以找其中最大者然后不斷循環放在右邊

//冒泡排序
void BubbleSort(int* arr, int n)
{int end = n;while (end){int flag = 0;for (int i = 1; i < end; ++i)//代表每一次進入循環左邊數據都會少一個{if (arr[i - 1] > arr[i]){int tem = arr[i];arr[i] = arr[i - 1];arr[i - 1] = tem;//右邊比左邊大就交換flag = 1;}}if (flag == 0){break;}--end;}
}

看懂這樣的代碼之后可以仔細想想:冒泡排序在最差的情況下(即恰好每一輪的找最大值都需要它一個不漏地遍歷其當時的所有數據)那么冒泡排序的時間復雜度為O(n^2)(即1一直累加到n-1)最好的情況下就是O(n),同時,在冒泡排序的每一趟過程中,假設有兩個連續的一樣的數據,那么冒泡排序在遍歷過程中,是不會改變這兩個數據的相對位置的,因此在不考慮時間復雜度的情況下,所以我稱冒泡排序這種排序方式是一種穩定的排序方式

二.堆排序

堆排序的具體使用我在以下文章https://blog.csdn.net/2403_87691282/article/details/145888014?spm=1001.2014.3001.5501里有過說明,所以這里就允許我稍微偷下懶展示一下現成代碼吧:

//向下調整算法
void AdjustDown(HP* hp, int parent, int n)//n是向下調整的下界范圍
{int child = parent * 2 + 1;//公式while (child < n){//建小根堆if (child + 1 < n && hp->arr[child + 1] < hp->arr[child])//child+1的目的是確保兩個孩子結點都是有意義的結點{child++;}if (hp->arr[parent] > hp->arr[child]){swap(&hp->arr[parent], &hp->arr[child]);parent = child;child = parent * 2 + 1;}else{//我最小的孩子都比父節點大,那么這個堆就是正常堆了,直接退出break;}}
}// 升序,建?堆
// 降序,建?堆
// O(N*logN)//基于數組的堆排序
void HeapSort(int* a, int n)
{// a數組直接建堆 O(N)for (int i = (n-1-1)/2; i >= 0; --i){AdjustDown(a, n, i);}// O(N*logN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

堆排序的時間復雜度也是比較好理解的:n*log(n),log(n)是向下調整算法每一層遍歷需要的時間,n則是每一次為了出堆頂數據而進行的循環調用,由于每一次的出堆頂數據都需要調用一次向下調整算法,因此堆排序的時間復雜度為n*log(n),而且需要補充的一點是由于每一次的取堆頂出堆頂,可能會導致原待排序數據中相同數據的相對位置在堆排序后出現改變,因此也可以說盡管堆排序的效率在排序里算不錯的,但其穩定性是屬于不穩定的那一批

三.插入排序

插入排序的邏輯其實也不難:

1.從第一個元素開始,假設該元素已經被排序
2.取下一個元素tem,從已排序的元素序列從后往前遍歷
3.如果該元素大于tem,則將該元素移到下一位
4.重復步驟3,直到找到已排序元素中小于等于tem的元素
5.tem插入到該元素的后面,若已排序所有元素都大于tem,則將tem插入到下標為0的位置
6.循環步驟2~5

void InsertSort(int* arr, int n)
{for (int i = 0; i < n - 1; ++i){//記錄有序序列最后一個元素的下標int end = i;//待插入的元素int tem = arr[end + 1];//單趟排while (end >= 0){//比插入的數大就向后移if (tem < arr[end]){arr[end + 1] = arr[end];end--;}//比插入的數小,跳出循環else{break;}}//tem放到比插入的數小的數的后面arr[end  + 1] = tem;//代碼執行到此位置有兩種情況://1.待插入元素找到應插入位置(break跳出循環到此)//2.待插入元素比當前有序序列中的所有元素都小(while循環結束后到此)}
}

代碼補充解釋:由于end作為記錄有序數據的最后一個數據的變量,所以在大循環for中,end的實際值是一直在改變的,因此就需要在單趟排之后隨時重置tmp的位置,而且tmp作為始終處于end后一位的變量(作為end的偵察兵,始終為了確定end是否需要與后一位交換位置而存在),在小循環while里end值每交換一次,就代表其與tmp值交換位置,因此需要在每個小循環的最后再將end值--使其回到正確的實際值,同樣還有一點需要注意的就是直接插入排序的時間復雜度為O(n^2),且由于其在遍歷過程中不會改變相同數據的相對位置,所以其排序性質是穩定的

四.選擇排序

選擇排序的思路也不算難,主要就是通過每次從待排序序列中挑一個最大再挑一個最小然后分別放在數組的兩端,依次類推:

具體代碼:

//選擇排序
void swap(int* a, int* b)
{int tem = *a;*a = *b;*b = tem;
}
void SelectSort(int* arr, int n)
{//保存參與單趟排序的第一個數和最后一個數的下標int begin = 0, end = n - 1;while (begin < end){//保存最大值的下標int maxi = begin;//保存最小值的下標int mini = begin;//找出最大值和最小值的下標for (int i = begin; i <= end; ++i){if (arr[i] < arr[mini]){mini = i;}if (arr[i] > arr[maxi]){maxi = i;}}//最小值放在序列開頭swap(&arr[mini], &arr[begin]);//最大值放在序列結尾swap(&arr[maxi], &arr[end]);++begin;--end;}
}

當理解了選擇排序后有一點需要注意的就是直接選擇排序的時間復雜度無論是最佳情況(即待排序數據已經排序完畢)還是最差情況(每一次找最大最小都要遍歷全部待排序數據)都是O(n^2),這點與冒泡排序不同,冒泡排序雖然最差情況時與直接排序相同,但在最好情況下的時間復雜度卻是O(n),主要還是因為冒泡排序如果排已經拍好的數組時,其中的小循環幾乎已經不需要進入執行了,但直接選擇排序即便是遍歷已經排好的數組也仍舊需要從頭到尾遍歷一遍,并且直接選擇在遇到相同數據時同樣是有可能會改變原始數據的相對順序的,是不穩定的

五.希爾排序

希爾排序的邏輯可能就有些復雜:其基本思想為先選定?個整數(通常是gap=n/3+1),把 待排序?件所有記錄分成各組,所有的距離相等的記錄分在同?組內,并對每?組內的記錄進行排序,然后gap=gap/3+1得到下?個整數,再將數組分成各組,進行插入排序,當gap=1時,就相當于直接插入排序,

再簡單點來說就是先將待排序列進行預排序,使待排序列接近有序,然后再對該序列進行一次插入排序,此時插入排序的時間復雜度為O(N)

由于它是在直接插入排序算法的基礎上進行改進?來的,綜合來說它的效率肯定是更高的

//希爾排序
void ShellSort(int* arr, int n)
{int gap = n;while (gap>1){//每次對gap折半操作gap = gap / 2;//單趟排序for (int i = 0; i < n - gap; ++i){int end = i;int tem = arr[end + gap];while (end >= 0){if (tem < arr[end]){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tem;}}
}

代碼方面的解釋也不算很難,希爾排序通過對原始數據不斷的分小組細化,然后對每一個小組使用直接插入排序,從而減少直接插入排序算法里循環的次數,希爾算法里對于其中小循環的解釋跟直接插入排序算法是有一樣的,都是基于end與tmp的相對位置不變,然后小的往前放,大的往后放并且隨時重置tmp(希爾里就是end+gap)使其一直處于end的前面而實現的算法,同樣,我們再說到其時間復雜度,這里希爾算法的時間復雜度由于數學理論的局限,因此:

但唯一可以確定的是,希爾排序確實在很大程度上優化了直接插入算法

六.快速排序

快速排序我認為是幾大排序里最復雜也是探究的最深的一個排序算法思想,其根本的邏輯就是通過先在原始數據任意建立一個參考點,然后通過各種方法使參考點的左邊的數據小于參考點,然后使參考點右邊的數據大于參考點

1.Lomuto版本(前后指針法)

思路:
1.選出一個keyi,一般是最左邊或是最右邊的。
2.起始時,prev指針指向序列開頭,cur指針指向prev+1
3.若cur指向的內容小于keyi,則prev先向后移動一位,然后交換prev和cur指針指向的內容,然后cur指針++;若cur指向的內容大于key,則cur指針直接++,(因此在排序過程中在沒遇到大于keyi參考值時,prev和cur都會在下一步之前重合,只有當遇到大于參考值時,cur才會多走一步從而拉開與prev的距離,而這樣拉開一步的解雇就是使prev的下一步指向值是一個比參考值大的數,因此此時交換prev和cur就可以實現數據大的往后移動)如此進行下去,直到cur到達end+1(出界)位置,此時將keyi和prev指針指向的內容交換即可。

經過一次單趟排序,最終也能使得key左邊的數據全部都小于key,key右邊的數據全部都大于key。

4.然后也還是將key的左序列和右序列再次進行這種單趟排序,如此反復操作下去,直到左右序列只有一個數據,或是左右序列不存在時,便停止操作

//Lomuto前后指針
int _QuickSort2(int* arr, int left, int right)
{int keyi = left;int prev = left, cur = prev + 1;while(cur <= right){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[prev], &arr[cur]);}++cur;}Swap(&arr[prev], &arr[keyi]);return prev;
}

2.Lomuto版本的非遞歸算法

非遞歸版本的快速排序就是在Lomuto算法的基礎上充分利用了棧的性質(這里有些具體的棧的操作我在之前有篇文章提過,具體請參考:https://blog.csdn.net/2403_87691282/article/details/145228592?spm=1001.2014.3001.5501),通過將數組的首尾元素存儲在棧里從而實現一種類似于不斷循環的二分數組的結構,而其中二分的核心即是我們最核心的代碼:Lomuto算法(找參考值)

#include<stdio.h>//定義棧的結構
typedef int STDataType;
typedef struct Stack
{STDataType* arr;int top;      //指向棧頂的位置int capacity;//棧的容量
}ST;void QuickSortNonR(int* arr, int left, int right)
{ST st;STInit(&st);StackPussh(&st, right);StackPussh(&st, left);while (!StackEmpty(&st)){//取棧頂兩次int begin = StackTop(&st);StackPop(&st);int end = StackTop(&st);StackPop(&st);//[begin,end]--找基準值int key1 = begin;int prev = begin, cur = prev + 1;//Lomuto前后指針法排序while (cur <= end){if (arr[cur] < arr[key1] && ++prev != cur){Swap(&arr[prev], &arr[cur]);}cur++;}Swap(&arr[prev], &arr[key1]);key1 = prev;//重新設定基準值以區分兩個數組//begin key1 end//左序列【begin,key1-1】  右序列【key1+1,end】//要確保有效if (key1 + 1 < end){StackPush(&st, end);StackPush(&st, key1+1);}if (begin < key1-1){StackPush(&st, key1 - 1);StackPush(&st, begin);}}//銷毀棧STDestroy(&st);
}

3.hoare版本(左右指針法)

//快速排序   hoare版本(左右指針法)
void QuickSort(int* arr, int begin, int end)
{//只有一個數或區間不存在if (begin >= end)return;int left = begin;int right = end;//選左邊為keyint keyi = begin;while (begin < end){//右邊選小   等號防止和key值相等    防止順序begin和end越界while (arr[end] >= arr[keyi] && begin < end){--end;}//左邊選大while (arr[begin] <= arr[keyi] && begin < end){++begin;}//小的換到右邊,大的換到左邊swap(&arr[begin], &arr[end]);}swap(&arr[keyi], &arr[end]);keyi = end;//[left,keyi-1]keyi[keyi+1,right]QuickSort(arr, left, keyi - 1);QuickSort(arr,keyi + 1,right);
}

在這個算法里有兩點需要額外關注,一就是遞歸時所使用的函數的參數,這個就是三路遞歸法,之所以使用這樣的方法是因為在快排的雙指針法中,可以確保遞歸的平衡,避免Lomuto雙指針法里產生的元素集中在左側或者右側而產生的遞歸不平衡,二就是代碼過程中判斷指針是否運動時的等號,之所以在相等時也讓指針持續運動,是為了使數據中相同的元素最后集中在三路里的中間一路,以避免多次重復的遞歸計算

4.挖坑法找分界值:

思路:

? ? ? ? 創建左右指針,?先從右向左找出?基準?的數據,找到后?即放?左邊坑中,當前位置變為新 的"坑",然后從左向右找出?基準?的數據,找到后?即放?右邊坑中,當前位置變為新的"坑",結 束循環后將最開始存儲的分界值放?當前的"坑"中,返回當前"坑"下標(即分界值下標)

int _QuickSort(int* a, int left, int right)
{int mid = a[left];int hole = left;int key = a[hole];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;return hole;
}

七.歸并排序

總結來說:歸并排序是一種基于分治法的高效排序算法,其核心思想是將數組逐步拆分為更小的子數組進行排序,再將有序的子數組合并為整體有序的數組

void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right){return;}int mid = (right + left) / 2;//[left,mid] [mid+1,right]_MergeSort(a, left, mid, tmp);_MergeSort(a, mid + 1, right, tmp);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = begin1;//合并兩個有序數組為?個數組 while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}for (int i = left; i <= right; i++){a[i] = tmp[i];}
}void MergeSort(int* arr, int n)
{//外部申請臨時數組空間,因此不計入空間復雜度int* tmp = (int*)malloc(sizeof(int*)*n);_MergeSort(arr, 0, n - 1, tmp);free(tmp);
}

八.計數排序


void CountSort(int* a, int n)
{int min = a[0], max = a[0];for (int i = 1; i < n; i++){if (a[i] > max)max = a[i];if (a[i] < min)min = a[i];}int range = max - min + 1;int* count = (int*)malloc(sizeof(int) * range);if (count == NULL){perror("malloc fail");return;}memset(count, 0, sizeof(int) * range);//初始化// 統計次數 for (int i = 0; i < n; i++){count[a[i] - min]++; }// 排序 int j = 0;for (int i = 0; i < range; i++){while (count[i]--){a[j++] = i + min;}}
}

九.排序算法復雜度及穩定性分析

ok阿,到這里為止,數據結構的最后一章排序也結束了,下面是整個數據結構章節的所有內容的概覽圖:

好了,那就到此為止吧

全文終

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

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

相關文章

【商城實戰(2)】商城架構設計:從底層邏輯到技術實現

【商城實戰】專欄重磅來襲&#xff01;這是一份專為開發者與電商從業者打造的超詳細指南。從項目基礎搭建&#xff0c;運用 uniapp、Element Plus、SpringBoot 搭建商城框架&#xff0c;到用戶、商品、訂單等核心模塊開發&#xff0c;再到性能優化、安全加固、多端適配&#xf…

Mac mini M4安裝nvm 和node

先要安裝Homebrew&#xff08;如果尚未安裝&#xff09;。在終端中輸入以下命令&#xff1a; /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 根據提示操作完成Homebrew的安裝。 安裝nvm。在終端中輸入以下命令&#xf…

FOC無感開環啟動算法

FOC無感開環啟動排除掉高頻注入這種直接識別當前轉子dq軸的位置直接閉環啟動&#xff0c;大部分的常規啟動方式就是三段式啟動&#xff0c;對齊-強拖-觀測器介入-觀測器誤差穩定后平滑過渡-閉環。 這里就只寫出I/F&#xff08;V/F&#xff09;啟動的角度輸出的代碼&#xff0c…

Android 自定義View 加 lifecycle 簡單使用

前言 本文是自定義view中最簡單的使用方法&#xff0c;分別進行 ‘onMeasure’、‘onDraw’、‘自定義樣式’、‘lifecycle’的簡單使用&#xff0c;了解自定義view的使用。 通過lifecycle來控制 動畫的狀態 一、onMeasure做了什么&#xff1f; 在onMeasure中獲取view 的寬和…

《挑戰你的控制力!開源小游戲“保持平衡”開發解析:用HTML+JS+CSS實現物理平衡挑戰》?

&#x1f4cc; 大家好&#xff0c;我是智界工具庫&#xff0c;致力于分享好用實用且智能的軟件以及在JAVA語言開發中遇到的問題&#xff0c;如果本篇文章對你有所幫助請幫我點個小贊小收藏吧&#xff0c;謝謝喲&#xff01;&#x1f618;&#x1f618;&#x1f618; 博主聲…

淺淺初識AI、AI大模型、AGI

前記&#xff1a;這里只是簡單了解&#xff0c;后面有時間會專門來擴展和深入。 當前&#xff0c;人工智能&#xff08;AI&#xff09;及其細分領域&#xff08;如AI算法工程師、自然語言處理NLP、通用人工智能AGI&#xff09;的就業前景呈現高速增長態勢&#xff0c;市場需求…

服務器時間同步

方法一 [rootbogon hwh-ansible]# cat time-sync.sh #!/bin/bash # NTP 服務器信息 NTP_SERVER"192.168.42.12" PASSWORD"123456" # 多個 IP 地址 HOSTS("192.168.42.8" "192.168.42.9" "192.168.42.10" "192.168.42…

Android Studio安裝與配置詳解

Android Studio安裝與配置詳解 前言 作為一名Android開發者&#xff0c;Android Studio是我們日常開發中最重要的工具。本文將詳細介紹Android Studio的安裝配置過程&#xff0c;幫助你搭建一個高效的開發環境。 一、Android Studio下載與安裝 1.1 下載Android Studio 訪問…

在PyCharm開發環境中,如何建立hello.py文件?

李升偉 整理 一、分析 首先&#xff0c;用戶可能是剛接觸PyCharm或者Python的新手&#xff0c;所以需要從打開軟件開始講起。不過用戶可能已經安裝好了PyCharm&#xff0c;但也許需要確認是否已經正確安裝。不過問題重點在創建文件&#xff0c;可能不需要深入安裝步驟。 接下…

es6常見知識點

官方文檔&#xff1a;[https://es6.ruanyifeng.com/](https://es6.ruanyifeng.com/) 一、Class 1、Class Class只是一個語法糖,其功能用es5也能實現,但是比es5更符合類的期待 定義: constructor代表構造方法,而this指向new 生成的實例 定義類方法時,可以不使用function 注…

國內外優秀AI外呼產品推薦

在數字化轉型浪潮中&#xff0c;AI外呼系統憑借其高效率、低成本、精準交互的特點&#xff0c;成為企業客戶觸達與服務的核心工具。本文基于行業實踐與技術測評&#xff0c;推薦國內外表現突出的AI外呼產品&#xff0c;重點解析國內標桿企業云蝠智能&#xff0c;并對比其他代表…

【無標題】FrmImport

文章目錄 前言一、問題描述二、解決方案三、軟件開發&#xff08;源碼&#xff09;四、項目展示五、資源鏈接 前言 我能抽象出整個世界&#xff0c;但是我不能抽象你。 想讓你成為私有常量&#xff0c;這樣外部函數就無法訪問你。 又想讓你成為全局常量&#xff0c;這樣在我的…

給定計算預算下的最佳LLM模型尺寸與預訓練數據量分配

給定計算預算下的最佳LLM模型尺寸與預訓練數據量分配 FesianXu 20250304 at Wechat Search Team 前言 如果給定了計算預算 C C C&#xff0c;如何分配LLM的模型尺寸 N N N和訓練的數據量 D D D&#xff0c;才能使得模型的效果 L L L最好呢&#xff1f;筆者在此介紹一篇經典的文…

青訓營:簡易分布式爬蟲

一、項目介紹 該項目是一個簡易分布式爬蟲系統&#xff0c;以分布式思想為基礎&#xff0c;通過多節點協作的方式&#xff0c;將大規模的網頁抓取任務分解&#xff0c;從而高效、快速地獲取網絡數據 。 項目地址&#xff1a;https://github.com/yanchengsi/distributed_crawle…

任務9:交換機基礎及配置

CSDN 原創主頁&#xff1a;不羈https://blog.csdn.net/2303_76492156?typeblog 一、交換機基礎 交換機的概念&#xff1a;交換機是一種網絡設備&#xff0c;用于連接多臺計算機或網絡設備&#xff0c;實現數據包在局域網內的快速交換。交換機基于MAC地址來轉發數據包&#x…

YOLOv8改進------------SPFF-LSKA

YOLOv8改進------------SPFF-LSKA 1、LSAK.py代碼2、添加YAML文件yolov8_SPPF_LSKA.yaml3、添加SPPF_LSKA代碼4、ultralytics/nn/modules/__init__.py注冊模塊5、ultralytics/nn/tasks.py注冊模塊6、導入yaml文件訓練 1、LSAK.py代碼 論文 代碼 LSKA.py添加到ultralytics/nn/…

[Lc(2)滑動窗口_1] 長度最小的數組 | 無重復字符的最長子串 | 最大連續1的個數 III | 將 x 減到 0 的最小操作數

目錄 1. 長度最小的字數組 題解 代碼 ?2.無重復字符的最長子串 題解 代碼 3.最大連續1的個數 III 題解 代碼 4.將 x 減到 0 的最小操作數 題解 代碼 1. 長度最小的字數組 題目鏈接&#xff1a;209.長度最小的字數組 題目分析: 給定一個含有 n 個 正整數 的數組…

安卓binder驅動內核日志調試打印開放及原理(第一節)

背景&#xff1a; 經常有學員朋友在做系統開發時候&#xff0c;有時候遇到binder相關的一些問題&#xff0c;這個時候可能就需要比較多的binder相關日志&#xff0c;但是正常情況下這些binder通訊的的內核日志都是沒有的打印的&#xff0c;因為經常binder通訊太過于頻繁&#…

docker 安裝達夢數據庫(離線)

docker安裝達夢數據庫&#xff0c;官網上已經下載不了docker版本的了&#xff0c;下面可通過百度網盤下載 通過網盤分享的文件&#xff1a;dm8_20240715_x86_rh6_rq_single.tar.zip 鏈接: https://pan.baidu.com/s/1_ejcs_bRLZpICf69mPdK2w?pwdszj9 提取碼: szj9 上傳到服務…

MWC 2025 | 紫光展銳聯合移遠通信推出全面支持R16特性的5G模組RG620UA-EU

2025年世界移動通信大會&#xff08;MWC 2025&#xff09;期間&#xff0c;紫光展銳聯合移遠通信&#xff0c;正式發布了全面支持5G R16特性的模組RG620UA-EU&#xff0c;以強大的靈活性和便捷性賦能產業。 展銳芯加持&#xff0c;關鍵性能優異 RG620UA-EU模組基于紫光展銳V62…