Java實(shí)現(xiàn)八種常用排序算法,你會(huì)幾個(gè)?
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
? 作者?|??低吟不作語
來源 |? urlify.cn/y6bYfu
66套java從入門到精通實(shí)戰(zhàn)課程分享
八種排序算法可以按照如圖分類

一、交換排序
所謂交換,就是序列中任意兩個(gè)元素進(jìn)行比較,根據(jù)比較結(jié)果來交換各自在序列中的位置,以此達(dá)到排序的目的。
1. 冒泡排序
冒泡排序是一種簡單的交換排序算法,以升序排序?yàn)槔?,其核心思想是?/span>
從第一個(gè)元素開始,比較相鄰的兩個(gè)元素。如果第一個(gè)比第二個(gè)大,則進(jìn)行交換。
輪到下一組相鄰元素,執(zhí)行同樣的比較操作,再找下一組,直到?jīng)]有相鄰元素可比較為止,此時(shí)最后的元素應(yīng)是最大的數(shù)。
除了每次排序得到的最后一個(gè)元素,對剩余元素重復(fù)以上步驟,直到?jīng)]有任何一對元素需要比較為止。

用 Java 實(shí)現(xiàn)的冒泡排序如下
public?void?bubbleSortOpt(int[]?arr)?{
????if(arr?==?null)?{
????????throw?new?NullPoniterException();
????}
????if(arr.length?2)?{
????????return;
????}
????int?temp?=?0;
????for(int?i?=?0;?i?????????for(int?j?=?0;?j?????????????if(arr[j]?>?arr[j?+?1])?{
????????????????temp?=?arr[j];
????????????????arr[j]?=?arr[j?+?1];
????????????????arr[j?+?1]?=?temp;
????????????}
????????}
????}
}
2. 快速排序
快速排序的思想很簡單,就是先把待排序的數(shù)組拆成左右兩個(gè)區(qū)間,左邊都比中間的基準(zhǔn)數(shù)小,右邊都比基準(zhǔn)數(shù)大。接著左右兩邊各自再做同樣的操作,完成后再拆分再繼續(xù),一直到各區(qū)間只有一個(gè)數(shù)為止。
舉個(gè)例子,現(xiàn)在我要排序 4、9、5、1、2、6 這個(gè)數(shù)組。一般取首位的 4 為基準(zhǔn)數(shù),第一次排序的結(jié)果是:
2、1、4、5、9、6
可能有人覺得奇怪,2 和 1 交換下位置也能滿足條件,為什么 2 在首位?這其實(shí)由實(shí)際的代碼實(shí)現(xiàn)來決定,并不影響之后的操作。以 4 為分界點(diǎn),對 2、1、4 和 5、9、6 各自排序,得到:
1、2、4、5、9、6
不用管左邊的 1、2、4 了,將 5、9、6 拆成 5 和 9、6,再排序,至此結(jié)果為:
1、2、4、5、6、9
為什么把快排劃到交換排序的范疇呢?因?yàn)樵氐囊苿?dòng)也是靠交換位置來實(shí)現(xiàn)的。算法的實(shí)現(xiàn)需要用到遞歸(拆分區(qū)間之后再對每個(gè)區(qū)間作同樣的操作)

