C語言-排序

常見的排序算法分為以下四種,插入排序,選擇排序,交換排序,歸并排序。

一、插入排序

(一)直接插入排序

直接插入排序,將一段數組看做被分成已排序序列和未排序序列,排序過程是從未排序序列的元素開始,該元素與已排序序列的元素從后向前掃描,找到第一個小于(或大于)該元素的已排序項,然后將該未排序項插入到已排序序列中的適當位置。

void InsertSort(int* a, int n)
{//最后一組,[0, n-2]for (int i = 0; i < n - 1; i++)//是n不是n-1!!!{int end = i;int tmp = a[end + 1];//[0,end]是有序的,從end+1開始插入while (end >= 0){if (tmp < a[end])//比tmp值大的,往后挪{a[end + 1] = a[end];end--;}else{//不在里面放是因為,如果要插入的,比所有值都小//end此時為-1,循環結束,跳不到elsebreak;}}a[end + 1] = tmp;}
}

(二)希爾排序

希爾排序是針對直接插入排序的改進

將數組元素分為gap組,即每隔gap個的元素為一組,組內排序,然后修改gap值,當gap為1時,就是直接插入排序

void ShellSort(int* a, int n)
{int gap = n;while (gap > 1){//gap = gap / 2;//除幾都可以,除3比較好,但是最后不能保證是1gap = gap / 3 + 1;//這樣就保證了最后結果一定是1for (size_t i = 0; i < n - gap; i++){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}elsebreak;}a[end + gap] = tmp;}OP()把打印注釋//printf("gap:%2d->", gap);//PrintArray(a, n);}
}

二、選擇排序

(一)選擇排序

選擇排序,遍歷整個數組,每次選擇出最大的

進行升級,每次遍歷找出最小最大的

注意下面代碼中的
?? ??? ?if (begin == maxi)
?? ??? ?? ? maxi = mini;

這個是很有必要的

比如說begin開始是最大值,和mini交換之后,接下來最大maxi要和begin進行交換,但是begin此時的值不是最大值

void SelectSort(int* a, int n)
{int begin = 0, end = n - 1;while (begin < end){int mini = begin, maxi = begin;for (int i = begin + 1; i < end; i++)//從begin+1是因為,自己跟自己比沒意義,<end也是{if (a[i] > a[maxi])maxi = i;if (a[i] < a[mini])mini = i;}Swap(&a[begin], &a[mini]);if (begin == maxi)maxi = mini;Swap(&a[end], &a[maxi]);++begin;--end;}
}

(二)堆排序

void AdjustDown(int* a, int n, int parent)
{// 先假設左孩子小int child = parent * 2 + 1;while (child < n)  // child >= n說明孩子不存在,調整到葉子了{// 找出小的那個孩子if (child + 1 < n && a[child + 1] > a[child]){++child;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
//堆排序
void HeapSort(int* a, int n)
{// 向下調整建堆 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;}
}

三、交換排序

(一)冒泡排序

兩層循環,內層單趟是找出最大的

void BubbleSort(int* a, int n)
{for (int i = 0; i < n; i++){//里面是單趟int flag = 0;for (int j = 0; j < n - 1 - i; j++){if (a[j - 1] > a[j]){Swap(&a[j - 1], &a[j]);flag = 1;}}if (flag == 0)break;}
}

(二)快速排序

1、hora方法

以第一個數為key,將比key小的元素放在基準元素的左邊,將比key大的元素放在基準元素的右邊。

遍歷結束后,key左邊所有的都比key小,右邊都比key大,key就排序完成了,然后在遞歸調用快排函數排序左右兩側。

但是這樣有個問題,假如數組一開始就有序,那么排序的深度很大(需要遞歸多次),這就可以使用三數取中來優化代碼,left,right,以及(left+right)/2這三個是數的下標,找到中間值給left進行交換。

同時,快排的遞歸調用類似于滿二叉樹,后三層遞歸次數占總次數很大,可以使用小區間優化,當該區間元素個數不夠時,使用一種排序方法排序,這里選擇直接插入排序,(直接插入排序在小規模數據上表現良好)。

int GetMidi(int* a, int left, int right)
{//取得是大小在中間的值int midi = (left + right) / 2;if (a[left] < a[midi]){if (a[midi] < a[right])return midi;else if (a[left] < a[right])//走到這里說明a[left] < a[midi], a[right] < a[midi]return right;elsereturn left;}else//a[left] >= a[midi]{if (a[midi] > a[right])return midi;else if (a[left] < a[right])return left;elsereturn right;}
}
void QuickSort(int* a, int left, int right)
{if (left >= right)return;// 小區間優化,不再遞歸分割排序,減少遞歸的次數if ((right - left + 1) < 10){InsertSort(a + left, right - left + 1);//a+left是因為InsertSort需要數組起始地址}else{// 三數取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int begin = left, end = right;while (begin < end){// 右邊找小while (begin < end && a[end] >= a[keyi]){--end;}// 左邊找大while (begin < end && a[begin] <= a[keyi]){++begin;}Swap(&a[begin], &a[end]);}//while運行完,左邊全是比key小,右邊全是比key大Swap(&a[keyi], &a[begin]);keyi = begin;// [left, keyi-1] keyi [keyi+1, right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}
}

2、前后指針方法

prev和cur

cur先向右移動,找到比key小的,然后++prev,交換prev和cur的值

遍歷一遍,prev最后位置左邊,都是比key值小的,跟Quick相似,左邊全比他小,右邊比key大

int PartSort2(int* a, int left, int right)
{三數取中//int midi = GetMidi(a, left, right);//Swap(&a[left], &a[midi]);int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right)//還在區間{//由于一開始緊挨著,后面判斷是為了防止自己交換//順序也不能反,如果a[cur] > a[keyi],就不會執行后面的,也就是說cur++但是prev不++if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}//不要寫else,cur無論什么情況都要++cur++;}Swap(&a[prev], &a[keyi]);return prev;
}void QuickSort(int* a, int left, int right)
{if (left >= right)return;// 小區間優化,不再遞歸分割排序,減少遞歸的次數if ((right - left + 1) < 10){InsertSort(a + left, right - left + 1);//a+left是因為InsertSort需要數組起始地址}else{int keyi = PartSort2(a,left,right);//修改此處即可// [left, keyi-1] keyi [keyi+1, right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}
}

3、非遞歸方法

快排非遞歸 -- 數據用棧模擬(隊列也可以,隊列先進先出,數據就不用倒著進)
?用棧類似于DFS(深度優先遍歷)
?用隊列類似于BFS(廣度優先遍歷 -- 二叉樹的廣度就是層序遍歷)
遞歸會建立棧幀,(溢出跟深度有關)
在遞歸里,棧幀存的核心數據是 -- 區間(所以非遞歸方式,棧存放區間)
循環每走一次,取棧頂區間,進行單趟排序,右左子區間入棧(右左是因為棧后進先出)
?函數調用是在棧區取空間(棧只有8M)
?數據結構的棧空間不夠去擴容是在堆(堆在32位下是2G)

void QuickSortNonR(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st))// 循環每走一次,相當于一次遞歸{int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);int keyi = PartSort2(a, begin, end);// [begin, keyi - 1] keyi [keyi + 1, end]// 先入右[keyi + 1, end]if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi + 1);}//再入左[begin, keyi - 1]if (begin < keyi - 1){STPush(&st, keyi - 1);STPush(&st, begin);}}
}

四、歸并排序

假設把數組分成兩段,如果左右區間有序,兩個指針指向左右區間第一個,一個比另一個小,就比所有的小
把小的尾插到一個tmp數組
類似二叉樹的后序,有序就歸并,無序就遞歸

1、遞歸

void _MergeSort(int* a, int* tmp, int begin, int end)//_從習慣和風格上是子函數
{//只有一個值,返回if (begin >= end)return;int midi = (begin + end) / 2;//[begin , midi] [midi + 1, end] -- 有序就可以進行歸并//不能是[begin , midi - 1] [midi, end]!!!//假設begin=0,end=1,此時midi=0,如果-1得到的分組就是[0,-1],[0,1]//[偶數,偶數+1]分組得到,[偶數,偶數],[偶數,偶數+1] -- [偶數,偶數+1]一直出現,造成棧溢出x//子問題遞歸來實現有序_MergeSort(a, tmp, begin, midi);_MergeSort(a, tmp, midi + 1, end);//歸并int begin1 = begin, end1 = midi;int begin2 = midi+1, end2 = end;int i = begin;//有一個滿足是結束的條件,while循環里要寫繼續的條件!!!while (begin1 <= end1 && begin2 <=end2){if (a[begin1] <= a[begin2])tmp[i++] = a[begin1++];elsetmp[i++] = a[begin2++];}//執行完之后,只排序了一個,再把兩個里面的都進一遍就行while(begin1 <= end1)tmp[i++] = a[begin1++];while(begin2 <= end2)tmp[i++] = a[begin2++];//數據還在tmp數組中,需要拷貝回去memcpy//不是拷整個區間,是begin到endmemcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
}
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);free(tmp);tmp = NULL;
}

