數據結構——八大排序算法

排序在生活中應用很多,對數據排序有按成績,商品價格,評論數量等標準來排序。

數據結構中有八大排序,插入、選擇、快速、歸并四類排序。

目錄

插入排序

直接插入排序

希爾排序?

?選擇排序

?堆排序

冒泡排序?

快速排序?

hoare版本快排

挖坑法

前后指針法?

歸并排序?


插入排序

什么是插入排序呢?插入排序就是把一個待排的值插入到已經有序的序列中,直到所有待排的值全部插入到序列中,得到一個新的有序序列。

插入排序有直接插入和希爾排序

直接插入排序

當插入到 i 個數據時,前i個數據已經有序,排第i個數據需要與前 i 個數據依次比較,將第 i 個數據放到合適的位置,根據下面的動圖可以幫助更好的理解

?代碼實現:

void InsertSort(int* a, int n)
{for (int i = 1; i < n; i++){int end = i-1;int tmp = a[i];while (end >= 0){if (tmp < a[end]){a[end+1] = a[end];end -= 1;}else{break;}}a[end+1] = tmp;}
}

tmp來存儲需要排序的值,end為需要排序值的前一個位置,tmp從后往前依次比較,當tmp小于end位置的值,就將這個值往后挪動,end往前移動一步?,直到tmp大于或者end不滿足進入循環的條件就把end+1下標的值改為tmp,就把前i個數據排好序了。

時間復雜度:最好情況:O(N) (數據全部有序)

? ? ? ? ? ? ? ? ? ? ? 平均情況:O(N^2)

? ? ? ? ? ? ? ? ? ? ?最壞情況:O(N^2)(數據與我們要排的順序逆序)

空間復雜度:O(1)

