面試 10:玩轉 Java 選擇和插入排序,附冒泡最終源碼

昨天給你們講解了 Java 玩轉冒泡排序,你們必定以爲並無什麼難度吧,不知道大佬們玩轉了嗎?不知道你們有沒有多加思考,實際上在咱們最後的一種思路上,還能夠再繼續改進。java

咱們先看看昨天最終版本的代碼。面試

public class Test09 { private static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } private static void printArr(int[] arr) { for (int anArr : arr) { System.out.print(anArr + " "); } } private static void bubbleSort(int[] arr) { if (arr == null) return; // 定義一個標記 isSort,當其值爲 true 的時候表明已經有序。 boolean isSort; for (int i = 0; i < arr.length - 1; i++) { isSort = true; for (int j = 1; j < arr.length - i; j++) { if (arr[j - 1] > arr[j]) { swap(arr, j - 1, j); isSort = false; } } if (isSort) break; } } public static void main(String[] args) { int[] arr = {6, 4, 2, 1, 8, 3, 7, 9, 5}; bubbleSort(arr); printArr(arr); } } 

咱們用一個 boolean 變量 isSort 來判斷是否已經排序完成,當一整趟遍歷都沒有發生數據交換的時候,說明已經排序完成,直接 break 退出循環便可。算法

咱們試想一下這樣的場景:假設有 100 個數字的數組,僅僅前 10 個無序,後面 90 個均有序而且都大於前面 10 個數字。數組

咱們採用上面的終極算法能夠明顯看到,第一趟排序後,最後發生交換的位置一定大於 10,且這個位置以後的數據一定已經有序了,但咱們仍是會去作徒勞的 90 次遍歷,並且咱們還要遍歷 10 次!網絡

顯然咱們能夠找到這樣的思路,在第一次排序後,就記住最後發生交換的位置,第二次只要從數組頭部遍歷到這個位置就 OK 了。post

咱們不妨直接看看代碼實現:優化

public class Test09 { private static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } private static void printArr(int[] arr) { for (int anArr : arr) { System.out.print(anArr + " "); } } private static void bubbleSort(int[] arr) { if (arr == null) return; int flag = arr.length; int k; for (int i = 0; i < arr.length - 1; i++) { k = flag; flag = 0; for (int j = 1; j < k; j++) { if (arr[j - 1] > arr[j]) { swap(arr, j - 1, j); flag = j; } } if (flag == 0) break; } } public static void main(String[] args) { int[] arr = {6, 4, 1, 2, 3, 5, 7, 8, 9}; bubbleSort(arr); printArr(arr); } } 

其實算法也就那麼一回事兒,用心去理解它的原理,理解後,不管是用哪一種語言實現起來都是很是簡單的。那咱們今天就來看看另外兩種排序,選擇排序和插入排序。spa

選擇排序

選擇排序(Selection sort)是一種簡單直觀的排序算法。選擇排序之因此叫選擇排序就是在一次遍歷過程當中找到最小元素的角標位置,而後把它放到數組的首端。咱們排序過程都是在尋找剩餘數組中的最小元素,因此就叫作選擇排序。code

它的思想以下:orm

  1. 從待排序序列中,找到關鍵字最小的元素;起始假定第一個元素爲最小
  2. 若是最小元素不是待排序序列的第一個元素,將其和第一個元素互換;
  3. 從餘下的 N - 1 個元素中,找出關鍵字最小的元素,重複1,2步,直到排序結束。
 
圖片來源於網絡

選擇排序的主要優勢與數據移動有關。若是某個元素位於正確的最終位置上,則它不會被移動。選擇排序每次交換一對元素,它們當中至少有一個將被移到其最終位置上,所以對 n 個元素的表進行排序總共進行至多 n - 1 次交換。在全部的徹底依靠交換去移動元素的排序方法中,選擇排序屬於很是好的一種。

咱們來看看用 Java 是怎麼實現的。