用 Java 實(shí)現(xiàn)的快速排序如下
public?void?quicksort(int[]?arr,?int?start,?int?end)?{
????if(start?????????//?把數(shù)組中的首位數(shù)字作為基準(zhǔn)數(shù)
????????int?stard?=?arr[start];
????????//?記錄需要排序的下標(biāo)
????????int?low?=?start;
????????int?high?=?end;
????????//?循環(huán)找到比基準(zhǔn)數(shù)大的數(shù)和比基準(zhǔn)數(shù)小的數(shù)
????????while(low?????????????//?右邊的數(shù)字比基準(zhǔn)數(shù)大
????????????while(low?=?stard)?{
????????????????high--;
????????????}
????????????//?使用右邊的數(shù)替換左邊的數(shù)
????????????arr[low]?=?arr[high];
????????????//?左邊的數(shù)字比基準(zhǔn)數(shù)小
????????????while(low?????????????????low++;
????????????}
????????????//?使用左邊的數(shù)替換右邊的數(shù)
????????????arr[high]?=?arr[low];
????????}
????????//?把標(biāo)準(zhǔn)值賦給下標(biāo)重合的位置
????????arr[low]?=?stard;
????????//?處理所有小的數(shù)字
????????quickSort(arr,?start,?low);
????????//?處理所有大的數(shù)字
????????quickSort(arr,?low?+?1,?end);
????}
}二、插入排序
插入排序是一種簡單的排序方法,其基本思想是將一個(gè)記錄插入到已經(jīng)排好序的有序表中,使得被插入數(shù)的序列同樣是有序的。按照此法對所有元素進(jìn)行插入,直到整個(gè)序列排為有序的過程。
1. 直接插入排序
直接插入排序就是插入排序的粗暴實(shí)現(xiàn)。對于一個(gè)序列,選定一個(gè)下標(biāo),認(rèn)為在這個(gè)下標(biāo)之前的元素都是有序的。將下標(biāo)所在的元素插入到其之前的序列中。接著再選取這個(gè)下標(biāo)的后一個(gè)元素,繼續(xù)重復(fù)操作。直到最后一個(gè)元素完成插入為止。我們一般從序列的第二個(gè)元素開始操作。


用 Java 實(shí)現(xiàn)的算法如下:
public?void?insertSort(int[]?arr)?{
????//?遍歷所有數(shù)字
????for(int?i?=?1;?i?????????//?當(dāng)前數(shù)字比前一個(gè)數(shù)字小
????????if(arr[i]?????????????int?j;
????????????//?把當(dāng)前遍歷的數(shù)字保存起來
????????????int?temp?=?arr[i];
????????????for(j?=?i?-?1;?j?>=?0?&&?arr[j]?>?temp;?j--)?{
????????????????//?前一個(gè)數(shù)字賦給后一個(gè)數(shù)字
????????????????arr[j?+?1]?=?arr[j];
????????????}
????????????//?把臨時(shí)變量賦給不滿足條件的后一個(gè)元素
????????????arr[j?+?1]?=?temp;
????????}
????}
}
2. 希爾排序
某些情況下直接插入排序的效率極低。比如一個(gè)已經(jīng)有序的升序數(shù)組,這時(shí)再插入一個(gè)比最小值還要小的數(shù),也就意味著被插入的數(shù)要和數(shù)組所有元素比較一次。我們需要對直接插入排序進(jìn)行改進(jìn)。
怎么改進(jìn)呢?前面提過,插入排序?qū)σ呀?jīng)排好序的數(shù)組操作時(shí),效率很高。因此我們可以試著先將數(shù)組變?yōu)橐粋€(gè)相對有序的數(shù)組,然后再做插入排序。
希爾排序能實(shí)現(xiàn)這個(gè)目的。希爾排序把序列按下標(biāo)的一定增量(步長)分組,對每組分別使用插入排序。隨著增量(步長)減少,一直到一,算法結(jié)束,整個(gè)序列變?yōu)橛行?。因此希爾排序又稱縮小增量排序。
一般來說,初次取序列的一半為增量,以后每次減半,直到增量為一。

用 Java 實(shí)現(xiàn)的算法如下:
public?void?shellSort(int[]?arr)?{
????// gap 為步長,每次減為原來的一半。
????for?(int?gap?=?arr.length?/?2;?gap?>?0;?gap?/=?2)?{
????????//?對每一組都執(zhí)行直接插入排序
????????for?(int?i?=?0?;i?????????????//?對本組數(shù)據(jù)執(zhí)行直接插入排序
????????????for?(int?j?=?i?+?gap;?j?????????????????//?如果?a[j]?????????????????if?(arr[j]?????????????????????int?k;
????????????????????int?temp?=?arr[j];
????????????????????for?(k?=?j?-?gap;?k?>=?0?&&?arr[k]?>?temp;?k?-=?gap)?{
????????????????????????arr[k?+?gap]?=?arr[k];
????????????????????}
????????????????????arr[k?+?gap]?=?temp;
????????????????}
????????????}
????????}
????}
}
三、選擇排序
選擇排序是一種簡單直觀的排序算法,首先在未排序序列中找到最?。ù螅┰?,存放到排序序列的起始位置,然后,再從剩余未排序元素中繼續(xù)尋找最?。ù螅┰?,然后放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
1. 簡單選擇排序
選擇排序思想的暴力實(shí)現(xiàn),每一趟從未排序的區(qū)間找到一個(gè)最小元素,并放到第一位,直到全部區(qū)間有序?yàn)橹埂?/span>

