• 图解快速排序


    基本思想:将一组要排序的数列分成两部分,其中一部分的值都比另一部分的小;然后按照这个方法分别对两部分数据进行快速排序,整个过程可以用递归进行,从而实现整个数列的排序。

    快速排序方法是基于分值策略的,排序在原地排序,不需要辅助的数组,但是分解困难。

    快速排序分为三个过程:分解、治理、合并。

    分解:先从数列num[low,high]中取出一个基准元素,将所有比基准元素小的数据存放在基准元素左侧,将所有比基准元素大的数据存放在基准元素右侧;基准元素此时已经位于正确的位置了,这个位置记为mid;

    治理:基准元素左右两个子数列num[low,mid-1]和num[mid+1,high]进行分解的操作,即进行快速排序的操作,这一步通过递归的方式实现;

    合并:由于快速排序是在原地进行排序,因此不需要进行合并的操作。

    快速排序中一个注意点的是基准元素该如何选择?如果基准元素选取不当,有可能分解成规模为0和n-1的两个子数列,快速排序会退化成冒泡排序。

    一般来说,基准元素有以下几种取法:

    • 取第一个元素
    • 取最后一个元素
    • 取中间元素
    • 取第一个、最后一个、中间位置元素三者的中位数
    • 取第一个和最后一个之间位置的随机数k(low<=k<=high),取R[k]作为基准元素

    快速排序的算法中,第二步是递归,关键的第一步分解如何实现呢?也就是说,我们选择好了基准元素后,如何将数列中比基准元素小的数都移到基准元素左边,而把数列中比基准元素大的数都移动到基准元素右边呢?

    下面选择基准元素为第一个元素,说明如何实现这一步。

    假设当前待排序的数列为num[low,high],其中low<=high。

    步骤1:首先取数组的第一个元素作为基准元素pivot = num[low], 设置两个指针i和j,i = low, j = high;

    步骤2:从右向左扫描(即 j不断向左移动),找到小于 pivot 的数,如果找到的话,num[i] 和 num[j] 交换, i++;(这一步其实就是将比基准元素小的数移动到基准元素左边)

    步骤3:从左向右扫描(即 i 不断向右移动),找到大于 pivot 的数,如果找到的话,num[i] 和 num[j] 交换,j--;(这一步其实就是将比基准元素大的数移动到基准元素左右边)

    步骤4:重复步骤2~步骤3,直到指针 i 和 j 重合,返回该位置 mid = i,该位置的数正好是pivot

    以数组{30,24,5,58,18,36,12,42,39}为例,上述步骤的演示过程如图

    实现代码如下所示:

     1     public static void main(String[] args){
     2         int[] num = {30,24,5,58,18,36,12,42,39};
     3         QuickSort(num,0,num.length-1);
     4         for(int k:num)
     5             System.out.print(k+",");
     6     }
     7     public static void QuickSort(int[] num,int low,int high) {
     8         int mid;
     9         if(low<high) {
    10             mid = Partition(num,low,high);
    11             QuickSort(num,low,mid-1);
    12             QuickSort(num,mid+1,high);
    13         }
    14     }
    15     public static int Partition(int[] num, int low, int high) {
    16         int i = low;
    17         int j = high;
    18         int pivot = num[low];
    19         while(i < j) {
    20             while(i < j && num[j] > pivot)
    21                 j--;
    22             if(i<j) {
    23                 swap(num,i,j);
    24                 i++;//注意,这一行不能在花括号外面
    25             }
    26             while(i<j && num[i]<pivot)
    27                 i++;
    28             if(i<j) {
    29                 swap(num,i,j);
    30                 j--;//注意,这一行不能在花括号外面
    31             }
    32         }
    33         return i;
    34     }
    35     public static void swap(int[] num,int i,int j) {
    36         int tmp = num[j];
    37         num[j] = num[i];
    38         num[i] = tmp;
    39     }

    需要注意的是,以上Partition()函数的实现只适用于基准元素是第一个元素的时候,当基准元素取最后一个元素时,需要将步骤2和步骤3调换。我们仔细看上述过程,会发现,指针i和j总有一个是指向基准元素,在做交换过程的时候,是基准元素和其它元素在做交换。如果我们将基准元素取最后一个元素是,可能会发生指针i和j指向的不是基准元素了。

    在代码中,我有两行加了注释,就是指针i++和指针j--这一过程必须在花括号里。这是因为只有在i<j的情况下,才能执行 i++和j--的操作。否则的话,当i==j了,此时需要换回i,如果i++不在i<j的约束下,肯定是要执行的,返回的就不是基准元素的位置,而是基准元素后一位了。

  • 相关阅读:
    Linux 系统下 “账户管理”
    gulp添加版本号解决缓存问题
    vue3.0的proxy浅析内层绑定原理
    rem用font-size布局与easyui的datagrid通用,出现table不显示
    堆与栈 | 对象深浅拷贝
    vue双向绑定原理值Object.defineProperty
    bootstrap模态框不出,只出现黑色蒙层bug
    Appdelegate 导航操作
    CLLocationManager 位置定位
    导航创建
  • 原文地址:https://www.cnblogs.com/Chsy/p/11755448.html
Copyright © 2020-2023  润新知