摘要:今天,一條就帶大家徹底跨過(guò)排序算法這道坎,保姆級(jí)教程建議收藏。利用遞歸算法,對(duì)分治后的子數(shù)組進(jìn)行排序?;舅枷攵雅判蚴抢枚堰@種數(shù)據(jù)結(jié)構(gòu)而設(shè)計(jì)的一種排序算法,堆排序是一種選擇排序,它的最壞,最好,平均時(shí)間復(fù)雜度均為,它也是不穩(wěn)定排序。
?本文收錄于專欄《糊涂算法》——從今天起,邁過(guò)數(shù)據(jù)結(jié)構(gòu)和算法這道坎
作者其它優(yōu)質(zhì)專欄推薦:
?《技術(shù)專家修煉》——搞技術(shù),進(jìn)大廠,聊人生三合一專欄
?《leetcode 300題》——每天一道算法題,進(jìn)大廠必備
?《源碼中的設(shè)計(jì)模式》——理論與實(shí)戰(zhàn)的完美結(jié)合
?《從實(shí)戰(zhàn)學(xué)python》——Python的爬蟲(chóng),自動(dòng)化,AI等實(shí)戰(zhàn)應(yīng)用(代碼開(kāi)源)
點(diǎn)擊跳轉(zhuǎn)到文末領(lǐng)取粉絲福利
哈嘍,大家好,我是一條~
今天給大家?guī)?lái)《糊涂算法》專欄的第二期內(nèi)容——排序算法的講解。相信無(wú)論是初學(xué)者學(xué)習(xí)還是大廠面試,都少不了排序算法這關(guān),即使沒(méi)學(xué)過(guò)算法,對(duì)冒泡排序也不會(huì)陌生。
今天,一條就帶大家徹底跨過(guò)「排序算法」這道坎,保姆級(jí)教程建議收藏。??
本文配套源碼地址:《八大排序》源碼,提取碼:5ehp
古語(yǔ)云:“兵馬未動(dòng),糧草先行”。想跟著一條一塊把「排序算法」弄明白的,建議先準(zhǔn)備好以下代碼模板。
? 觀看本教程需知道基本循環(huán)語(yǔ)法、兩數(shù)交換、雙指針等前置知識(shí)。
? 建議先看完代碼和逐步分析后再嘗試自己寫。
Java
工程,本文全篇也基于Java語(yǔ)言實(shí)現(xiàn)代碼。MainTest
測(cè)試類中編寫測(cè)試模板。/** * 測(cè)試類 * Author:一條 * Date:2021/09/23 */public class MainTest { public static void main(String[] args) { //待排序序列 int[] array={6,10,4,5,2,8}; //調(diào)用不同排序算法 // BubbleSort.sort(array); // 創(chuàng)建有100000個(gè)隨機(jī)數(shù)據(jù)的數(shù)組 int[] costArray=new int[100000]; for (int i = 0; i < 100000; i++) { // 生成一個(gè)[0,100000) 的一個(gè)數(shù) costArray[i] = (int) (Math.random() * 100000); } Date start = new Date(); //過(guò)長(zhǎng),先注釋掉逐步打印 //BubbleSort.sort(costArray); Date end = new Date(); System.out.println("耗時(shí):"+(end.getTime()-start.getTime())/1000+"s"); }}
該段代碼內(nèi)容主要有兩個(gè)功能:
10w
個(gè)數(shù)排好序需要的時(shí)間。更加具象的理解時(shí)間復(fù)雜度的不同通過(guò)對(duì)亂序序列從前向后遍歷,依次比較相鄰元素的值,若發(fā)現(xiàn)逆序則交換,使值較大的元素逐漸從前移向后部。
像水底下的氣泡一樣逐漸向上冒一樣。
不理解的小伙伴可以用
debug
模式逐步分析。
/** * 冒泡排序 * Author:一條 * Date:2021/09/23 */public class BubbleSort{ public static int[] sort(int[] array){ for (int i = 0; i < array.length; i++) { for (int j = 0; j < array.length-1; j++) { //依次比較,將最大的元素交換到最后 if (array[j]>array[j+1]){ // 用臨時(shí)變量temp交換兩個(gè)值 int temp=array[j]; array[j]=array[j+1]; array[j+1]=temp; } } //輸出每一步的排序結(jié)果 System.out.println(Arrays.toString(array)); } return array; }}
輸出結(jié)果
逐步分析
[6,10,4,5,2,8]
6
拿出來(lái)和后一個(gè)10
比較,6<10
,不用交換。- > j++;
10
拿出來(lái)和后一個(gè)4
比較,10>4
,交換。- > [6,4,10,5,2,8]
j++
與后一個(gè)比較交換。i
循環(huán)完,打印第一行- > [6, 4, 5, 2, 8, 10]
,此時(shí)最后一位10
在正確位置上。 - > i++
4
開(kāi)始,繼續(xù)比較交換,倒數(shù)第二位8
回到正確位置。[2, 4, 5, 6, 8, 10]
這時(shí)再回去看動(dòng)圖理解。
記得先注釋掉排序類逐步打印代碼。
時(shí)間復(fù)雜度:O(n^2)
優(yōu)化點(diǎn)一
外層第一次遍歷完,最后一位已經(jīng)是正確的,j
就不需要再比較,所以結(jié)束條件應(yīng)改為j-i-1;
。
優(yōu)化點(diǎn)二
因?yàn)榕判虻倪^(guò)程中,各元素不斷接近自己的位置,如果一趟比較下來(lái)沒(méi)有進(jìn)行過(guò)交換,就說(shuō)明序列有序,因此要在排序過(guò)程中設(shè)置一個(gè)標(biāo)志flag
判斷元素是否進(jìn)行過(guò)交換。從而減少不必要的比較。
優(yōu)化代碼
public static int[] sortPlus(int[] array){ System.out.println("優(yōu)化冒泡排序開(kāi)始----------"); for (int i = 0; i < array.length; i++) { boolean flag=false; for (int j = 0; j < array.length-i-1; j++) { if (array[j]>array[j+1]){ flag=true; int temp=array[j]; array[j]=array[j+1]; array[j+1]=temp; } } if (flag==false){ break; }// System.out.println(Arrays.toString(array)); } return array; }
優(yōu)化測(cè)試
通過(guò)基礎(chǔ)測(cè)試看到當(dāng)序列已經(jīng)排好序,即不發(fā)生交換后終止循環(huán)。
耗時(shí)測(cè)試由27s
優(yōu)化到17s
。
選擇排序和冒泡排序很像,是從亂序序列的數(shù)據(jù)中,按指定的規(guī)則選出某一元素,再依規(guī)定交換位置后達(dá)到排序的目的。
public class SelectSort { public static int[] sort(int[] array) { System.out.println("選擇排序開(kāi)始----------"); for (int i = 0; i < array.length; i++) { //每個(gè)值只需與他后面的值進(jìn)行比較,所以從開(kāi)始 for (int j = i; j < array.length; j++) { //注意此處是哪兩個(gè)值比較 if (array[i]>array[j]){ int temp=array[i]; array[i]=array[j]; array[j]=temp; } } System.out.println(Arrays.toString(array)); } return array; }}
輸出結(jié)果
逐步分析
[6,10,4,5,2,8]
6
與10
比較,不交換 - > j++
6
與2
比較,交換 - > j++
2
繼續(xù)比較,都不交換,確定第一位(最小的數(shù))為2
- > i++
[2, 4, 5, 6, 8, 10]
這時(shí)再回去看動(dòng)圖理解。
時(shí)間復(fù)雜度:O(n^2)
上訴代碼中使用交換的方式找到較小值,還可以通過(guò)移動(dòng)的方式,即全部比較完只交換一次。
這種對(duì)空間的占有率會(huì)有些增益,但對(duì)時(shí)間的增益幾乎沒(méi)有,可忽略,亦不再演示。
把n個(gè)亂序的元素看成為一個(gè)有序表和一個(gè)無(wú)序表,開(kāi)始時(shí)有序表中只包含一個(gè)元素,無(wú)序表中包含有n-1個(gè)元素,排序過(guò)程中通過(guò)不斷往有序表插入元素,獲取一個(gè)局部正確解,逐漸擴(kuò)大有序序列的長(zhǎng)度,直到完成排序。
/** * 插入排序 * Author:一條 * Date:2021/09/23 */public class InsertSort { public static void sort(int[] array) { for (int i = 1; i < array.length; i++) { //插入有序序列,且將有序序列擴(kuò)大 for (int j = i; j > 0; j--) { if (array[j]>array[j-1]){ int temp=array[j]; array[j]=array[j-1]; array[j-1]=temp; } }// System.out.println(Arrays.toString(array)); } }}
輸出結(jié)果
見(jiàn)下方希爾排序,就是希爾對(duì)插入排序的優(yōu)化。
希爾排序是插入排序的一個(gè)優(yōu)化,思考往
[2,3,4,5,6]
中插入1
,需要將所有元素的位置都移動(dòng)一遍,也就是說(shuō)在某些極端情況下效率不高,也稱該算法不穩(wěn)定。希爾排序是插入排序經(jīng)過(guò)改進(jìn)之后的一個(gè)更高效的版本,也稱為縮小增量排序。
希爾排序是把記錄按下標(biāo)的一定增量分組,對(duì)每組使用插入排序;
隨著增量逐漸減少,每組包含的關(guān)鍵詞越來(lái)越多,當(dāng)增量減至1時(shí),整個(gè)序列恰被分成一組,算法便終止。
和插入排序一樣,從局部到全部,希爾排序是局部再局部。
/** * 希爾排序 * Author:一條 * Date:2021/09/23 */public class ShellSort { public static void sort(int[] array) { System.out.println("希爾排序開(kāi)始--------"); //gap初始增量=length/2 逐漸縮小:gap/2 for (int gap = array.length/2; gap > 0 ; gap/=2) { //插入排序 交換法 for (int i = gap; i < array.length ; i++) { int j = i; while(j-gap>=0 && array[j]<array[j-gap]){ //插入排序采用交換法 int temp = array[j]; array[j]=array[j-gap]; array[j-gap]=temp; j-=gap; } } System.out.println(Arrays.toString(array)); } }}
輸出結(jié)果
無(wú)
快速排序(Quicksort)是對(duì)冒泡排序的一種改進(jìn),相比冒泡排序,每次的交換都是跳躍式的。
將要排序的數(shù)據(jù)分割成獨(dú)立的兩部分,其中一部分的所有數(shù)據(jù)都比另外一部分的所有數(shù)據(jù)都要小,然后再按此方法對(duì)這兩部分?jǐn)?shù)據(jù)分別進(jìn)行快速排序,整個(gè)排序過(guò)程可以遞歸進(jìn)行,以此達(dá)到整個(gè)數(shù)據(jù)變成有序序列。
體現(xiàn)出分治的思想。
思路如下:
- 首先在這個(gè)序列中找一個(gè)數(shù)作為基準(zhǔn)數(shù),為了方便可以取第一個(gè)數(shù)。
- 遍歷數(shù)組,將小于基準(zhǔn)數(shù)的放置于基準(zhǔn)數(shù)左邊,大于基準(zhǔn)數(shù)的放置于基準(zhǔn)數(shù)右邊。此處可用雙指針實(shí)現(xiàn)。
- 此時(shí)基準(zhǔn)值把數(shù)組分為了兩半,基準(zhǔn)值算是已歸位(找到排序后的位置)。
- 利用遞歸算法,對(duì)分治后的子數(shù)組進(jìn)行排序。
public class QuickSort { public static void sort(int[] array) { System.out.println("快速排序開(kāi)始---------"); mainSort(array, 0, array.length - 1); } private static void mainSort(int[] array, int left, int right) { if(left > right) { return; } //雙指針 int i=left; int j=right; //base就是基準(zhǔn)數(shù) int base = array[left]; //左邊小于基準(zhǔn),右邊大于基準(zhǔn) while (i<j) { //先看右邊,依次往左遞減 while (base<=array[j]&&i<j) { j--; } //再看左邊,依次往右遞增 while (base>=array[i]&&i<j) { i++; } //交換 int temp = array[j]; array[j] = array[i]; array[i] = temp; } //最后將基準(zhǔn)為與i和j相等位置的數(shù)字交換 array[left] = array[i]; array[i] = base; System.out.println(Arrays.toString(array)); //遞歸調(diào)用左半數(shù)組 mainSort(array, left, j-1); //遞歸調(diào)用右半數(shù)組 mainSort(array, j+1, right); }}
輸出結(jié)果
逐步分析
6
作為基準(zhǔn)數(shù),利用左右指針使左邊的數(shù)<6
,右邊的數(shù)>6
。5
作為基準(zhǔn)數(shù)繼續(xù)比較。left > right
結(jié)束遞歸。快速排序?yàn)槭裁催@么快?
優(yōu)化一
三數(shù)取中(median-of-three):我們目前是拿第一個(gè)數(shù)作為基準(zhǔn)數(shù),對(duì)于部分有序序列,會(huì)浪費(fèi)循環(huán),可以用三數(shù)取中法優(yōu)化,感性的小伙伴可自行了解。
優(yōu)化二
快速排序?qū)τ陂L(zhǎng)序列非常快,但對(duì)于短序列不如插入排序??梢跃C合使用。
此章節(jié)對(duì)基礎(chǔ)知識(shí)要求較高,初學(xué)者可跳過(guò)。
堆排序是利用堆這種數(shù)據(jù)結(jié)構(gòu)而設(shè)計(jì)的一種排序算法,堆排序是一種**選擇排序,**它的最壞,最好,平均時(shí)間復(fù)雜度均為O(nlogn)
,它也是不穩(wěn)定排序。首先簡(jiǎn)單了解下堆結(jié)構(gòu)。
堆
堆是具有以下性質(zhì)的完全二叉樹(shù):
主要利用堆頂元素最大或最小的特性,通過(guò)不斷構(gòu)建大頂堆,交換堆頂和堆尾,斷尾重構(gòu)的方式實(shí)現(xiàn)排序。
public class HeapSort { public static void sort(int[] array) { //創(chuàng)建堆 for (int i = (array.length - 1) / 2; i >= 0; i--) { //從第一個(gè)非葉子結(jié)點(diǎn)從下至上,從右至左調(diào)整結(jié)構(gòu) adjustHeap(array, i, array.length); } //調(diào)整堆結(jié)構(gòu)+交換堆頂元素與末尾元素 for (int i = array.length - 1; i > 0; i--) { //將堆頂元素與末尾元素進(jìn)行交換 int temp = array[i]; array[i] = array[0]; array[0] = temp; //重新對(duì)堆進(jìn)行調(diào)整 adjustHeap(array, 0, i); } } /** * 調(diào)整堆 * @param array 待排序列 * @param parent 父節(jié)點(diǎn) * @param length 待排序列尾元素索引 */ private static void adjustHeap(int[] array, int parent, int length) { //將temp作為父節(jié)點(diǎn) int temp = array[parent]; //左孩子 int lChild = 2 * parent + 1; while (lChild < length) { //右孩子 int rChild = lChild + 1; // 如果有右孩子結(jié)點(diǎn),并且右孩子結(jié)點(diǎn)的值大于左孩子結(jié)點(diǎn),則選取右孩子結(jié)點(diǎn) if (rChild < length && array[lChild] < array[rChild]) { lChild++; } // 如果父結(jié)點(diǎn)的值已經(jīng)大于孩子結(jié)點(diǎn)的值,則直接結(jié)束 if (temp >= array[lChild]) { break; } // 把孩子結(jié)點(diǎn)的值賦給父結(jié)點(diǎn) array[parent] = array[lChild]; //選取孩子結(jié)點(diǎn)的左孩子結(jié)點(diǎn),繼續(xù)向下篩選 parent = lChild; lChild = 2 * lChild + 1; } array[parent] = temp; System.out.println(Arrays.toString(array)); }}
輸出結(jié)果
逐步分析
優(yōu)化點(diǎn)關(guān)鍵就在于我們以什么手法找到頂部元素該有的位置,有興趣同學(xué)可以繼續(xù)研究。
歸并排序(MERGE-SORT)是利用歸并的思想實(shí)現(xiàn)的排序方法,采用經(jīng)典的分治(divide-and-conquer)策略。
將亂序序列不斷的分成一半,排好序再拼回去,用遞歸實(shí)現(xiàn)。
難點(diǎn)在于如何歸并兩個(gè)排好序的數(shù)組?
我們可以開(kāi)辟一個(gè)臨時(shí)數(shù)組,使用快慢指針來(lái)輔助我們的歸并。
雖然需要額外空間的來(lái)完成這個(gè)排序。但是現(xiàn)在計(jì)算機(jī)的內(nèi)存都比較大,時(shí)間的效率要比空間的效率重要的多。
所以空間換時(shí)間也是優(yōu)化算法時(shí)的一個(gè)方向。
public class MergeSort { public static void sort(int[] array){ divide(array,0,array.length-1); } private static int[] divide(int[] array, int left, int right) { int mid = (left+right)/2; if(left<right){ divide(array,left,mid); divide(array,mid+1,right); //左右歸并 merge(array,left,mid,right); } System.out.println(Arrays.toString(array)); return array; } public static void merge(int[] array, int left, int mid, int right) { int[] temp = new int[right-left+1]; int i= left; int j = mid+1
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/121429.html
?? 一條獨(dú)家專欄 ?? 搞技術(shù),進(jìn)大廠,聊人生 ?《大廠面試突擊》——面試10多家中大廠的萬(wàn)字總結(jié) ?《技術(shù)專家修煉》——高薪必備,企業(yè)真實(shí)場(chǎng)景 ?《leetcode 300題》——每天一道算法題,進(jìn)大廠必備 ?《糊涂算法》——數(shù)據(jù)結(jié)構(gòu)+算法全面講解 ?《從實(shí)戰(zhàn)學(xué)python》——python的各種應(yīng)用 ?《程序人生》——聽(tīng)一條聊職場(chǎng),聊人生 ?更多資料點(diǎn)這里 天下難事,必作于易;天下大事,...
摘要:本篇博客我要來(lái)和大家一起聊一聊數(shù)據(jù)結(jié)構(gòu)初階中的最后一篇博客八大經(jīng)典排序算法的總結(jié),其中會(huì)介紹他們的原來(lái),還有復(fù)雜度的分析以及各種優(yōu)化??焖倥判蜻f歸版本快速排序是于年提出的一種二叉樹(shù)結(jié)構(gòu)的交換排序方法。 ...
摘要:當(dāng)?shù)竭_(dá)時(shí)等同于直接插入排序,此時(shí)序列已基本有序,所有記錄在統(tǒng)一組內(nèi)排好成有序序列。當(dāng)面對(duì)大量數(shù)據(jù)時(shí),希爾排序?qū)⒈戎苯硬迦肱判蚋邇?yōu)勢(shì)圖示講解第一趟取增量,所有間隔為的元素分在一組,在各個(gè)組中分別進(jìn)行直接插入排序。 ...
閱讀 804·2023-04-25 20:32
閱讀 2446·2021-11-24 10:27
閱讀 4625·2021-09-29 09:47
閱讀 2334·2021-09-28 09:36
閱讀 3727·2021-09-22 15:27
閱讀 2859·2019-08-30 15:54
閱讀 426·2019-08-30 11:06
閱讀 1328·2019-08-30 10:58