用 Java 實(shí)現(xiàn)的算法如下:
public?void?selectSort(int[]?arr)?{
????//?遍歷所有的數(shù)
????for?(int?i?=?0;?i?????????int?minIndex?=?i;
????????//?把當(dāng)前遍歷的數(shù)和后面所有的數(shù)進(jìn)行比較,并記錄下最小的數(shù)的下標(biāo)
????????for?(int?j?=?i?+?1;?j?????????????if?(arr[j]?????????????????//?記錄最小的數(shù)的下標(biāo)
????????????????minIndex?=?j;
????????????}
????????}
????????//?如果最小的數(shù)和當(dāng)前遍歷的下標(biāo)不一致,則交換
????????if?(i?!=?minIndex)?{
????????????int?temp?=?arr[i];
????????????arr[i]?=?arr[minIndex];
????????????arr[minIndex]?=?temp;
????????}
????}
}
2. 堆排序
我們知道,對于任何一個(gè)數(shù)組都可以看成一顆完全二叉樹。堆是具有以下性質(zhì)的完全二叉樹:每個(gè)結(jié)點(diǎn)的值都大于或等于其左右孩子結(jié)點(diǎn)的值,稱為大頂堆;或者每個(gè)結(jié)點(diǎn)的值都小于或等于其左右孩子結(jié)點(diǎn)的值,稱為小頂堆。如下圖:

像上圖的大頂堆,映射為數(shù)組,就是 [50, 45, 40, 20, 25, 35, 30, 10, 15]。可以發(fā)現(xiàn)第一個(gè)下標(biāo)的元素就是最大值,將其與末尾元素交換,則末尾元素就是最大值。所以堆排序的思想可以歸納為以下兩步:
根據(jù)初始數(shù)組構(gòu)造堆
每次交換第一個(gè)和最后一個(gè)元素,然后將除最后一個(gè)元素以外的其他元素重新調(diào)整為大頂堆
重復(fù)以上兩個(gè)步驟,直到?jīng)]有元素可操作,就完成排序了。
我們需要把一個(gè)普通數(shù)組轉(zhuǎn)換為大頂堆,調(diào)整的起始點(diǎn)是最后一個(gè)非葉子結(jié)點(diǎn),然后從左至右,從下至上,繼續(xù)調(diào)整其他非葉子結(jié)點(diǎn),直到根結(jié)點(diǎn)為止。