public class Test09 { private static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } private static void printArr(int[] arr) { for (int anArr : arr) { System.out.print(anArr + " "); } } private static void selectSort(int[] arr) { if (arr == null) return; int i, j, min, len = arr.length; for (i = 0; i < len - 1; i++) { min = i; // 未排序的序列中最小元素的下標 for (j = i + 1; j < len; j++) { //在未排序元素中繼續尋找最小元素,並保存其下標 if (arr[min] > arr[j]) { min = j; } } if (min != i) swap(arr, min, i); } } public static void main(String[] args) { int[] arr = {6, 4, 2, 1, 8, 3, 7, 9, 5}; selectSort(arr); printArr(arr); } } 

上述 java 代碼能夠看出咱們除了交換元素並未開闢額外的空間,因此額外的空間複雜度爲 O(1)。

對於時間複雜度而言,選擇排序序冒泡排序同樣都須要遍歷 n(n-1)/2 次,可是相對於冒泡排序來講每次遍歷只須要交換一次元素,這對於計算機執行來講有必定的優化。可是選擇排序也是名副其實的慢性子,即便是有序數組,也須要進行 n(n-1)/2 次比較,因此其時間複雜度爲 O(n²)。

即使不管如何也要進行 n(n-1)/2 次比較,選擇排序還是不穩定的排序算法,咱們舉一個例子如:序列 5 8 5 2 9, 咱們知道第一趟選擇第 1 個元素 5 會與 2 進行交換,那麼原序列中兩個 5 的相對前後順序也就被破壞了。

選擇排序總結:

  1. 選擇排序的算法時間平均複雜度爲O(n²)。
  2. 選擇排序空間複雜度爲 O(1)。
  3. 選擇排序爲不穩定排序。

插入排序

對於插入排序,大部分資料都是使用撲克牌整理做爲例子來引入的,咱們打牌都是一張一張摸牌的,每摸到一張牌就會跟手裏全部的牌比較來選擇合適的位置插入這張牌,這也就是直接插入排序的中心思想,咱們先來看下動圖:

 
圖片來源於網絡

相信你們看完動圖之後大概知道了插入排序的實現思路了。那麼咱們就來講下插入排序的思想。

插入排序的思想

  1. 從第一個元素開始,該元素能夠認爲已經被排序
  2. 取出下一個元素,在已經排序的元素序列中從後向前掃描
  3. 若是該元素(已排序)大於新元素,將該元素移到下一位置
  4. 重複步驟 3,直到找到已排序的元素小於或者等於新元素的位置
  5. 將新元素插入到該位置後
  6. 重複步驟 2~5

理解上述思想其實並不難,咱們來看看用 Java 怎麼實現:

public class Test09 { private static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } private static void printArr(int[] arr) { for (int anArr : arr) { System.out.print(anArr + " "); } } private static void insertionSort(int[] arr) { if (arr == null) return; int j; int temp; for (int i = 1; i < arr.length; i++) { // 設置哨兵,拿出待插入的值 temp = arr[i]; j = i; // 而後尋找正確插入的位置 while (j > 0 && arr[j - 1] > temp) { arr[j] = arr[j - 1]; j--; } arr[j] = temp; } } public static void main(String[] args) { int[] arr = {6, 4, 2, 1, 8, 3, 7, 9, 5}; insertionSort(arr); printArr(arr); } } 

插入排序的時間複雜度和空間複雜度分析

對於插入的時間複雜度和空間複雜度,經過代碼就能夠看出跟選擇和冒泡來講沒什麼區別同屬於 O(n²) 級別的時間複雜度算法 ,只是遍歷方式有原來的 n n-1 n-2 ... 1,變成了 1 2 3 ... n 了。最終獲得時間複雜度都是 n(n-1)/2。

對於穩定性來講,插入排序和冒泡同樣,並不會改變原有的元素之間的順序,若是碰見一個與插入元素相等的,那麼把待插入的元素放在相等元素的後面。因此,相等元素的先後順序沒有改變,從原無序序列出去的順序還是排好序後的順序,因此插入排序是穩定的。

對於插入排序這裏說一個很是重要的一點就是:因爲這個算法能夠提早終止內層比較( arr[j-1] > arr[j])因此這個排序算法頗有用!所以對於一些 NlogN 級別的算法,後邊的歸併和快速都屬於這個級別的,算法來講對於 n 小於必定級別的時候(Array.sort 中使用的是47)均可以用插入算法來優化,另外對於近乎有序的數組來講這個提早終止的方式就顯得更加又有優點了。

插入排序總結:

  1. 插入排序的算法時間平均複雜度爲O(n²)。
  2. 插入排序空間複雜度爲 O(1)。
  3. 插入排序爲穩定排序。
  4. 插入排序對於近乎有序的數組來講效率更高,插入排序可用來優化高級排序算法

到如今,咱們的三種簡單排序就告一段落了,下面咱們將直接進入 歸併排序 和 快速排序 的講解。這兩個算法也是面試上的常客了,因此你準備好了麼?

文章參考來源:http://www.javashuo.com/article/p-reikarpv-bb.html

相關文章
相關標籤/搜索