1、排序算法介紹‘java
衆所周知,經常使用的排序算法有冒泡排序、選擇排序、插入排序、歸併排序、快速排序、堆排序等,常見的比較其特色的結論網上很容易得到,好比時間複雜度、空間複雜度、穩定性等等。可是光了解這幾個排序的特色是遠遠不夠的,技術不是死記硬背,瞭解排序的原理是很是有必要的。接下來,筆者就按照本身的理解,對以上幾個算法進行儘量簡單易懂的概括,若是其中有錯誤的地方,也請在博客底下留言,歡迎探討。在討論算法以前,也請讀者瞭解一下時間複雜度、空間複雜度、穩定性等概念,這有利於本文的理解。算法
2、各排序算法描述數組
這個排序算法應該是最基礎的算法,大體的思路就是經過從首位數依次往下比較大小肯定最大值,使最大值放到末尾的過程。其排序過程能夠這樣描述:假設數組arr[n],取第0個位置數,和第1位置個數相比,若是arr[0>arr[1],則交換,不然不交換,而後取第1位置數和第2位置數比較,逐次比較下去,經過較大者交換的思路,第一輪把最大數放到n位置。而後再次從0位置數取到n-1位置數,獲得第二大的數放在n-1位置,依次循環。因此最後耗費的時間複雜度大體能夠經過n-1 + n-2 + n-3 +... +1 累加獲得n的2次方。而空間複雜度則爲交換數組須要的O(1)。核心代碼以下:設計
public static void bubbleSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int e = arr.length - 1; e > 0; e--) { for (int i = 0; i < e; i++) { if (arr[i] > arr[i + 1]) { swap(arr, i, i + 1); } } } }
選擇排序的思路與冒泡排序大同小異,即把數組中的最小值選出來放在數組第1個位置,而後選出第二小的數組,放在數組第2個位置。依次選擇,交換位置,交換完把固定的值排除可變範圍。詳細就再也不贅述,其空間複雜度爲O(n^2),空間複雜度爲O(1)。核心代碼以下:3d
public static void selectionSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int i = 0; i < arr.length - 1; i++) { int minIndex = i; for (int j = i + 1; j < arr.length; j++) { minIndex = arr[j] < arr[minIndex] ? j : minIndex; } swap(arr, i, minIndex); } }
插入排序的思路是,經過一個位置的數與它前一個面位置數比較,若是當前數小於前一個位置數,則實現前插操做(交換位置),而後繼續比較交換完位置的數與前一個數,直到第一個數。其排序過程大體以下:假設數組arr[n],取下標第1個數,看arr[1]是否小於arr[0],若小於則將arr[1]插入到arr[0]位置,arr[0]日後;依次取第2個數,與第1個數比較,而後取第一個數與第0個數比較,若比當前位置小,則swap ,若大於,則不繼續。這樣依次取到第n-1個數便可。其時間複雜度並非固定不變的,最理想狀況下,時間複雜度爲O(n),最差狀況下,時間複雜度爲O(n),而空間複雜度則固定爲O(1),其核心代碼以下:code
public static void insertionSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int i = 1; i < arr.length; i++) { for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) { swap(arr, j, j + 1); } } }
歸併排序,顧名思義,用到了遞歸的概念,而在每個遞歸中,都會有一個BaseCase,及不會往下遞歸的基礎處理狀況。在歸併排序中,大概的思路是取一個數組的左半邊數組進行排序,而後取右半邊數組進行排序,最後把左右兩邊進行排序獲得最終的數組。其中左半邊數組能夠繼續遞歸取其中的左半邊和右半邊數組排序,右半邊數組亦如此,最終BaseCase 是當左下標L 與 右下標R 相同時返回。其中把左半邊數組與右半邊數組進行整合的過程,是一個外排的過程,即取左半邊+右半邊數組size和大小的輔助數組,而後把左右數組比較大小放入便可(由於左右半邊的數組已經通過排序,不須要考慮數組內排序,因此只要考慮兩個數組的前後順序就能夠了)。具體的遞歸代碼實現能夠這樣寫:blog
public static void mergeSort(int[] arr, int l, int r) { if (l == r) { return; } int mid = l + ((r - l) >> 1); mergeSort(arr, l, mid); mergeSort(arr, mid + 1, r); merge(arr, l, mid, r); }
外排過程的代碼能夠按如下寫:排序
public static void merge(int[] arr, int l, int m, int r) { int[] help = new int[r - l + 1]; int i = 0; int p1 = l; int p2 = m + 1; while (p1 <= m && p2 <= r) { help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } while (p1 <= m) { help[i++] = arr[p1++]; } while (p2 <= r) { help[i++] = arr[p2++]; } for (i = 0; i < help.length; i++) { arr[l + i] = help[i]; } }
快速排序是工程上應用較多的排序算法,其排序的思路大體以下:假設有一個partition過程,能夠作到一個左下標l 右下標r 的數組中,取其中任意一個數做爲基準值,使得通過partition過程後,該數左邊爲小於該數的值,右邊爲大於該數的值,而後返回該數的下標值。假如這樣的partition過程實現的話,將得到的下標從新把數組拆分紅兩個數組而後繼續partition,一直遞歸到BaseCase,就能夠完成整個數組的排序了。遞歸
基於這樣的思路,很快就能夠設計出partition的排序過程:假設數組arr[n],左邊界爲l,右邊界爲r,取r上的值做爲基準值,劃分小於區(起始爲l-1),等於區,大於區(起始爲r),從l開始判斷,若當前值小於基準值,則把當前值與小於區位置後一個數交換,小於區位置++,當前值位置++;若當前值等於基準值,當前值位置++;若當前值大於基準值,則把當前值與大於區前一個數位置交換,大於區位置--,當前位置不動,繼續判斷。直到當前值遇到大於區邊界。達到邊界條件後,將基準值下標跟大於區第一個位置交換,大於區下標++,而後基準值下標便可。開發
其中快速排序每每又分爲經典快速排序和隨機快速排序,這兩個的區別就在於取得基準值是最後一個值仍是隨機的選取數組中的一個值,具體的代碼細節以下所示:
說到堆排序,不得不先描述幾個概念。堆排序的過程當中離不開建堆的過程,其中創建的堆,其實能夠類比於一個徹底二叉樹結構,即按照從上到下、從左到右的順序所有填滿的樹。而對於一個數組中,造成堆結構後,父子節點的下標值能夠有這樣的規則:節點i的左子節點下標爲2 * i+1,右子節點的下標爲2 * i +2,父節點的下標爲(i-1)/2。
理解了以上幾個概念,堆排序的過程就能夠描述爲一個建堆的過程加上一個堆逐步調整的過程。建堆的過程即按照排序規則(從小到大/從大到小)創建對應的大根堆/小根堆,接下來以大根堆爲例描述堆排序的大體過程。從數組第一個位置往下,進行一個建堆的過程,即把每一個節點跟本身的父節點比較,若子節點大於父節點,則交換下標,如此獲得一個大根堆。
建完堆以後,就會進行對應的堆調整的過程,即每調整一個流程,能夠肯定一個數做爲最大值放在數組的最後,而後調整範圍減一。具體過程以下:創建完堆之後,把堆頂點跟最後一個數交換,可變數組size範圍-1,而後從堆頂點標準值k跟兩個孩子中大的比較,若是堆頂點小於最大孩子值,則交換,換完後該值k繼續往下比較。比較完,把堆頂點跟最後一個數交換,可變數組size範圍-1;依次往下便可。其代碼實現以下:
3、總結
對於這幾個排序算法,可能最易理解的是冒泡排序和選擇排序,可是由於其時間複雜度較大,因此在實際運用中不多用到。而下面的快排、堆排雖然思路上沒有那麼簡單,可是在實際運用中卻能夠提高排序的效率。讀者也能夠在以後的開發過程當中,結合實際進行運用,但願此文能夠給你帶來一點幫助。