Github 地址
排序,不管是日常開發還是面試中,都會被光顧,雖然現在 jdk 的集合中提供了排序的方法,但是我們也得去熟悉一些基礎的排序算法,這裏筆者給大家分享五個常問的基礎排序,冒泡、選擇、插入、希爾、快排。
冒泡排序
冒泡排序,顧名思義就是排序元素時像冒泡一樣,把最大或者最小慢慢比較到數組尾部,實現排序的方法。具體就是,如果有一個長度爲 n 的數組 num,將 num[0] 與 num[1] 比較,如果 num[0] > num[1] ,則交換位置,反之則不交換。再比較 num[1] 和 num[2], 根據大小決定位置,一直交換到數組結束。那麼 num[n-1] 一定是數組中最大的。再次做上述比較,那麼 num[n-2] 一定是第二大的,一直循環直到整個數組排序完成。 下圖只展示冒第一個泡。
public int[] order(int[] num){
if (num.length == 0){
return null;
}
// -1 是因爲我們比較 i 和 i+1,防止數組越界
for (int i = 0; i < num.length -1 ; i++) {
// 因爲第 num.length - i 到 num.length 的數字肯定是排序好的
// 所以只用比到 num.length - i
for (int j = 0; j < num.length - i - 1 ; j++) {
if (num[j]>num[j+1]){
int temp = num[j];
num[j] = num[j+1];
num[j+1] = temp;
}
}
}
return num;
}
選擇排序
選擇排序,就是第一次選擇數組中最小的與第一個交換,第二次選擇第二小的與第二個元素交換,直到交換到最後一個。
public int[] order(int[] num){
if (num.length == 0){
return null;
}
for (int i = 0; i < num.length; i++) {
int minIndex = i;
// 因爲前 i 位肯定是排序好的,所以只需要從 i 位開始找
for (int j = i; j < num.length ; j++) {
if (num[j] < num[minIndex]){
minIndex = j;
}
}
int temp = num[i];
num[i] = num[minIndex];
num[minIndex] = temp;
}
return num;
}
插入排序
插入排序是從拿第二個元素開始,與左邊的元素挨個比較,找到比自己大的元素放,然後把自己放到該元素左邊,如果沒有就不換位置,再從第三個元素開始,直到排序完成。
public int[] order(int[] num){
if (num.length == 0 || num.length == 1){
return num;
}
for (int i = 1; i < num.length; i++) {
int changeIndex = i;
for (int j = i; j > 0; j--) {
if (num[changeIndex] < num[changeIndex - 1]){
int temp = num[changeIndex];
num[changeIndex] = num[changeIndex-1];
num[changeIndex-1] = temp;
changeIndex--;
}else {
// 因爲是從選擇元素的左邊第一個開始比較
// 左邊已經排序完成,如果選擇元素大於已經要比較的元素可以直接退出比較
break;
}
}
}
return num;
}
希爾排序
什麼是希爾排序呢?希爾排序是插入排序的改進版,我們可以看到,上面的插入排序在什麼時候排序會比較快呢?首先,和所有排序一樣,元素的個數少時,還有呢?我們可以看到,當數組基本有序時,那麼插入排序就會更快。所以希爾排序是什麼改進的呢?希爾排序將數組按照增量分割成若干個小組,利用插入排序完成排序,然後變小增量,再次分割並排序(這裏的分割只是邏輯上的分割,並沒有真正將數組分成幾個新的數組)。下面來詳細說一下:
先解釋一下,增量爲每組數組兩個元素之間下標差值,一般來說第一次增量爲數組長度的一半,第二次分組的增量爲上次的增量的一半,一直到增量爲 1。可以看到,第一次分組插入排序後,每組有序,數組大致有序,我們再接着分組:
如果再次分組的話,grow = grow/2 = 1,這樣的增量爲1,就不需要分組了,我們可以看到現在的數組比原數組有序多了,使用現在的數組比進行插入排序會比原數組快很多。
代碼表示(其實就是分組使用插入排序):
public int[] order(int[] num){
if (num.length == 0 || num.length == 1){
return num;
}
for (int grow = num.length/2; grow > 0 ; grow/=2) {
for (int j = grow; j < num.length; j++) {
int k = 1;
int changeIndex = j;
while (j - grow * k >= 0){
if (num[changeIndex] < num[changeIndex - grow]){
int temp = num[changeIndex];
num[changeIndex] = num[changeIndex - grow];
num[changeIndex - grow] = temp;
changeIndex -= grow;
}else {
// 因爲是每組元素從選擇元素的左邊第一個開始比較
// 左邊已經排序完成,如果選擇元素大於已經要比較的元素可以直接退出比較
break;
}
k++;
}
}
}
return num;
}
快速排序
快速排序就是我們經常聽到的快排,那麼快排是怎麼對數組進行排序的呢?快排中有比較重要的三個變量,基準、左指針,右指針。基準一般是數組的第一個元素,左指針指第一個元素,右指針指最後一個元素,分別從左右指針指的元素與基準比較,比基準大的放右邊,比基準小的放左邊。這樣到最後,就會找到一個數,這個數的左邊都比這個數小,右邊都比這個數大,然後再遞歸按照上面的方法分別遍歷這個數的左區間和右區間,一直遍歷到左右區間只有一個數時,則排序完成。具體見下圖:
接着遞歸左區間和右區間,左區間的基準元素爲 1,左指針元素爲 1,右指針元素爲 4,右區間的基準元素爲 7,左指針元素爲 7,右指針元素爲 8,一直遞歸到所有的子左區間和子右區間元素個數爲 1,則排序完成。具體代碼如下:
public int[] order(int[] num){
if (num.length == 0 || num.length == 1){
return num;
}
sort(num,0,num.length-1);
return num;
}
public void sort(int[] num,int l,int r){
// 證明左右區間元素爲 1
if (l > r){
return;
}
// 因爲 l,r 還需要給左右區間確定範圍,所以使用臨時變量移動指針
int index = l,left = l,right=r;
while (left < right){
// 先比較右邊
while (num[index]<=num[right] && left<right){
right--;
}
// 再比較左邊
while (num[index]>=num[left] && left<right){
left++;
}
if (left<right){
int temp = num[left];
num[left] = num[right];
num[right] = temp;
}
}
// 交換基準元素
int temp = num[index];
num[index] = num[left];
num[left] = temp;
index = left;
// 遞歸左區間
sort(num,l,index-1);
// 遞歸右區間
sort(num,index+1,r);
}
幾個常問的基礎排序就先分享到這了,喜歡的話關注我的個人微信公衆號,謝謝啦。