1. 程式人生 > 實用技巧 >排序-快速排序

排序-快速排序

https://en.wikipedia.org/wiki/Quicksort

快速排序

  • 原理:通過中位值(pivot)進行比較來進行不斷分割槽,直到所有的區間達到最小(容量為1)時,此時結果就是有序的; 在執行對比操作時是從低位到高位的對比操作,通過不斷摘除中間值的方式來實現排序
  • 快速排序所有的移動操作都是在原陣列中進行操作的,並不需要額外的陣列空間,因此屬於原地排序演算法
  • 穩定性:由於對於相同元素也是需要移動的,因此有可能出現相同元素排序前後相對位置不一致;所以快速排序是不穩定的排序

快排體現了分治的思想,不過重點在於分割槽,分割槽的目的是為了找到中心點的位置,排除中心點元素,對左右區間繼續分割槽步驟;

快排通過每次確認每個分割槽點中心位置,並對於分割槽排除已確定位置的中心元素來實現排序的目的

public interface Sort<T extends Comparable<T>> {
    /**
     * 對陣列進行排序
     *
     * @param array
     * @param flag  true 代表的是正序;false代表的是逆序
     */
    void sort(T[] array, boolean flag);
}

public class QuickSort<T extends Comparable<T>> implements
Sort<T> { /** * 快排的主要思想在於分割槽,通過不斷的分割槽達到排序的效果 * 5 4 3 2 1 * pivot(中心點) * low->開始遍歷當前區間high * i,j ; 當存在 array[j] < pivot 時, 交換 array[i],array[j] = array[j],array[i] (golang語法),此時i + 1,後移一位,j繼續往後移動直到high,交換當前i和high,array[i],array[high] = array[high],array[i] , 此時i就作為當前分割槽的中心點 * 第一次分割槽結束後的結果為 ,當前pivot 的索引為 0(i) ;分成的區間為 [][1][4,3,2,5];由中心點繼續分割槽 * *
@param array * @param flag true 代表的是正序;false代表的是逆序 */ @Override public void sort(T[] array, boolean flag) { sort(array, 0, array.length - 1, flag); } /** * 對於快速排序實際就是一個不斷遞迴分割槽的過程 * pivot元素初始時是當前分割槽的最高位元素,high為pivot元素前一個位置,low為最低位 * 通過low high雙指標實現對比交換 * * @param array * @param low * @param high * @param flag true代表升序,false代表降序 */ public void sort(T[] array, int low, int high, boolean flag){ // 如果低位索引值大於等於高位索引值,則表示沒有必要在進行繼續分割槽 if (low >= high) { return; } // 返回分割槽中間值索引 // int pivotIndex = partition(array,low,high,flag); // 雙指標移動, low和high int pivotIndex = partition2(array,low,high,flag); // 對於左右分割槽都不會包含中間元素 // 遞迴 左側分割槽 sort(array, low, pivotIndex - 1, flag); // 遞迴 右側分割槽 sort(array, pivotIndex + 1, high, flag); } // 雙指標,通過塊慢指標均從低位移動, 對於[0,i]區間可以保證的是其中的元素肯定都大於等於或小於等於中間元素 public int partition(T[] array, int low, int high, boolean flag) { // 最高位作為中心點 T pivot = array[high]; // 待交換位置 int i = low; // 遍歷遊標 int j = low; for (; j < high; j++) { // 如果為升序,當存在array[j] =< pivot,將array[j]放到前面和i進行交換位置,並將i後移一位 if ((flag && array[j].compareTo(pivot) < 1) || (!flag && array[j].compareTo(pivot) > 0)) { // 交換i和j的元素 T temp = array[i]; array[i] = array[j]; array[j] = temp; // 將i後移一位 i++; } } // 不管i是否變化,都要將中心點元素和當前i所在位置元素進行交換 // 交換當前high 和 i 的元素,將中間點的元素移動到中間索引位置 array[high] = array[i]; array[i] = pivot; // 返回中間索引 return i; } // 採用雙指標移動, 低位指標從低往高位移動,高位指標從高位往低位移動 public int partition2(T[] array, int low, int high, boolean flag) { // 將最高位設定為pivot元素 T pivot = array[high]; while (low < high) { if ((flag && array[low].compareTo(pivot) == -1) || (!flag && array[low].compareTo(pivot) == 1)) { low++; } else { // 交換高位和低位元素 array[high--] = array[low]; // 此時high已經前移一位 array[low] = array[high]; // 此時high的位置已經出現空閒插槽 } } // 將中間元素放入空閒插槽中 array[high] = pivot; return high; } public static void main(String[] args) { Integer[] values = new Integer[]{3,7,8,5,2,1,5,4}; Sort<Integer> integerInsertSort = new QuickSort<>(); integerInsertSort.sort(values, true); Stream.of(values).forEach(System.out::print); System.out.println(); integerInsertSort.sort(values, false); Stream.of(values).forEach(System.out::print); } }

對於快排的分割槽操作有兩種操作方式

  1. 雙指標的快慢指標方式(對應方法partition),都是從低位到高位的遍歷順序,使用快指標指向的元素和pivot元素對比,對於滿足排序條件的元素,將塊慢指標指向的元素進行交換操作,並同時向後移動快慢指標;反之對於不滿足排序條件的元素,只移動塊指標;最後,慢指標元素和pivot元素進行交換,並返回慢指標索引為中間值索引;通過慢指標保證了[0,慢指標索引]區間中的元素都是大於等於或小於等於pivot元素;
  2. 雙指標的低位和高位指標方式(對應方法partition2),低位指標從低到高進行遍歷,高位指標從高到低進行遍歷,使用低位指標指向的元素和pivot元素進行對比,當符合排序條件時,只向後移動低位指標;當不滿足排序條件時,將低位指標指向的元素寫入到高位指標索引位置,將高位指標前移一位,將當前高位指標指向的元素寫入到低位指標索引位置,低位指標不移動,此時高位指標實際指向的就是一個空閒插槽; 當高位指標和低位指標相遇時,表示分割槽結束,將中間元素寫入到高位指標指向位置;此時高位指標就是中間元素指標

"未完待續-----剩餘使用python+Matplotlib 繪製視覺化動畫"

https://zhuanlan.zhihu.com/p/38157591