zoukankan      html  css  js  c++  java
  • 经典排序算法--快速排序

    一、快速排序的基本思想:

      快速排序使用了分治的思想,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小。之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

    二、快速排序的三个步骤

    1) 选择基准:在待排序列中,按照某种方式挑出一个元素,作为 “基准”(pivot);

    2) 分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。如果为升序,则此时,在基准左边的元素都比该基准小,在基准右边的元素都比基准大;而基准则在排序后正确的位置上。

    3) 递归地对两个序列进行快速排序,直到序列为空或者只有一个元素;

    三、选择基准元的方式

    对于分治算法,当每次划分时,算法若都能分成两个等长的子序列时,那么分治算法效率会达到最大。也就是说,基准的选择是很重要的。选择基准的方式决定了两个分割后两个子序列的长度,进而对整个算法的效率产生决定性影响。

    最理想的方法是,选择的基准恰好能把待排序序列分成两个等长的子序列。

    方法一:固定基准元(基本的快速排序)

      选取第一个元素或最后一个元素作为基准元。

     1 public static void sortCommon(int[] data,int low,int high){
     2         if(high>low){
     3             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
     4             int key = getStandard(data, low, high);
     5             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
     6             sortCommon(data, low, key-1);
     7             sortCommon(data, key+1, high);
     8         }
     9     }
    10     
    11     public static int getStandard(int[] data,int low,int high){
    12         //将基准元提取出来
    13         int tmp = data[low];
    14         while (low<high) {
    15             //从右往左查询,查找第一个小于基准元的元素,并将其放置在data[low]位置
    16             while(low<high&&data[high]>=tmp){
    17                 high--;
    18             }
    19             data[low] = data[high];
    20             //从左往右查询,查找第一个大于基准元的元素,并将其放置在data[high]位置
    21             while(low<high&&data[low]<=tmp){
    22                 low++;
    23             }
    24             data[high] = data[low];
    25         }
    26         //基准元归为
    27         data[low] = tmp;
    28         return low;
    29     }

    方法二:随机基准元

    思想:取待排序列中任意一个元素作为基准元。

    引入的原因:在待排序列是部分有序时,固定选取基准元使快排效率底下,要缓解这种情况,就引入了随机选取基准元。

     1     public static void sortRandom(int[] data,int low,int high){
     2         if(high>low){
     3             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
     4             getStandardRandom(data, low, high);
     5             int key = getStandard(data, low, high);
     6             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
     7             sortRandom(data, low, key-1);
     8             sortRandom(data, key+1, high);
     9         }
    10     }
    11     
    12     private static void getStandardRandom(int[] data, int low, int high) {
    13         Random r = new Random();
    14         int ran = low+r.nextInt(high-low);
    15         //将基准元提取出来
    16         swap(data, low, ran);
    17     }

    方法三:三数取中

    引入的原因:虽然随机选取基准时,减少出现不好分割的几率,但是还是最坏情况下还是O(n^2),要缓解这种情况,就引入了三数取中选取基准。

    分析:最佳的划分是将待排序的序列分成等长的子序列,最佳的状态我们可以使用序列的中间的值,也就是第N/2个数。可是,这很难算出来,并且会明显减慢快速排序的速度。这样的中值的估计可以通过随机选取三个元素并用它们的中值作为基准元而得到。事实上,随机性并没有多大的帮助,因此一般的做法是使用左端、右端和中心位置上的三个元素的中值作为基准元。显然使用三数中值分割法消除了预排序输入的不好情形,并且减少快排大约14%的比较次数。

    举例:待排序序列为:8 1 4 9 6 3 5 2 7 0

    左边为:8,右边为0,中间为6

    我们这里取三个数排序后,中间那个数作为枢轴,则枢轴为6

    注意:在选取中轴值时,可以从由左中右三个中选取扩大到五个元素中或者更多元素中选取,一般的,会有(2t+1)平均分区法(median-of-(2t+1),三平均分区法英文为median-of-three。

    具体思想:对待排序序列中low、mid、high三个位置上数据进行排序,取他们中间的那个数据作为基准,并用0下标元素存储基准。

    即:采用三数取中,并用0下标元素存储基准。

     1 public static void sortMiddleOfThree(int[] data,int low,int high){
     2         if(high>low){
     3             //三数去中,并将中间数换到low位置上
     4             middleOfThree(data, low, high);
     5             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
     6             int key = getStandard(data, low, high);
     7             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
     8             sortMiddleOfThree(data, low, key-1);
     9             sortMiddleOfThree(data, key+1, high);
    10         }
    11     }
    12     private static void middleOfThree(int[] data, int low, int high) {
    13         int middle = (high-low)/2+low;
    14         if (data[middle] > data[high])
    15         {
    16             swap(data, middle, high);
    17         }
    18         if (data[low] > data[high])
    19         {
    20             swap(data, low, high);
    21         }
    22         if (data[middle] > data[low])
    23         {
    24             swap(data, middle, low);
    25         }      
    26     }

    四.  两种优化的方法

    优化一:当待排序序列的长度分割到一定大小后,使用插入排序

    原因:对于很小和部分有序的数组,快排不如插排好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排。

    截止范围:待排序序列长度N = 10,虽然在5~20之间任一截止范围都有可能产生类似的结果,这种做法也避免了一些有害的退化情形。

    —-摘自《数据结构与算法分析》Mark Allen Weiness 著

     1 public static void sortThreeInsert(int[] data, int low, int high){
     2         if(high-low+1<10){
     3             insertSort(data);
     4             return;
     5         }else{
     6             //三数去中,并将中间数换到low位置上
     7             middleOfThree(data, low, high);
     8             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
     9             int key = getStandard(data, low, high);
    10             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
    11             sortThreeInsert(data, low, key-1);
    12             sortThreeInsert(data, key+1, high);
    13         }
    14     }

    优化二:在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割

    举例:

    待排序序列 1 4 6 7 6 6 7 6 8 6

    三数取中选取基准:下标为4的数6

    转换后,待分割序列:6 4 6 7 1 6 7 6 8 6

                 基准key:6

    本次划分后,未对与key元素相等处理的结果:1 4 6 6 7 6 7 6 8 6

    下次的两个子序列为:1 4 6 和 7 6 7 6 8 6

    本次划分后,对与key元素相等处理的结果:1 4 6 6 6 6 6 7 8 7

    下次的两个子序列为:1 4 和 7 8 7

    经过对比,我们可以看出,在一次划分后,把与key相等的元素聚在一起,能减少迭代次数,效率会提高不少

    具体过程:在处理过程中,会有两个步骤

    第一步,在划分过程中,把与key相等元素放入数组的两端

    第二步,划分结束后,把与key相等的元素移到枢轴周围

     1 public static void sortThreeInsertGather(int[] data, int low, int high){
     2         if(high-low+1<10){
     3             insertSort(data);
     4             return;
     5         }else{
     6             //三数去中,并将中间数换到low位置上
     7             middleOfThree(data, low, high);
     8             //进行一次排序,确定基准元位置,并将和基准元相等的元素,一直数组的两侧
     9             //first 和last 用来计算基准元
    10             int first = low;
    11             int last = high;
    12             //left和right 用来记录和基准元相等的元素
    13             int left = low;
    14             int right = high;
    15             //leftLength和rightLength用来记录左右两侧各有多少和基准元相等的元素
    16             int leftLength = 0;
    17             int rightLength = 0;
    18             int tmp = data[first];
    19             while(first<last){
    20                 while(first<last&&data[last]>=tmp){
    21                     if(data[last]==tmp){
    22                         swap(data, last, right);
    23                         right--;
    24                         rightLength++;
    25                     }
    26                     last--;
    27                 }
    28                 data[first] = data[last];
    29                 while(first<last&&data[first]<=tmp){
    30                     if(data[first]==tmp){
    31                         swap(data, first, left);
    32                         left++;
    33                         leftLength++;
    34                     }
    35                     first++;
    36                 }
    37                 data[last] = data[first];
    38             }
    39             data[first] = tmp;
    40             
    41             //一次排序完成,将两侧与基准元相等的元素移到基准元旁边
    42             int i= first-1;
    43             int j = low;
    44             while (j < left && data[i] != tmp)
    45             {
    46                 swap(data, i, j);
    47                 i--;
    48                 j++;
    49             }
    50             i = last + 1;
    51             j = high;
    52             while (j > right && data[i] != tmp)
    53             {
    54                 swap(data, i, j);
    55                 i++;
    56                 j--;
    57             }
    58             sortThreeInsert(data, low, first-1-leftLength);
    59             sortThreeInsert(data, first+1+rightLength, high);
    60         }
    61     }
    62     

    以下是全部的测试程序源码:

      1 package sortDemo;
      2 
      3 import java.util.Random;
      4 
      5 public class QuickSort {
      6     
      7     public static void main(String[] args) {
      8         Random r = new Random();
      9         int[] data = new int[100];
     10         for (int i = 0; i < data.length; i++) {
     11             data[i] = r.nextInt(10);
     12         }
     13         Long startTime = System.currentTimeMillis();
     14         print(data);
     15         Long endTime = System.currentTimeMillis();
     16         //sortCommon(data, 0, data.length-1);
     17         //sortRandom(data, 0, data.length-1);
     18         //sortMiddleOfThree(data, 0, data.length-1);
     19         //sortThreeInsert(data, 0, data.length-1);
     20         sortThreeInsertGather(data, 0, data.length-1);
     21         print(data);
     22         System.out.println("排序用时:"+(endTime-startTime));
     23         
     24         
     25         
     26     }
     27     
     28     public static void sortCommon(int[] data,int low,int high){
     29         if(high>low){
     30             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
     31             int key = getStandard(data, low, high);
     32             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
     33             sortCommon(data, low, key-1);
     34             sortCommon(data, key+1, high);
     35         }
     36     }
     37     
     38     public static int getStandard(int[] data,int low,int high){
     39         //将基准元提取出来
     40         int tmp = data[low];
     41         while (low<high) {
     42             //从右往左查询,查找第一个小于基准元的元素,并将其放置在data[low]位置
     43             while(low<high&&data[high]>=tmp){
     44                 high--;
     45             }
     46             data[low] = data[high];
     47             //从左往右查询,查找第一个大于基准元的元素,并将其放置在data[high]位置
     48             while(low<high&&data[low]<=tmp){
     49                 low++;
     50             }
     51             data[high] = data[low];
     52         }
     53         //基准元归为
     54         data[low] = tmp;
     55         return low;
     56     }
     57     
     58     public static void sortRandom(int[] data,int low,int high){
     59         if(high>low){
     60             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
     61             getStandardRandom(data, low, high);
     62             int key = getStandard(data, low, high);
     63             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
     64             sortRandom(data, low, key-1);
     65             sortRandom(data, key+1, high);
     66         }
     67     }
     68     
     69     private static void getStandardRandom(int[] data, int low, int high) {
     70         Random r = new Random();
     71         int ran = low+r.nextInt(high-low);
     72         //将基准元提取出来
     73         swap(data, low, ran);
     74     }
     75     
     76     public static void sortMiddleOfThree(int[] data,int low,int high){
     77         if(high>low){
     78             //三数去中,并将中间数换到low位置上
     79             middleOfThree(data, low, high);
     80             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
     81             int key = getStandard(data, low, high);
     82             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
     83             sortMiddleOfThree(data, low, key-1);
     84             sortMiddleOfThree(data, key+1, high);
     85         }
     86     }
     87     private static void middleOfThree(int[] data, int low, int high) {
     88         int middle = (high-low)/2+low;
     89         if (data[middle] > data[high])
     90         {
     91             swap(data, middle, high);
     92         }
     93         if (data[low] > data[high])
     94         {
     95             swap(data, low, high);
     96         }
     97         if (data[middle] > data[low])
     98         {
     99             swap(data, middle, low);
    100         }      
    101     }
    102     
    103     public static void sortThreeInsert(int[] data, int low, int high){
    104         if(high-low+1<10){
    105             insertSort(data);
    106             return;
    107         }else{
    108             //三数去中,并将中间数换到low位置上
    109             middleOfThree(data, low, high);
    110             //进行一次排序,将基准元放置在正确顺序的位置上,并确定当前基准元位置
    111             int key = getStandard(data, low, high);
    112             //分别将基准元左侧和基准元右侧当做一个序列从新进行排序,知道序列的长度为1
    113             sortThreeInsert(data, low, key-1);
    114             sortThreeInsert(data, key+1, high);
    115         }
    116     }
    117     
    118     public static void sortThreeInsertGather(int[] data, int low, int high){
    119         if(high-low+1<10){
    120             insertSort(data);
    121             return;
    122         }else{
    123             //三数去中,并将中间数换到low位置上
    124             middleOfThree(data, low, high);
    125             //进行一次排序,确定基准元位置,并将和基准元相等的元素,一直数组的两侧
    126             //first 和last 用来计算基准元
    127             int first = low;
    128             int last = high;
    129             //left和right 用来记录和基准元相等的元素
    130             int left = low;
    131             int right = high;
    132             //leftLength和rightLength用来记录左右两侧各有多少和基准元相等的元素
    133             int leftLength = 0;
    134             int rightLength = 0;
    135             int tmp = data[first];
    136             while(first<last){
    137                 while(first<last&&data[last]>=tmp){
    138                     if(data[last]==tmp){
    139                         swap(data, last, right);
    140                         right--;
    141                         rightLength++;
    142                     }
    143                     last--;
    144                 }
    145                 data[first] = data[last];
    146                 while(first<last&&data[first]<=tmp){
    147                     if(data[first]==tmp){
    148                         swap(data, first, left);
    149                         left++;
    150                         leftLength++;
    151                     }
    152                     first++;
    153                 }
    154                 data[last] = data[first];
    155             }
    156             data[first] = tmp;
    157             
    158             //一次排序完成,将两侧与基准元相等的元素移到基准元旁边
    159             int i= first-1;
    160             int j = low;
    161             while (j < left && data[i] != tmp)
    162             {
    163                 swap(data, i, j);
    164                 i--;
    165                 j++;
    166             }
    167             i = last + 1;
    168             j = high;
    169             while (j > right && data[i] != tmp)
    170             {
    171                 swap(data, i, j);
    172                 i++;
    173                 j--;
    174             }
    175             sortThreeInsert(data, low, first-1-leftLength);
    176             sortThreeInsert(data, first+1+rightLength, high);
    177         }
    178     }
    179     
    180     
    181     
    182     public static void insertSort(int[] a){
    183         //从下标为1开始比较,知道数组的末尾
    184         for (int i = 1; i < a.length; i++) {
    185             int j;
    186             //将要比较的元素,拿出待比较过后再插入数组
    187             int tmp = a[i];
    188             //一次与前一元素比较,如果前一元素比要插入的元素大,则互换位置
    189             for (j = i-1; j >=0&&a[j]>tmp; j--) {
    190                 a[j+1] = a[j];
    191             }
    192             //将比较的元素插入
    193             a[j+1] = tmp;
    194         }
    195     }
    196     
    197     public static void print(int[] a){
    198         for (int i = 0; i < a.length; i++) {
    199             System.out.print(a[i]+" ");
    200         }
    201         System.out.println();
    202     }
    203     
    204     private static void swap(int[] data,int i,int j){
    205         if(i==j){
    206             return;
    207         }
    208         data[i] = data[i] + data[j];
    209         data[j] = data[i] - data[j];
    210         data[i] = data[i] - data[j];
    211     }
    212 }

    学习整理自:http://www.cnblogs.com/HuoAA/p/4338424.html

     

  • 相关阅读:
    使用 crontab 来定时执行脚本,无法执行,但是如果直接通过命令(如:./test.sh)又可以正常执行,
    解决RABBITMQ ADMIN账号登陆失败的问题
    Job for rabbitmq-server.service failed because the control process exited with error code. See "systemctl status rabbitmq-server.service" and "journalctl -xe" for details.
    git 删除远端分支,本地新创建分支推到远程或者 拉取远程分支并创建本地分支
    细品 Spring Boot+Thymeleaf,还有这么多好玩的细节!
    Spring Boot 日志各种使用姿势,是时候捋清楚了!
    SQL多节节点MS SQL中的递归
    支付DLL接口
    (转)ubuntu下查询CPU数,核心数,线程数
    (转)C/C++中计算程序运行时间
  • 原文地址:https://www.cnblogs.com/xsyfl/p/6901315.html
Copyright © 2011-2022 走看看