2、非遞歸

?使用非遞歸 -- 一開始每兩個數歸并,然后每四個數歸并...把遞歸倒著看
?gap -- 每組歸并數據的數據個數(gap是其中一組歸并數據的個數)
?gap, gap 這樣歸并,所以數據個數*2
?[i, i + gap -1], [gap + i,i + 2*gap - 1]

void MergeSortNonR(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap)//i代表每組歸并的起始位置{//[begin1,end1][begin2,end2]//end = 起點 + 個數 - 1int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + gap * 2 - 1;printf("[%d, %d] [%d, %d]", begin1, end1, begin2, end2);//第二組都越界 -- [begin1,end1] n [begin2,end2]if (begin2 >= n){break;}//第二組begin2沒越界,end2越界,需要修正一下繼續歸并 -- [begin1,end1][begin2, (n) end2]if (end2 >= n){end2 = n - 1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2])//歸并排序,這里加個=才是穩定的,當相等的時候取前一個tmp[j++] = a[begin1++];elsetmp[j++] = a[begin2++];}while (begin1 <= end1)tmp[j++] = a[begin1++];while (begin2 <= end2)tmp[j++] = a[begin2++];memcpy(a + i, tmp + i, (end2 - i + 1) * sizeof(int));}gap *= 2;printf("\n");}free(tmp);tmp = NULL;
}

