其中 冒泡,選擇,歸併,快速,希爾,堆排序屬於比較排序
git
穩定性理解算法
若是相等的兩個元素,在排序先後的相對位置保持不變,那麼這是穩定的排序算法。api
原地算法(In-place Algorithm)理解數組
定義:不依賴額外的資源或依賴少數的額外資源(空間複雜度較低),僅依靠輸出覆蓋輸入(例如直接對輸入的數組進行操做)app
用於提供測試數據與測試代碼正確性
public class Asserts { public static void test(boolean value) { try { if (!value) throw new Exception("測試未經過"); } catch (Exception e) { e.printStackTrace(); } } }
public class Integers { /** 生成隨機數 */ public static Integer[] random(int count, int min, int max) { if (count <= 0 || min > max) return null; Integer[] array = new Integer[count]; int delta = max - min + 1; for (int i = 0; i < count; i++) { array[i] = min + (int)(Math.random() * delta); } return array; } /** 合併兩個數組 */ public static Integer[] combine(Integer[] array1, Integer[] array2) { if (array1 == null || array2 == null) return null; Integer[] array = new Integer[array1.length + array2.length]; for (int i = 0; i < array1.length; i++) { array[i] = array1[i]; } for (int i = 0; i < array2.length; i++) { array[i + array1.length] = array2[i]; } return array; } public static Integer[] same(int count, int unsameCount) { if (count <= 0 || unsameCount > count) return null; Integer[] array = new Integer[count]; for (int i = 0; i < unsameCount; i++) { array[i] = unsameCount - i; } for (int i = unsameCount; i < count; i++) { array[i] = unsameCount + 1; } return array; } /** * 生成頭部和尾部是升序的數組 * disorderCount:但願多少個數據是無序的 */ public static Integer[] headTailAscOrder(int min, int max, int disorderCount) { Integer[] array = ascOrder(min, max); if (disorderCount > array.length) return array; int begin = (array.length - disorderCount) >> 1; reverse(array, begin, begin + disorderCount); return array; } /** * 生成中間是升序的數組 * disorderCount:但願多少個數據是無序的 */ public static Integer[] centerAscOrder(int min, int max, int disorderCount) { Integer[] array = ascOrder(min, max); if (disorderCount > array.length) return array; int left = disorderCount >> 1; reverse(array, 0, left); int right = disorderCount - left; reverse(array, array.length - right, array.length); return array; } /** * 生成頭部是升序的數組 * disorderCount:但願多少個數據是無序的 */ public static Integer[] headAscOrder(int min, int max, int disorderCount) { Integer[] array = ascOrder(min, max); if (disorderCount > array.length) return array; reverse(array, array.length - disorderCount, array.length); return array; } /** * 生成尾部是升序的數組 * disorderCount:但願多少個數據是無序的 */ public static Integer[] tailAscOrder(int min, int max, int disorderCount) { Integer[] array = ascOrder(min, max); if (disorderCount > array.length) return array; reverse(array, 0, disorderCount); return array; } /** 升序生成數組 */ public static Integer[] ascOrder(int min, int max) { if (min > max) return null; Integer[] array = new Integer[max - min + 1]; for (int i = 0; i < array.length; i++) { array[i] = min++; } return array; } /** 降序生成數組 */ public static Integer[] descOrder(int min, int max) { if (min > max) return null; Integer[] array = new Integer[max - min + 1]; for (int i = 0; i < array.length; i++) { array[i] = max--; } return array; } /** 反轉數組 */ private static void reverse(Integer[] array, int begin, int end) { int count = (end - begin) >> 1; int sum = begin + end - 1; for (int i = begin; i < begin + count; i++) { int j = sum - i; int tmp = array[i]; array[i] = array[j]; array[j] = tmp; } } /** 複製數組 */ public static Integer[] copy(Integer[] array) { return Arrays.copyOf(array, array.length); } /** 判斷數組是否升序 */ public static boolean isAscOrder(Integer[] array) { if (array == null || array.length == 0) return false; for (int i = 1; i < array.length; i++) { if (array[i - 1] > array[i]) return false; } return true; } /** 打印數組 */ public static void println(Integer[] array) { if (array == null) return; StringBuilder string = new StringBuilder(); for (int i = 0; i < array.length; i++) { if (i != 0) string.append("_"); string.append(array[i]); } System.out.println(string); } }
public class Times { private static final SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss.SSS"); public interface Task { void execute(); } public static void test(String title, Task task) { if (task == null) return; title = (title == null) ? "" : ("【" + title + "】"); System.out.println(title); System.out.println("開始:" + fmt.format(new Date())); long begin = System.currentTimeMillis(); task.execute(); long end = System.currentTimeMillis(); System.out.println("結束:" + fmt.format(new Date())); double delta = (end - begin) / 1000.0; System.out.println("耗時:" + delta + "秒"); System.out.println("-------------------------------------"); } }
public abstract class Sort<T extends Comparable<T>> implements Comparable<Sort<T>> { /** 目標數組 */ protected T[] array; /** 比較次數 */ private int cmpCount; /** 交換次數 */ private int swapCount; /** 執行時間 */ private long time; /** 小數格式化 */ private DecimalFormat fmt = new DecimalFormat("#.00"); /** 預處理 */ public void sort(T[] array) { if (array == null || array.length < 2) return; this.array = array; long begin = System.currentTimeMillis(); sort(); time = System.currentTimeMillis() - begin; } /** 目標方法 */ protected abstract void sort(); /** * 比較數組下標對應的值 * * 返回值等於0,表明 array[index1] == array[index2] * 返回值小於0,表明 array[index1] < array[index2] * 返回值大於0,表明 array[index1] > array[index2] */ protected int cmp(int index1, int index2) { cmpCount++; return array[index1].compareTo(array[index2]); } /** 比較值 */ protected int cmp(T value1, T value2) { cmpCount++; return value1.compareTo(value2); } /** 交換值 */ protected void swap(int index1, int index2) { swapCount++; T tmp = array[index1]; array[index1] = array[index2]; array[index2] = tmp; } /** 穩定性測試 */ @SuppressWarnings("unchecked") private boolean isStable() { Student[] students = new Sort.Student[20]; for (int i = 0; i < students.length; i++) { //(0,10) (10,10) (20,10) (30,10) students[i] = new Student(i * 10, 10); } sort((T[]) students);//只會對年齡進行排序 for (int i = 1; i < students.length; i++) { int score = students[i].score; int prevScore = students[i - 1].score; if (score != prevScore + 10) return false; } return true; } private static class Student implements Comparable<Student>{ Integer score; Integer age; public Student(Integer score, Integer age) { this.score = score; this.age = age; } @Override public int compareTo(Student o) { return age - o.age; } } /** 排序方式 */ @Override public int compareTo(Sort o) { int result = (int)(time - o.time); if(result != 0) return result; result = cmpCount - o.cmpCount; if(result != 0) return result; return swapCount - o.swapCount; } @Override public String toString() { return "【" + getClass().getSimpleName() + "】n" + "交換次數 ==> " + numberString(swapCount) + "n" + "比較次數 ==> " + numberString(cmpCount) + "n" + "執行時間 ==> " + time * 0.001 + "s" + "n" + "穩定性 ==> " + isStable() + "n" + "================================="; } /** 數字格式化 */ private String numberString(int number) { if (number < 10000) return "" + number; if (number < 100000000) { return fmt.format(number / 10000.0) + "萬"; } return fmt.format(number / 100000000.0) + "億"; } }
public void sort() { for (int eIndex = array.length - 1; eIndex > 0; eIndex--) { for (int i = 1; i <= eIndex; i++) { if (cmp(i, i - 1) < 0) { swap(i, i - 1); } } } }
優化方案:若是序列已經徹底有序,能夠提早終止冒泡排序dom
缺點:只有當徹底有序時纔會提早終止冒泡排序,機率很低ide
public void sort() { for (int eIndex = array.length - 1; eIndex > 0; eIndex--) { boolean sorted = true; for (int i = 1; i <= eIndex; i++) { if (cmp(i,i - 1) < 0) { swap(i, i - 1); sorted = false; } } if (sorted) break; } }
優化方案:若是序列尾部已經局部有序,能夠記錄最後一次交換的位置,減小比較次數工具
public class BubbleSort<T extends Comparable<T>> extends Sort<T> { /** * 優化方式二:若是序列尾部已經局部有序,能夠記錄最後依次交換的位置,減小比較次數 * 爲何這裏sortedIndex爲1(只要保證 eIndex-- > 0 便可)? * => 若是sortedIndex爲eIndex,當數組第一次就徹底有序時,就退回到最初的版本了 * => 若是sortedIndex爲1,當數組第一次就徹底有序時,一輪掃描就結束了! * */ @Override public void sort() { for (int eIndex = array.length - 1; eIndex > 0; eIndex--) { int sortedIndex = 1; //記錄最後一次交換的下標位置 for (int i = 1; i <= eIndex; i++) { if (cmp(i, i - 1) < 0) { swap(i, i - 1); sortedIndex = i; } } eIndex = sortedIndex; } } }
注意:稍有不慎,穩定的排序算法也能被寫成不穩定的排序算法,以下冒泡排序是不穩定的
public void sort() { for (int eIndex = array.length - 1; eIndex > 0; eIndex--) { for (int i = 1; i <= eIndex; i++) { if (cmp(i, i - 1) <= 0) { swap(i, i - 1); } } } }
這裏以選最小元素爲例
public class SelectionSort<T extends Comparable<T>> extends Sort<T> { @Override public void sort() { for (int eIndex = array.length - 1; eIndex > 0; eIndex--) { int maxIndex = 0; for (int i = 1; i <= eIndex; i++) { //注意:爲了穩定性,這裏要寫 <= if (cmp(maxIndex, i) <= 0) { maxIndex = i; } } if(maxIndex != eIndex) swap(maxIndex, eIndex); } } }
選擇排序是否還有優化的空間? => 使用堆來選擇最大值
堆排序能夠認爲是對選擇排序的一種優化性能
重複執行如下操做,直到堆的元素數量爲1學習
public class HeapSort<T extends Comparable<T>> extends Sort<T> { /** 記錄堆數據 */ private int heapSize; @Override protected void sort() { // 原地建堆(直接使用數組建堆) heapSize = array.length; for (int i = (heapSize >> 1) - 1; i >= 0; i--) { siftDown(i); } while (heapSize > 1) { // 交換堆頂元素和尾部元素 swap(0, --heapSize); // 對0位置進行siftDown(恢復堆的性質) siftDown(0); } } /** 堆化 */ private void siftDown(int index) { T element = array[index]; int half = heapSize >> 1; while (index < half) { // index必須是非葉子節點 // 默認是左邊跟父節點比 int childIndex = (index << 1) + 1; T child = array[childIndex]; int rightIndex = childIndex + 1; // 右子節點比左子節點大 if (rightIndex < heapSize && cmp(array[rightIndex], child) > 0) { child = array[childIndex = rightIndex]; } // 大於等於子節點 if (cmp(element, child) >= 0) break; array[index] = child; index = childIndex; } array[index] = element; } }
@SuppressWarnings({"rawtypes","unchecked"}) public class SortTest { public static void main(String[] args) { Integer[] arr1 = Integers.random(10000, 1, 20000); testSort(arr1, new SelectionSort(), new HeapSort(), new BubbleSort()); } static void testSort(Integer[] arr,Sort... sorts) { for (Sort sort: sorts) { Integer[] newArr = Integers.copy(arr); sort.sort(newArr); //檢查排序正確性 Asserts.test(Integers.isAscOrder(newArr)); } Arrays.sort(sorts); for (Sort sort: sorts) { System.out.println(sort); } } }
public class InsertionSort<T extends Comparable<T>> extends Sort<T> { @Override protected void sort() { for (int i = 1; i < array.length; i++) { int cur = i; while(cur > 0 && cmp(cur,cur - 1) < 0) { swap(cur,cur - 1); cur--; } } } }
什麼是逆序對? => 數組 [2,3,8,6,1] 的逆序對爲:<2,1> < 3,1> <8,1> <8,6> <6,1>
插入排序的時間複雜度與逆序對的數量成正比關係
時間複雜度最高以下:O(n^2)
優化思路 => 將交換改成挪動
注意:逆序對越多,該優化越明顯
public class InsertionSort<T extends Comparable<T>> extends Sort<T> { @Override protected void sort() { for (int i = 1; i < array.length; i++) { int cur = i; T val = array[cur]; while(cur > 0 && cmp(val,array[cur - 1]) < 0) { array[cur] = array[cur - 1];//優化重點在這裏 cur--; } array[cur] = val; } } }
優化思路 => 將交換改成二分搜索(較少比較次數)
二分搜索理解
如何肯定一個元素在數組中的位置?(假設數組裏全是整數)
思路
實例
/** 二分搜索-基本實現 * 查找val在有序數組arr中的位置,找不到就返回-1 */ private static int indexOf(Integer[] arr,int val) { if(arr == null || arr.length == 0) return -1; int begin = 0; //注意這裏end設計爲arr.length便於求數量(end - begin) int end = arr.length; while (begin < end) { int mid = (begin + end) >> 1; if(val < arr[mid]) { end = mid; } else if(val > arr[mid]) { begin = mid + 1; } else { return mid; } } return -1; }
二分搜索(Binary Search)優化實現
適合於插入排序的二分搜索必須知足:要求二分搜索返回的插入位置是第1個大於 val 的元素位置
實現思路
*/
private static int search(Integer[] arr,int val) { if(arr == null || arr.length == 0) return -1; int begin = 0; int end = arr.length; while (begin < end) { int mid = (begin + end) >> 1; if(val < arr[mid]) { end = mid; } else { begin = mid + 1; } } return begin; }
插入排序最終實現
注意:使用了二分搜索後,只是減小了比較次數,但插入排序的平均時間複雜度依然是O(n^2)
public class InsertionSort<T extends Comparable<T>> extends Sort<T> { /** 優化 => 二分搜索 */ @Override protected void sort() { for (int begin = 1; begin < array.length; begin++) { //這裏爲何傳索引而不是傳值? // => 傳索引還能夠知道前面已經排好序的數組區間:[0,i) insert(begin,search(begin)); } } /** 將source位置的元素插入到dest位置 */ private void insert(int source,int dest) { //將[dest,source)範圍內的元素往右邊挪動一位 T val = array[source]; for (int i = source; i > dest; i--) { array[i] = array[i - 1]; } //插入 array[dest] = val; } private int search(int index) { T val = array[index]; int begin = 0; int end = index; while (begin < end) { int mid = (begin + end) >> 1; if(cmp(val,array[mid]) < 0) { end = mid; } else { begin = mid + 1; } } return begin; } }
merge
大體想法
細節
@SuppressWarnings("unchecked") public class MergeSort<T extends Comparable<T>> extends Sort<T> { private T[] leftArr; @Override protected void sort() { leftArr = (T[]) new Comparable[array.length >> 1]; sort(0, array.length); } /** 對 [begin,end) 位置的元素進行歸併排序 */ private void sort(int begin, int end) { if (end - begin < 2) return; int mid = (begin + end) >> 1; sort(begin, mid); sort(mid, end); merge(begin, mid, end); } /** 將 [begin,mid) 和 [mid,end) 範圍的序列合併成一個有序序列 */ private void merge(int begin, int mid, int end) { int li = 0, le = mid - begin; int ri = mid, re = end; int ai = begin; //備份左邊數組 for (int i = 0; i < le; i++) { leftArr[i] = array[begin + i]; } //若是左邊尚未結束(狀況一) while (li < le) { //當 ri < re 不成立,就會一直leftArr挪動(狀況二) if (ri < re && cmp(array[ri],leftArr[li]) < 0) { array[ai++] = array[ri++]; } else { //注意穩定性 array[ai++] = leftArr[li++]; } } } }
複雜度分析
T(n) = sort() + sort() + merge() => T(n) = T(n/2) + T(n/2) + O(n) => T(n) = 2T(n/2) + O(n) //因爲sort()是遞歸調用,用T表示,因爲T(n/2)很差估算,如今要理清T(n)與O(n)之間的關係 T(1) = O(1) T(n)/n = T(n/2) / (n/2) + O(1) //令S(n) = T(n)/n S(1) = O(1) S(n) = S(n/2) + O(1) = S(n/4) + O(2) = S(n/8) + O(3) = S( n/(2^k) ) + O(k) = S(1) + O(log^n) = O(lon^n) T(n) = n*S(n) = O(nlog^n) => 歸併排序時間複雜度:O(nlog^n)
常見遞推式
總結
更多關於Java基礎的學習能夠加入個人十年Java學習園地,技術交流,答疑解惑,資源共享。