美文网首页
【算法图文动画详解系列】QuickSort 快速排序算法

【算法图文动画详解系列】QuickSort 快速排序算法

作者: 光剑书架上的书 | 来源:发表于2021-04-29 03:11 被阅读0次

    快排简介

    快速排序(Quicksort)是对冒泡排序算法的一种改进。

    快速排序由C. A. R. Hoare在1960年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

    快排算法原理

    快速排序算法通过多次比较和交换来实现排序,其排序流程如下:

    (1) 首先设定一个分界值(pivot):通过该分界值将数组分成左右两部分(partition)。
    (2) Compare and Swap:将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
    (3) Recursive:然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
    (4) 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

    动图演示快排原理:

    QuickSort Algorithm视频演示:

    https://video.kuaishou.com/short-video/3xytg4s3xviab3u

    算法原理详解

    快速排序(QuickSort )是一个分治算法(Divide and Conquer)。它选择一个元素作为枢轴元素(pivot),并围绕选定的主元素对给定数组进行分区(partition)。快速排序有很多不同的版本,它们以不同的方式选择枢轴。

    1. 总是选择第一个元素作为 pivot。
    2. 总是选择最后一个元素作为 pivot。
    3. 随机选一个元素作为 pivot。
    4. 选择中值作为 pivot。

    QuickSort 中的关键步骤是 partition()。在数组中选择的一个元素为支点(pivot), 把所有小于 pivot 的元素放到 pivot 左面, 大于 pivot 的放右边。这样数组 x[n] 会被划分成3个部分:

    x[0] , ... , x[pivot - 1]
    x[pivot]
    x[pivot+1] , ... , x[n]

    所有这应该是在线性时间内完成。
    接下来,就是递归调用:

     quicksort(x, low, pivot - 1)
     quicksort(x, pivot + 1, high)
    

    Pseudo Code for recursive QuickSort function

    /* low  --> Starting index,  high  --> Ending index */
    void quickSort(arr[], low, high)
    {
        if (low < high)
        {
            /* pi is partitioning index, arr[pi] is now
               at right place */
            pi = partition(arr, low, high);
    
            quickSort(arr, low, pi - 1);  // Before pi
            quickSort(arr, pi + 1, high); // After pi
        }
    }
    

    Partition Algorithm

    There can be many ways to do partition, following pseudo code adopts the method given in CLRS book.

    The logic is simple, we start from the leftmost element and keep track of index of smaller (or equal to) elements as i. While traversing, if we find a smaller element, we swap current element with arr[i]. Otherwise we ignore current element.

    Pseudo code for partition()

    /* This function takes last element as pivot, places
       the pivot element at its correct position in sorted
        array, and places all smaller (smaller than pivot)
       to left of pivot and all greater elements to right
       of pivot */
    partition (arr[], low, high)
    {
        // pivot (Element to be placed at right position)
        pivot = arr[high];  
     
        i = (low - 1)  // Index of smaller element and indicates the 
                       // right position of pivot found so far
    
        for (j = low; j <= high- 1; j++)
        {
            // If current element is smaller than the pivot
            if (arr[j] < pivot)
            {
                i++;    // increment index of smaller element
                swap arr[i] and arr[j]
            }
        }
        swap arr[i + 1] and arr[high])
        return (i + 1)
    }
    
    

    Illustration of partition()

    arr[] = {10, 80, 30, 90, 40, 50, 70}
    Indexes:  0   1   2   3   4   5   6 
    
    low = 0, high =  6, pivot = arr[h] = 70
    Initialize index of smaller element, i = -1
    
    Traverse elements from j = low to high-1
    j = 0 : Since arr[j] <= pivot, do i++ and swap(arr[i], arr[j])
    i = 0 
    arr[] = {10, 80, 30, 90, 40, 50, 70} // No change as i and j 
                                         // are same
    
    j = 1 : Since arr[j] > pivot, do nothing
    // No change in i and arr[]
    
    j = 2 : Since arr[j] <= pivot, do i++ and swap(arr[i], arr[j])
    i = 1
    arr[] = {10, 30, 80, 90, 40, 50, 70} // We swap 80 and 30 
    
    j = 3 : Since arr[j] > pivot, do nothing
    // No change in i and arr[]
    
    j = 4 : Since arr[j] <= pivot, do i++ and swap(arr[i], arr[j])
    i = 2
    arr[] = {10, 30, 40, 90, 80, 50, 70} // 80 and 40 Swapped
    j = 5 : Since arr[j] <= pivot, do i++ and swap arr[i] with arr[j] 
    i = 3 
    arr[] = {10, 30, 40, 50, 80, 90, 70} // 90 and 50 Swapped 
    
    We come out of loop because j is now equal to high-1.
    Finally we place pivot at correct position by swapping
    arr[i+1] and arr[high] (or pivot) 
    arr[] = {10, 30, 40, 50, 70, 90, 80} // 80 and 70 Swapped 
    
    Now 70 is at its correct place. All elements smaller than
    70 are before it and all elements greater than 70 are after
    it.
    

    快排算法源代码

    Java 源代码

    // Java implementation of QuickSort
    import java.io.*;
    
    class QuickSort{
        
    // A utility function to swap two elements
    static void swap(int[] arr, int i, int j)
    {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    
    /* This function takes last element as pivot, places
    the pivot element at its correct position in sorted
    array, and places all smaller (smaller than pivot)
    to left of pivot and all greater elements to right
    of pivot */
    static int partition(int[] arr, int low, int high)
    {
        
        // pivot
        int pivot = arr[high];
        
        // Index of smaller element and
        // indicates the right position
        // of pivot found so far
        int i = (low - 1);
    
        for(int j = low; j <= high - 1; j++)
        {
            
            // If current element is smaller
            // than the pivot
            if (arr[j] < pivot)
            {
                
                // Increment index of
                // smaller element
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, high);
        return (i + 1);
    }
    
    /* The main function that implements QuickSort
            arr[] --> Array to be sorted,
            low --> Starting index,
            high --> Ending index
    */
    static void quickSort(int[] arr, int low, int high)
    {
        if (low < high)
        {
            
            // pi is partitioning index, arr[p]
            // is now at right place
            int pi = partition(arr, low, high);
    
            // Separately sort elements before
            // partition and after partition
            quickSort(arr, low, pi - 1);
            quickSort(arr, pi + 1, high);
        }
    }
    
    public static void main(String[] args)
    {
        int[] arr = { 10, 7, 8, 9, 1, 5 };
        int n = arr.length;
        
        quickSort(arr, 0, n - 1);
    }
    }
    
    // This code is contributed by Ayush Choudhary
    
    

    Kotlin 源代码(优化版本)

    package com.light.sword
    
    import kotlin.random.Random
    
    /**
     * @author: Jack
     * 2021/4/28 下午2:34
     * Like Merge Sort, QuickSort is a Divide and Conquer algorithm.
     * It picks an element as pivot and partitions the given array around the picked pivot.
     * There are many different versions of quickSort that pick pivot in different ways.
    Always pick first element as pivot.
    Always pick last element as pivot (implemented below)
    Pick a random element as pivot.
    Pick median as pivot.
    The key process in quickSort is partition(). Target of partitions is,
    given an array and an element x of array as pivot, put x at its correct position in sorted array,
    and put all smaller elements (smaller than x) before x,
    and put all greater elements (greater than x) after x.
    All this should be done in linear time.
     */
    fun quicksort(x: IntArray, low: Int, high: Int) {
        // index boundary check
        if (low >= high) return
        // random select a pivot element
        val pivotIndex = Random(1).nextInt(low, high)
        // put the pivot element at head
        swap(x, low, pivotIndex)
        val pivotValue = x[low]
        // boundary index i, elements on the left side of i are smaller than pivotValue
        var i = low
        (low + 1..high).forEach {
            // x[j] comparing  pivotValue, find smaller than pivotValue,
            // and put it on the left side of i position
            val j = it
            if (x[j] < pivotValue) {
                swap(x, ++i, j)
            }
        }
        // i should be the pivot index, so swap x[low],x[i]
        swap(x, low, i)
        // divide and conquer, recursive call
        quicksort(x, low, i - 1)
        quicksort(x, i + 1, high)
    }
    
    fun swap(x: IntArray, a: Int, b: Int) {
        val temp = x[a]
        x[a] = x[b]
        x[b] = temp
    }
    
    fun main() {
        val x = intArrayOf(7, 2, 1, 8, 6, 3, 5, 4)
        quicksort(x, 0, x.size - 1)
        println(x.joinToString { it.toString() })
    }
    

    参考资料

    1、《代码之美》Chapter 3:我从未编写过的最漂亮的代码(Jon Bentley)
    2、QuickSort:https://www.geeksforgeeks.org/quick-sort/
    3、快速排序百科:https://baike.baidu.com/item/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/369842

    相关文章

      网友评论

          本文标题:【算法图文动画详解系列】QuickSort 快速排序算法

          本文链接:https://www.haomeiwen.com/subject/gqlirltx.html