五、各個排序的時間復雜度及穩定性

穩定性指的是相同的值排完后,相對順序不變

直接插入排序

兩層循環

時間復雜度O(N^2),每次插入大約移動一半元素

最壞時間復雜度O(N^2),數組完全逆序

穩定,因為插入操作不會改變相同元素的相對順序

希爾排序

時間復雜度O(N^1.3) - O(N^2),O(N^1.3)即可

最壞時間復雜度O(N^2),取決于分組情況gap

不穩定,元素的移動可能跨越多個間隔,導致相同元素相對順序改變

選擇排序

時間復雜度O(N^2),每次插入大約移動一半元素

最壞時間復雜度O(N^2),數組完全逆序

不穩定,每次選擇最小元素并交換,會改變相同元素的相對順序

堆排序

時間復雜度O(nlogn),

最壞時間復雜度O(nlogn),

由于堆的調整操作,無論輸入數組的初始狀態如何,時間復雜度不變

不穩定,在調整堆的過程中,會交換元素,可能改變相同元素的相對順序

冒泡排序

時間復雜度O(N^2),

最壞時間復雜度O(N^2),當數組完全逆序時

穩定,相鄰元素比較和交換,相同元素不會交換位置

快速排序

時間復雜度O(nlogn),

最壞時間復雜度O(N^2),當每次選擇的基準元素都為當前子數組中的最大或最小元素時,導致劃分極不均衡,遞歸深度達到最大

不穩定,分區過程中,基準元素的交換可能導致相同元素相對順序改變

歸并排序

時間復雜度O(nlogn),

最壞時間復雜度O(nlogn),

無論數組初始狀態如何,通過不斷將數組對半劃分并合并,所以其時間復雜度不會變

穩定,在合并過程中,可以保證相同元素的相對順序不變

?? ?while (begin1 <= end1 && begin2 <=end2)
?? ?{
?? ??? ?if (a[begin1] <= a[begin2])
?? ??? ??? ?tmp[i++] = a[begin1++];
?? ??? ?else
?? ??? ??? ?tmp[i++] = a[begin2++];
?? ?}

if中的<=,=可以確保相同值時相對順序不變(遞歸非遞歸都是這個=)

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

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

相關文章