/**
?*?轉(zhuǎn)化為大頂堆
?*?@param?arr?待轉(zhuǎn)化的數(shù)組
?*?@param?size?待調(diào)整的區(qū)間長度
?*?@param?index?結(jié)點(diǎn)下標(biāo)
?*/
public?void?maxHeap(int[]?arr,?int?size,?int?index)?{
????//?左子結(jié)點(diǎn)
????int?leftNode?=?2?*?index?+?1;
????//?右子結(jié)點(diǎn)
????int?rightNode?=?2?*?index?+?2;
????int?max?=?index;
????//?和兩個(gè)子結(jié)點(diǎn)分別對比,找出最大的結(jié)點(diǎn)
????if?(leftNode??arr[max])?{
????????max?=?leftNode;
????}
????if?(rightNode??arr[max])?{
????????max?=?rightNode;
????}
????//?交換位置
????if?(max?!=?index)?{
????????int?temp?=?arr[index];
????????arr[index]?=?arr[max];
????????arr[max]?=?temp;
????????//?因?yàn)榻粨Q位置后有可能使子樹不滿足大頂堆條件,所以要對子樹進(jìn)行調(diào)整
????????maxHeap(arr,?size,?max);
????}
}
/**
?*?堆排序
?*?@param?arr?待排序的整型數(shù)組
?*/
public?static?void?heapSort(int[]?arr)?{
????//?開始位置是最后一個(gè)非葉子結(jié)點(diǎn),即最后一個(gè)結(jié)點(diǎn)的父結(jié)點(diǎn)
????int?start?=?(arr.length?-?1)?/?2;
????//?調(diào)整為大頂堆
????for?(int?i?=?start;?i?>=?0;?i--)?{
????????SortTools.maxHeap(arr,?arr.length,?i);
????}
????//?先把數(shù)組中第?0?個(gè)位置的數(shù)和堆中最后一個(gè)數(shù)交換位置,再把前面的處理為大頂堆
????for?(int?i?=?arr.length?-?1;?i?>?0;?i--)?{
????????int?temp?=?arr[0];
????????arr[0]?=?arr[i];
????????arr[i]?=?temp;
????????maxHeap(arr,?i,?0);
????}
}
四、歸并排序
歸并排序是建立在歸并操作上的一種有效,穩(wěn)定的排序算法。該算法采用分治法的思想,是一個(gè)非常典型的應(yīng)用。歸并排序的思路如下:
將 n 個(gè)元素分成兩個(gè)各含 n/2 個(gè)元素的子序列
借助遞歸,兩個(gè)子序列分別繼續(xù)進(jìn)行第一步操作,直到不可再分為止
此時(shí)每一層遞歸都有兩個(gè)子序列,再將其合并,作為一個(gè)有序的子序列返回上一層,再繼續(xù)合并,全部完成之后得到的就是一個(gè)有序的序列
關(guān)鍵在于兩個(gè)子序列應(yīng)該如何合并。假設(shè)兩個(gè)子序列各自都是有序的,那么合并步驟就是:
創(chuàng)建一個(gè)用于存放結(jié)果的臨時(shí)數(shù)組,其長度是兩個(gè)子序列合并后的長度
設(shè)定兩個(gè)指針,最初位置分別為兩個(gè)已經(jīng)排序序列的起始位置
比較兩個(gè)指針?biāo)赶虻脑?,選擇相對小的元素放入臨時(shí)數(shù)組,并移動(dòng)指針到下一位置
重復(fù)步驟 3 直到某一指針達(dá)到序列尾
將另一序列剩下的所有元素直接復(fù)制到合并序列尾

用 Java 實(shí)現(xiàn)的歸并排序如下:
/**
?*?合并數(shù)組
?*/
public?static?void?merge(int[]?arr,?int?low,?int?middle,?int?high)?{
????//?用于存儲(chǔ)歸并后的臨時(shí)數(shù)組
????int[]?temp?=?new?int[high?-?low?+?1];
????//?記錄第一個(gè)數(shù)組中需要遍歷的下標(biāo)
????int?i?=?low;
????//?記錄第二個(gè)數(shù)組中需要遍歷的下標(biāo)
????int?j?=?middle?+?1;
????//?記錄在臨時(shí)數(shù)組中存放的下標(biāo)
????int?index?=?0;
????//?遍歷兩個(gè)數(shù)組,取出小的數(shù)字,放入臨時(shí)數(shù)組中
????while?(i?<=?middle?&&?j?<=?high)?{
????????//?第一個(gè)數(shù)組的數(shù)據(jù)更小
????????if?(arr[i]?<=?arr[j])?{
????????????//?把更小的數(shù)據(jù)放入臨時(shí)數(shù)組中
????????????temp[index]?=?arr[i];
????????????//?下標(biāo)向后移動(dòng)一位
????????????i++;
????????}?else?{
????????????temp[index]?=?arr[j];
????????????j++;
????????}
????????index++;
????}
????//?處理剩余未比較的數(shù)據(jù)
????while?(i?<=?middle)?{
????????temp[index]?=?arr[i];
????????i++;
????????index++;
????}
????while?(j?<=?high)?{
????????temp[index]?=?arr[j];
????????j++;
????????index++;
????}
????//?把臨時(shí)數(shù)組中的數(shù)據(jù)重新放入原數(shù)組
????for?(int?k?=?0;?k?????????arr[k?+?low]?=?temp[k];
????}
}
/**
?*?歸并排序
?*/
public?static?void?mergeSort(int[]?arr,?int?low,?int?high)?{
????int?middle?=?(high?+?low)?/?2;
????if?(low?????????//?處理左邊數(shù)組
????????mergeSort(arr,?low,?middle);
????????//?處理右邊數(shù)組
????????mergeSort(arr,?middle?+?1,?high);
????????//?歸并
????????merge(arr,?low,?middle,?high);
????}
}
五、基數(shù)排序
基數(shù)排序的原理是將整數(shù)按位數(shù)切割成不同的數(shù)字,然后按每個(gè)位數(shù)分別比較。為此需要將所有待比較的數(shù)值統(tǒng)一為同樣的數(shù)位長度,數(shù)位不足的數(shù)在高位補(bǔ)零。