穩定性:穩定(判斷穩定性的方法,在這組數據中有兩個相同的數據,在把這組數據排好序后,在未排好時在后的數據不會在排好序后跑到未排好時在前的數據的前面

在插入排序中如果有兩個數據相同,在排序過程中,相同不會挪動該數據。

希爾排序?

相比與直接插入排序希爾排序多了一個預排序,因為在直接插入排序中,這組數據越有序效率就越快,所以在希爾排序中就先對這組數據預排序,再使用直接插入排序。

那么怎么樣進行預排序呢?如下

?上圖是gap=3的單趟排序。

距離為gap的為一組,按以上數據就可以分為三組,就這三組排序就可以達到預排序的效果,gap越大就分的組就越多,當gap=1時就是直接插入排序,所以我們可以先讓gap為一個比較大的值,為數據個數的二分之一,一直減小直到為1,就排好序了。

代碼展示:

void ShellSort(int* a, int n)
{int gap = n;while (gap>1){gap = gap / 2;//gap == 1 就是插入排序//gap > 1 預排序for (int j = 0; j < gap; j++){for (int i = gap + j; i < n; i += gap){int end = i - gap;int tmp = a[i];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}}
}

?為什么這樣效率會更快呢?,gap = 2^2^2^2^2……2^2,循環次數為logN,內循環為N,所以希爾排序的時間復雜度為NlogN(底為2)。

時間復雜度:最好情況:O(N^1.3)

? ? ? ? ? ? ? ? ? ? ?平均情況:O(NlogN~N^2)

? ? ? ? ? ? ? ? ? ? ? 最壞情況:O(N^2)

空間復雜度:O(1)

穩定性:不穩定(因為在預排序時可能會把兩個相同的數的相對前后位置改變)

?選擇排序

選擇排序思想:每次選擇待排序數據中最小(最大)的數據,存放在待排序列起始位置,直到全部數據排完。

?代碼實現:

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void SelectSort(int* a, int n)
{int left = 0, right = n - 1;while (left < right){int maxi = left, mini = left;for (int i = left + 1; i <= right; i++){if (a[i] < a[mini]){mini = i;}if (a[i] > a[maxi]){maxi = i;}}Swap(&a[left], &a[mini]);if (left == maxi || right == mini){maxi = mini;}Swap(&a[right], &a[maxi]);left++;right--;}
}

這里我們思想是和選擇一樣的,只不過我們使用雙指針從待排序列的兩邊開始,在序列中找最大和最小值,最小值放到待排的序列起始位置,最大的值放到待排序列的最后一個位置,當兩個指針相等時就結束循環,這里需要注意的是在最小值交換到待排序列的位置時,left為待排序列的起始位置,right為最后一個位置,當left等于最大值的下標或者right等于最小值的下標時,需要將最小值的下標給最大值的下標。例如排序3、2、1、0。

這里進行了第一次交換,如果沒有判斷的話直接進入第二次交換就回到第一次交換之前, 這兩個數據就沒有發生變換,導致排序失敗。

時間復雜度:最好情況:O(N^2)

? ? ? ? ? ? ? ? ? ? ? 平均情況:O(N^2)

? ? ? ? ? ? ? ? ? ? ? 最壞情況:O(N^2)

空間復雜度:O(1)

穩定性:不穩定

示例:

?堆排序

堆排序是利用堆積樹來這種數據結構所設計的一種算法,通過堆來選數據,排升序要建大堆(每一個子樹都滿足雙親大于兩個孩子),排降序建小堆(每一個子樹都滿足雙親小于兩個孩子)。

在我之前的文章有堆更詳細的講解,更好幫住理解堆排序:https://blog.csdn.net/lrhhappy/article/details/147029631?spm=1001.2014.3001.5502

代碼展示:

void AdjustDwon(int* a, int n, int parent)
{int child = parent * 2 + 1;while ((parent*2+1) < n){if ((child + 1) < n && a[child] < a[child + 1]){child++;}if (a[parent] < a[child]){Swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapSort(int* a, int n)
{//建堆for (int i = (n-2)/2; i>=0; i--){AdjustDwon(a, n, i);}printArr(a, n);int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDwon(a, end, 0);end--;}printArr(a, n);
}

時間復雜度 :最好情況:O(NlongN)

? ? ? ? ? ? ? ? ? ? ? ?平均情況:O(NlongN)

? ? ? ? ? ? ? ? ? ? ? ?最壞情況:O(NlongN)

空間復雜度:O(1)

穩定性:不穩定? (想想示例)

冒泡排序?

冒泡排序在是在初學是最早接觸的排序,但是實際應用不怎么用,相比其他排序效率太低了。

冒泡思想:將鍵值較大的記錄向序列的尾部移動,鍵值較小的記錄向序列的前部移動。每次從序列起始位置開始與下一個位置比較,如果該位置大于下一個位置的值,就進行交換,就把最大的值換到序列的最后一個,循環往復,就可以排完全部數據。?

代碼展示:

//冒泡排序
void BubbleSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){for (int j = 0; j <n-i-1; j++){if (a[j] > a[j + 1]){Swap(&a[j], &a[j + 1]);}}}
}

時間復雜度:O(n^2) (不管要排序的序列是否有序,都需要進行比較)

空間復雜度:O(1)

穩定性:穩定(相等不會交換)?

快速排序?

hoare版本快排

hoare快排思想:任取待排序元素序列中的某元素作為基準值,按照該排序碼將待排序集合分割成兩子序列,左子序列中所有元素均小于基準值,右子序列中所有元素均大于基準值,然后最左右子序列重復該過程,直到所有元素都排列在相應位置上為止。

代碼展示:

//三數取中
int GetMidi(int* a,int left, int right)
{int mid = (right - left) / 2;if (a[left] > a[mid]){if (a[right] > a[left]){return left;}else if (a[mid] > a[right]){return mid;}else{return right;}}else // a[left] < a[mid]{if(a[right]>a[mid]){return mid;}else if (a[left] > a[right]){return left;}else{return right;}}
}
//快排
void PartSort1(int* a, int left, int right)
{if (left >= right)return;int begin = left, end = right;//隨機數法/*int reti = left + (rand() % (right - left));Swap(&a[left], &a[reti]);*///三數取中法int reti = GetMidi(a,left,right);Swap(&a[left], &a[reti]);int keyi = left;while (left < right){//找小while (left < right && a[right] >= a[keyi])right--;//找大while (left < right && a[left] <= a[keyi])left++;Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);keyi = left;PartSort1(a, begin, keyi - 1);PartSort1(a, keyi + 1, end);
}

根據上面的圖可以得出大概的邏輯,left為序列的起始位置下標,right為最后一個數據的下標,選起始位置下標為keyi,right目標是找小于或者等于keyi位置的值,left的目標是找大于或者等于keyi位置的值,right先走,left再走,兩個對應的值進行交換,當left等于right時與keyi位置的值交換,就完成了單趟的排序,以keyi位置分為兩個區間[left,keyi] keyi [keyi+1,right],接著排這兩個區間,排完就排四個區間……,從這里就可以知道使用遞歸會更好

注意:left和right在走的時候需要滿足left小于right。

?這里用到三數取中,以及隨機取數,這里更推薦三數取中,這兩個是對快排的優化,使keyi選擇這個序列中更中間的數,更可以提高該算法的效率。

挖坑法

挖坑法key保存的是起始位置(看圖中保存的是起始位置的值,因為創建的key是局部變量),當左邊為key值,就需要先讓右邊先走,反之左邊先走,定義一個變量key來存放起始位置,right先走,目標是找比key對應值小的值,再把right對應值給left對應值,接著left走,目標是找比key對應值大的值,找到之后把left對應值給right對應值,直到left和right相等,相等位置的值被賦值key對應值,這只是單趟的排序,這里使用遞歸,與hoare一樣分兩個、四個……區間,直到left大于或者等于right就返回。

void PartSort2(int* a, int left, int right)
{if (left >= right)return;int begin = left, end = right;//隨機數法/*int reti = left + (rand() % (right - left));Swap(&a[left], &a[reti]);*///三數取中法/*int reti = GetMidi(a, left, right);Swap(&a[left], &a[reti]);*/int key = a[left];while (left < right){//找小while (left < right && a[right] >= key)right--;a[left] = a[right];//找大while (left < right && a[left] <= key)left++;a[right] = a[left];}a[left] = key;PartSort1(a, begin, left - 1);PartSort1(a, left + 1, end);
}

當完成一趟排序后,key對應值就到了最終排完后的位置,所以只需要對key對應值前后區間排序就可以,創建兩個變量begin和end來存儲當前序列的起始位置和末尾,遞歸時用得上。

前后指針法?

思想:創建兩個變量prev和cur,prev在起始位置,cur在prev后一個位置,先對cur對應值和key對應值比較,如果小于key對應值就prev++,然后再交換prev和cur的值,cur++,如果大于key對應值,只cur++。

void PartSort3(int* a, int left, int right)
{if (left >= right)return;int prev = left, cur = left + 1;int keyi = left;while (cur <= right){if (a[cur] < a[keyi]){prev++;Swap(&a[cur],&a[prev]);}cur++;}Swap(&a[keyi], &a[prev]);//優化前/*PartSort3(a, left, prev - 1);PartSort3(a, prev + 1, right);*///優化后if ((right - left + 1) > 10)//在數據量大的時候可以省去很多次遞歸,分割成很多小區間時使用插入排序。{PartSort3(a, left, prev - 1);PartSort3(a, prev + 1, right);}else{//插入排序InsertSort(a + left, right - left + 1);}
}

?創建兩個變量prev和cur,prev在起始位置,cur在prev后一個位置,根據上面的思想寫出代碼,cur需要小于或者等于right,完成單趟就遞歸,這里進行了優化,因為遞歸就像二叉樹的結構,在最后一層遞歸數量占一半了,我們可以在遞歸只剩最后幾層時使用直接插入排序就可以增加算法效率,當區間小于十就開始使用插入排序。

時間復雜度:最好情況:O(NlogN)

? ? ? ? ? ? ? ? ? ? ?平均情況:O(NlogN)

? ? ? ? ? ? ? ? ? ? ?最壞情況:O(N^2)

空間復雜度:O(logN~N)

穩定性:不穩定

示例:

歸并排序?

基本思想:歸并排序是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法的一個非常典型的應用。將已有序的子序列合并,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合并成一個有序表,稱為二路歸并。

?

從上面的圖可以更好的理解歸并排序,將序列逐漸分為一個,再兩個兩個對比,這時就需要一個新的數組來存放排序后的數據,小的數據先放進新的數組,則每個序列對比時都是有序的,在把兩個有序的序列合并成一個新的有序序列,直到將全部的小序列合并成一個有序序列就完成了歸并排序?。

代碼展示:

//歸并排序子函數
void _Mergesort(int* a, int left, int right, int* tmp)
{if (left >= right)return;int mid = (right + left) / 2;_Mergesort(a, left, mid, tmp);_Mergesort(a, mid + 1, right, tmp);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int j = left;//歸并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 + left, tmp + left, sizeof(int) * (right - left + 1));
}//歸并排序函數
void Mergesort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_Mergesort(a,0,n-1,tmp);free(tmp);
}

歸并排序需要分為兩個函數,因為這里是寫遞歸的歸并,開辟數組只需要開辟一次,所以我們在另一個函數寫歸并排序,歸并排序就相當于后序遍歷,先將序列分為一個一個再進行比較。

時間復雜度:O(NlogN)

空間復雜度:O(N)

穩定性:穩定

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

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

相關文章

吃透LangChain(五):多模態輸入與自定義輸出

多模態數據輸入 這里我們演示如何將多模態輸入直接傳遞給模型。我們目前期望所有輸入都以與OpenAl 期望的格式相同的格式傳遞。對于支持多模態輸入的其他模型提供者&#xff0c;我們在類中添加了邏輯以轉換為預期格式。 在這個例子中&#xff0c;我們將要求模型描述一幅圖像。 …

【Rust 精進之路之第10篇-借用·規則】引用 (``, `mut`):安全、高效地訪問數據

系列: Rust 精進之路:構建可靠、高效軟件的底層邏輯 作者: 碼覺客 發布日期: 2025年4月20日 引言:所有權的“限制”與“變通”之道 在上一篇【所有權核心】中,我們揭示了 Rust 如何通過所有權規則和移動 (Move) 語義來保證內存安全,避免了垃圾回收器的同時,也防止了諸…

劍指Offer(數據結構與算法面試題精講)C++版——day16

劍指Offer&#xff08;數據結構與算法面試題精講&#xff09;C版——day16 題目一&#xff1a;序列化和反序列化二叉樹題目二&#xff1a;從根節點到葉節點的路徑數字之和題目三&#xff1a;向下的路徑節點值之和附錄&#xff1a;源碼gitee倉庫 題目一&#xff1a;序列化和反序…

OpenCV 模板與多個對象匹配方法詳解(繼OpenCV 模板匹配方法詳解)

文章目錄 前言1.導入庫2.圖片預處理3.輸出模板圖片的寬和高4.模板匹配5.獲取匹配結果中所有符合閾值的點的坐標5.1 threshold 0.9&#xff1a;5.2 loc np.where(res > threshold)&#xff1a; 6.遍歷所有匹配點6.1 loc 的結構回顧6.2 loc[::-1] 的作用6.2.1 為什么需要反轉…

產品經理學習過程

一&#xff1a;掃盲篇&#xff08;初始產品經理&#xff09; 階段1&#xff1a;了解產品經理 了解產品經理是做什么的、產品經理的分類、產品經理在實際工作中都會接觸什么樣的崗位、以及產品經理在實際工作中具體要做什么事情。 二&#xff1a;準備篇 階段2&#xff1a;工…

【消息隊列RocketMQ】一、RocketMQ入門核心概念與架構解析

在當今互聯網技術飛速發展的時代&#xff0c;分布式系統的架構設計愈發復雜。消息隊列作為分布式系統中重要的組件&#xff0c;在解耦應用、異步處理、削峰填谷等方面發揮著關鍵作用。RocketMQ 作為一款高性能、高可靠的分布式消息中間件&#xff0c;被廣泛應用于各類互聯網場景…

從“鏈主”到“全鏈”:供應鏈數字化轉型的底層邏輯

1. 制造業與供應鏈數字化轉型的必然性 1.1. 核心概念與戰略重要性 制造業的數字化轉型&#xff0c;是利用新一代數字技術&#xff08;如工業互聯網、人工智能、大數據、云計算、邊緣計算等&#xff09;對制造業的整體價值鏈進行根本性重塑的過程。這不僅涉及技術的應用&#…

x-ui重新申請ssl證書失敗

由于某些需要我們重新申請ssl證書&#xff0c;x-ui自動化腳本不能強制更新&#xff0c;根據x-ui倉庫源碼&#xff1a; https://github.com/vaxilu/x-ui/blob/main/x-ui.sh 在申請ssl證書的地方稍作修改&#xff0c;得到&#xff0c;運行下面的腳本就可以重新申請ssl證書&#…

Java NIO Java 虛擬線程(微線程)與 Go 協程的運行原理不同 為何Go 能在低配機器上承接10萬 Websocket 協議連接

什么是Java NIO&#xff1f; Java NIO&#xff08;New Input/Output&#xff09; 是Java 1.4&#xff08;2002年&#xff09;引入的一種非阻塞、面向緩沖區的輸入輸出框架&#xff0c;旨在提升Java在高性能和高并發場景下的I/O處理能力。它相比傳統的 Java IO&#xff08;java…

go環境安裝mac

下載go安裝包&#xff1a;https://golang.google.cn/dl/ 找到對應自己環境的版本下載。 注意有二進制的包&#xff0c;也有圖形界面安裝的包。圖形界面直接傻瓜式點就行了。 二進制的按照下面操作&#xff1a; 1、下載二進制包。 2、將下載的二進制包解壓至 /usr/local目錄…

LVGL源碼(9):學會控件的使用(自定義彈窗)

LVGL版本&#xff1a;8.3 LVGL的控件各式各樣&#xff0c;每種控件都有自己的一些特性&#xff0c;當我們想要使用一個LVGL控件時&#xff0c;我們首先可以通過官網去了解控件的一些基本特性&#xff0c;官網鏈接如下&#xff1a; LVGL Basics — LVGL documentation&#xf…

《軟件設計師》復習筆記(1)——考試介紹【新】

目錄 一、考試介紹 證書價值 考試要求 二、【新】計算機與軟件工程知識 三、軟件設計 一、考試介紹 >考試科目>考題形式>考試時長>合格標準計算機與軟件工程知識75道單選題&#xff08;每題1分&#xff0c;總分75分&#xff09;2023年11月改革機試后&#…

MCU中的BSS和data都占用SRAM空間嗎?

在MCU中&#xff0c;BSS段和data段都占用SRAM空間&#xff0c;但它們的存儲方式和用途有所不同。? BSS段 BSS段&#xff08;Block Started by Symbol&#xff09;用于存儲未初始化的全局變量和靜態變量。這些變量在程序啟動時會被清零&#xff0c;因此它們不占用Flash空間&a…

Ubuntu 22.04 更換 Nvidia 顯卡后啟動無法進入桌面問題的解決

原顯卡為 R7 240, 更換為 3060Ti 后, 開機進桌面時卡在了黑屏界面, 鍵盤有反應, 但是無法進入 shell. 解決方案為 https://askubuntu.com/questions/1538108/cant-install-rtx-4060-ti-on-ubuntu-22-04-lts 啟動后在開機菜單中(如果沒有開機菜單, 需要按shift鍵), 進入recove…

Python爬蟲-爬取貓眼演出數據

前言 本文是該專欄的第53篇,后面會持續分享python爬蟲干貨知識,記得關注。 貓眼平臺除了有影院信息之外,它還涵蓋了演出信息,比如說“演唱會,音樂節,話劇音樂劇,脫口秀,音樂會,戲曲藝術,相聲”等等各種演出相關信息。 而本文,筆者將以貓眼平臺為例,基于Python爬蟲…

人工智能-機器學習(線性回歸,邏輯回歸,聚類)

人工智能概述 人工智能分為:符號學習&#xff0c;機器學習。 機器學習是實現人工智能的一種方法&#xff0c;深度學習是實現機器學習的一種技術。 機器學習&#xff1a;使用算法來解析數據&#xff0c;從中學習&#xff0c;然后對真實世界中是事務進行決策和預測。如垃圾郵件檢…

FPGA學習(五)——DDS信號發生器設計

FPGA學習(五)——DDS信號發生器設計 目錄 FPGA學習(五)——DDS信號發生器設計一、FPGA開發中常用IP核——ROM/RAM/FIFO1、ROM簡介2、ROM文件的設置&#xff08;1&#xff09;直接編輯法&#xff08;2&#xff09;用C語言等軟件生成初始化文件 3、ROM IP核配置調用 二、DDS信號發…

【Vue】從 MVC 到 MVVM:前端架構演變與 Vue 的實踐之路

個人博客&#xff1a;haichenyi.com。感謝關注 一. 目錄 一–目錄二–架構模式的演變背景?三–MVC&#xff1a;經典的分層起點?四–MVP&#xff1a;面向接口的解耦嘗試?五–MVVM&#xff1a;數據驅動的終極形態??六–Vue&#xff1a;MVVM 的現代化實踐??? 二. 架構模…

【算法】快速排序、歸并排序(非遞歸版)

目錄 一、快速排序&#xff08;非遞歸&#xff09; 1.原理 2.實現 2.1 stack 2.2 partition(array,left,right) 2.3 pivot - 1 > left 二、歸并排序&#xff08;非遞歸&#xff09; 1.原理 2.實現 2.1 gap 2.1.1 i 2*gap 2.1.2 gap * 2 2.1.3 gap < array.…

CasualLanguage Model和Seq2Seq模型的區別

**問題1&#xff1a;**Causal Language Modeling 和 Conditional Generation 、Sequence Classification 的區別是什么&#xff1f; 因果語言模型(Causal Language Model)&#xff1a; 預測給定文本序列中的下一個字符&#xff0c;一般用于文本生成、補全句子等&#xff0c;模型…