【Java筆記】LinkedList 底層結構

一、LinkedList 的全面說明 LinkedList底層實現了雙向鏈表和雙端隊列特點可以添加任意元素(元素可以重復)&#xff0c;包括null線程不安全&#xff0c;沒有實現同步 二、LinkedList 的底層操作機制 三、LinkedList的增刪改查案例 public class LinkedListCRUD { public stati…

網管平臺(基礎篇):路由器的介紹與管理

路由器簡介 路由器&#xff08;Router&#xff09;是一種計算機網絡設備&#xff0c;它的主要作用是將數據通過打包&#xff0c;并按照一定的路徑選擇算法&#xff0c;將網絡傳送至目的地。路由器能夠連接兩個或更多個網絡&#xff0c;并根據信道的情況自動選擇和設定路由&…

排序算法(2):選擇排序

問題 排序 [30, 24, 5, 58, 18, 36, 12, 42, 39] 選擇排序 選擇排序每次從待排序序列中選出最小&#xff08;或最大&#xff09;的元素&#xff0c;將其放到序列的起始位置&#xff0c;然后&#xff0c;再從剩余未排序元素中繼續尋找最小&#xff08;或最大&#xff09;元素…

Tongweb8命令行使用收集(by lqw)

文章目錄 聲明對應版本修改thanos用戶密碼部署應用到默認實例節點相關操作新增節點(一般一個服務器ip只能裝一個節點)啟動節點(需確認節點沒有運行)停止節點刪除節點節點新增應用節點查看應用節點啟動應用節點停止應用節點卸載應用(謹慎操作,卸載后應用就沒有了,建議備份后…

Artec Leo3D掃描儀在重型機械設備定制中的應用【滬敖3D】

挑戰&#xff1a;一家加拿大制造商需要有效的方法&#xff0c;為富于變化且難度較高的逆向工程&#xff0c;快速、安全、準確地完成重型機械幾何采集。 解決方案&#xff1a;Artec Leo, Artec Studio, Geomagic for SOLIDWORKS 效果&#xff1a;Artec Leo三維掃描代替過去的手動…

Nginx 限制只能白名單 uri 請求的配置

實際生產項目中&#xff0c;大多數時候我們會將后端的 http 接口通過前置 nginx 進行反向代理&#xff0c;對互聯網用戶提供服務。往往我們后端服務所能提供的接口服務是大于互聯網用戶側的實際請求的接口地址數量的&#xff08;例如后端服務一共有100個api接口&#xff0c;經過…

題海拾貝:力扣 141.環形鏈表

Hello大家好&#xff01;很高興我們又見面啦&#xff01;給生活添點passion&#xff0c;開始今天的編程之路&#xff01; 我的博客&#xff1a;<但凡. 我的專欄&#xff1a;《編程之路》、《數據結構與算法之美》、《題海拾貝》 歡迎點贊&#xff0c;關注&#xff01; 1、題…

Vite快速構建Vue教程

步驟 1: 初始化項目目錄 創建一個名為 projects 的文件夾&#xff0c;作為存放所有 Vite 項目的根目錄。這個文件夾將容納多個獨立的 Vite 項目。 步驟 2: 創建 Vite 項目 右鍵點擊 projects 文件夾并選擇“在此處打開終端”或使用您偏好的代碼編輯器&#xff08;如 VSCode&…

深入理解 CSS 文本換行: overflow-wrap 和 word-break

前言 正常情況下&#xff0c;在固定寬度的盒子中的中文會自動換行。但是&#xff0c;當遇到非常長的英文單詞或者很長的 URL 時&#xff0c;文本可能就不會自動換行&#xff0c;而會溢出所在容器。幸運的是&#xff0c;CSS 為我們提供了一些和文本換行相關的屬性&#xff1b;今…

【NumPy進階】:內存視圖、性能優化與高級線性代數

目錄 1. 深入理解 NumPy 的內存視圖與拷貝1.1 內存視圖&#xff08;View&#xff09;1.1.1 創建視圖1.1.2 視圖的特點 1.2 數組拷貝&#xff08;Copy&#xff09;1.2.1 創建拷貝1.2.2 拷貝的特點 1.3 視圖與拷貝的選擇 2. NumPy 的優化與性能提升技巧2.1 向量化操作示例&#x…

HarmonyOS 5.0應用開發——屬性動畫

【高心星出品】 文章目錄 屬性動畫animateTo屬性動畫animation屬性動畫 屬性動畫 屬性接口&#xff08;以下簡稱屬性&#xff09;包含尺寸屬性、布局屬性、位置屬性等多種類型&#xff0c;用于控制組件的行為。針對當前界面上的組件&#xff0c;其部分屬性&#xff08;如位置屬…

機器學習支持向量機(SVM)算法

一、引言 在當今數據驅動的時代&#xff0c;機器學習算法在各個領域發揮著至關重要的作用。支持向量機&#xff08;Support Vector Machine&#xff0c;SVM&#xff09;作為一種強大的監督學習算法&#xff0c;以其在分類和回歸任務中的卓越性能而備受矚目。SVM 具有良好的泛化…

介紹一款docker ui 管理工具

http://vm01:18999/main.html 管理員登陸賬號 jinghan/123456 ui啟動命令所在文件夾目錄 /work/docker/docker-ui 參考鏈接 DockerUI&#xff1a;一款功能強大的中文Docker可視化管理工具_docker ui-CSDN博客

Motrix WebExtension 使用教程

Motrix WebExtension 使用教程 項目地址:https://gitcode.com/gh_mirrors/mo/motrix-webextension 項目介紹 Motrix WebExtension 是一個瀏覽器擴展,用于與 Motrix 下載管理器集成。該擴展允許用戶通過 Motrix 下載管理器自動下載文件,而不是使用瀏覽器的原生下載管理器。…

前端(四)css選擇器、css的三大特性

css選擇器、css的三大特性 文章目錄 css選擇器、css的三大特性一、css介紹二、css選擇器2.1 基本選擇器2.2 組合選擇器2.3 交集并集選擇器2.4序列選擇器2.5屬性選擇器2.6偽類選擇器2.7偽元素選擇器 三、css三大特性3.1 繼承性3.2 層疊性3.3 優先級 一、css介紹 CSS全稱為Casca…

《探索視頻數字人:開啟未來視界的鑰匙》

一、引言 1.1視頻數字人技術的崛起 在當今科技飛速發展的時代&#xff0c;視頻數字人技術如一顆璀璨的新星&#xff0c;正逐漸成為各領域矚目的焦點。它的出現&#xff0c;猶如一場科技風暴&#xff0c;徹底改變了傳統的視頻制作方式&#xff0c;為各個行業帶來了前所未有的機…

【ETCD】[源碼閱讀]深度解析 EtcdServer 的 processInternalRaftRequestOnce 方法

在分布式系統中&#xff0c;etcd 的一致性與高效性得益于其強大的 Raft 協議模塊。而 processInternalRaftRequestOnce 是 etcd 服務器處理內部 Raft 請求的核心方法之一。本文將從源碼角度解析這個方法的邏輯流程&#xff0c;幫助讀者更好地理解 etcd 的內部實現。 方法源碼 …

免費下載 | 2024算網融合技術與產業白皮書

《2024算網融合技術與產業白皮書&#xff08;2023年&#xff09;》的核心內容概括如下&#xff1a; 算網融合發展概述&#xff1a; 各國細化算網戰略&#xff0c;指引行業應用創新升級。 算網融合市場快速增長&#xff0c;算力互聯成為投資新熱點。 算網融合產業模式逐漸成型…

基于卷積神經網絡的圖像二分類檢測模型訓練與推理實現教程 | 幽絡源

前言 對于本教程&#xff0c;說白了&#xff0c;就是期望能通過一個程序判斷一張圖片是否為某個物體&#xff0c;或者說判斷一張圖片是否為某個缺陷。因為本教程是針對二分類問題&#xff0c;因此主要處理 是 與 不是 的問題&#xff0c;比如我的模型是判斷一張圖片是否為蘋果…

安全見聞全解析

跟隨 瀧羽sec團隊學習 聲明&#xff01; 學習視頻來自B站up主 瀧羽sec 有興趣的師傅可以關注一下&#xff0c;如涉及侵權馬上刪除文章&#xff0c;筆記只是方便各位師傅的學習和探討&#xff0c;文章所提到的網站以及內容&#xff0c;只做學習交流&#xff0c;其他均與本人以及…