使用 Java 實(shí)現(xiàn)的基數(shù)排序:
/**
?*?基數(shù)排序
?*/
public?static?void?radixSort(int[]?arr)?{
????//?存放數(shù)組中的最大數(shù)字
????int?max?=?Integer.MIN_VALUE;
????for?(int?value?:?arr)?{
????????if?(value?>?max)?{
????????????max?=?value;
????????}
????}
????//?計(jì)算最大數(shù)字是幾位數(shù)
????int?maxLength?=?(max?+?"").length();
????//?用于臨時(shí)存儲(chǔ)數(shù)據(jù)
????int[][]?temp?=?new?int[10][arr.length];
????//?用于記錄在?temp?中相應(yīng)的下標(biāo)存放數(shù)字的數(shù)量
????int[]?counts?=?new?int[10];
????//?根據(jù)最大長度的數(shù)決定比較次數(shù)
????for?(int?i?=?0,?n?=?1;?i?????????//?每一個(gè)數(shù)字分別計(jì)算余數(shù)
????????for?(int?j?=?0;?j?????????????//?計(jì)算余數(shù)
????????????int?remainder?=?arr[j]?/?n?%?10;
????????????//?把當(dāng)前遍歷的數(shù)據(jù)放到指定的數(shù)組中
????????????temp[remainder][counts[remainder]]?=?arr[j];
????????????//?記錄數(shù)量
????????????counts[remainder]++;
????????}
????????//?記錄取的元素需要放的位置
????????int?index?=?0;
????????//?把數(shù)字取出來
????????for?(int?k?=?0;?k?????????????//?記錄數(shù)量的數(shù)組中當(dāng)前余數(shù)記錄的數(shù)量不為?0
????????????if?(counts[k]?!=?0)?{
????????????????//?循環(huán)取出元素
????????????????for?(int?l?=?0;?l?????????????????????arr[index]?=?temp[k][l];
????????????????????//?記錄下一個(gè)位置
????????????????????index++;
????????????????}
????????????????//?把數(shù)量置空
????????????????counts[k]?=?0;
????????????}
????????}
????}
}
六、八種排序算法的總結(jié)
| 排序法 | 最好情形 | 平均時(shí)間 | 最差情形 | 穩(wěn)定度 | 空間復(fù)雜度 |
| 冒泡排序 | O(n) | O(n^2) | O(n^2) | 穩(wěn)定 | O(1) |
| 快速排序 | O(nlogn) | O(nlogn) | O(n^2) | 不穩(wěn)定 | O(nlogn) |
| 直接插入排序 | O(n) | O(n^2) | O(n^2) | 穩(wěn)定 | O(1) |
| 希爾排序 | 不穩(wěn)定 | O(1) | |||
| 直接選擇排序 | O(n^2) | O(n^2) | O(n^2) | 不穩(wěn)定 | O(1) |
| 堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | 不穩(wěn)定 | O(nlogn) |
| 歸并排序 | O(nlogn) | O(nlogn) | O(nlogn) | 穩(wěn)定 | O(n) |


??? ?
感謝點(diǎn)贊支持下哈?
