排序演算法:快速排序

七月回來繼續發部落格發表於2018-07-18

Sorting Algorithms:Quick Sort

前言

該部落格用於本弱雞複習鞏固,打牢基礎,還望各大佬不吝賜教。

基本思路

1.對一個未排序序列,假設從該序列中的元素中取一個基準值pivotkey,將小於pivotkey放左邊,大於pivotkey放右邊;
2.接著以該k為中間,左右兩邊的分割作為新的序列,重新進行1操作。
快排因為用到了遞迴操作,所以在簡單排序中效能不如直接插入排序,
而在大量資料排序時,遞迴產生的效能影響對於演算法的整體效能優勢可以忽略。

動圖示例

Quick Sort
Quick Sort

演算法複雜度分析

平均 最壞 最好 穩定性 空間複雜度
O(nlogn) O(n^2) O(nlogn) 不穩定 O(logn)

p.s.

  • 最壞情況:待排序為正序或逆序,這樣每次分割後的子序列一個之比上一次序列少一個元素,一個為空。如 1 2 3 4 5 pivotkey=1;分割後一個序列為 2 3 4 5 一個為空,最終O(n^2)
  • 最好情況:每一次分割都能平分,很均勻 O(nlogn)
  • 平均情況:O(n*logn) 數學歸納法
  • 空間複雜度:主要由遞迴而產生的對棧空間的影響
  • 最好:O(logn)
  • 最壞:O(n)
  • 平均:O(logn)
  • 穩定性 不穩定 比較和交換是跳躍進行的

程式碼實現

import java.util.Arrays;
import java.util.Random;

/**
 * QuickSort
 * 1.對一個未排序序列,假設從該序列中的元素中取一個基準值pivotkey,將<pivotkey放左邊 >pivotkey放右邊
 * 2.接著以該k為中間,左右兩邊的分割作為新的序列,重新進行1操作
 * <p>
 * 快排因為用到了遞迴操作,所以在簡單排序中效能不如直接插入排序
 * 而在大量資料排序時,遞迴產生的效能影響對於演算法的整體效能優勢可以忽略
 * <p>
 * 時間複雜度分析:
 * 最壞情況:待排序為正序或逆序,這樣每次分割後的子序列一個之比上一次序列少一個元素,一個為空
 * 如 1 2 3 4 5 pivotkey=1;分割後一個序列為 2 3 4 5 一個為空
 * 最終O(n^2)
 * 最好情況:每一次分割都能平分,很均勻 O(n*logn)
 * 平均情況:O(n*logn) 數學歸納法
 * 空間複雜度:主要有地櫃而產生的對棧空間的影響
 * 最好:O(logn)
 * 最壞:O(n)
 * 平均:O(logn)
 * 穩定性 不穩定 比較和交換是跳躍進行的
 * <p>
 * 如何合理基準值pivotkey
 * 該值的取值對該演算法有相當影響,若pivotkey取到了最大或最小,都會增加演算法複雜度,影響效能
 * 1.隨機選取,在待排序列中隨機選取,以降低取到最大或最小值的概率
 * 2.三數取中,在待排序列的左端,中間,右端去三個值選取中位數,節省隨機數產生的時間開銷,以降低取到最大或最小值的概率
 * 三數取中時,比較的同時應將三個元素按中間,小,大的順序重新排好位置
 * 3.九數取中,三次取樣,每次取三個數,取它們的中位數,再取三個中位數的中位數
 */

public class QuickSort {
    public static void main(String[] args) {
        int[] a = new int[10];
        boolean flag = true;
        //random array
        for (int i = 0; i < a.length; i++) {
            Random rd = new Random();
            a[i] = rd.nextInt(10);
        }

        System.out.println("Random Array :");
        System.out.println(Arrays.toString(a));
        System.out.println();
        System.out.println("Quick Sort :");

        //快速排序開始
        quickSort(a, 0, a.length - 1);

        System.out.println(Arrays.toString(a));
    }

    /**
     * @param a
     * @param low
     * @param high
     */

    public static void quickSort(int[] a, int low, int high) {
        //該值定義了從哪個位置開始分割序列
        int pivot;
        //當high-low大於某一值時適合快速排序
        //if ((high - low) >MAX_LENGTH_INSERT_SORT) 該值取7或50
        if (low < high) {
            //partition方法對序列進行排序
            pivot = partition(a, low, high);
            //分割兩個序列繼續進行快排操作
            quickSort(a, low, pivot - 1);
            quickSort(a, pivot + 1, high);
        }
    }

    /**
     * @param a
     * @param low
     * @param high
     * @return
     */

    public static int partition(int[] a, int low, int high) {
        //取每個序列的第一個值作為基準值
        int pivotkey = a[low];
        while (low < high) {
            //從序列的右邊開始往左遍歷,直到找到小於基準值的元素
            while (high > low && a[high] >= pivotkey) {
                high--;
            }
            //將元素直接賦予給左邊第一個,即pivotkey所在的位置
            a[low] = a[high];
            //a[high] = pivotkey;
            //從序列的左邊邊開始往右遍歷,直到找到大於基準值的元素
            while (high > low && a[low] <= pivotkey) {
                low++;
            }
            //此時的a[high]<pivotkey,已經被賦予到左邊,所以可以將元素賦予給a[high]
            a[high] = a[low];
            //a[low] = pivotkey;


        }
        //最後的low是基準值所在的位置
        a[low] = pivotkey;
        return low;
    }


}
複製程式碼

如何合理取基準值pivotkey

  • 該值的取值對該演算法有相當影響,若pivotkey取到了最大或最小,都會增加演算法複雜度,影響效能。
  • 隨機選取,在待排序列中隨機選取,以降低取到最大或最小值的概率。
  • 三數取中,在待排序列的左端,中間,右端去三個值選取中位數,節省隨機數產生的時間開銷,以降低取到最大或最小值的概率。
    三數取中時,比較的同時應將三個元素按中間,小,大的順序重新排好位置。
  • 九數取中,三次取樣,每次取三個數,取它們的中位數,再取三個中位數的中位數。

參考

GeeksforGeeks:https://www.geeksforgeeks.org/quick-sort/

十大經典排序演算法:https://www.cnblogs.com/onepixel/articles/7674659.html

《大話資料結構》:https://book.douban.com/subject/6424904/

相關文章