zoukankan      html  css  js  c++  java
  • 理解快速排序(有图有真相)

     看完郝斌老师的数据结构,特来做做笔记(有写的不好的地方请大佬指教)

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

    假设我们现在对“6  1  2 7  9  3  4  5 10  8”这个10个数进行排序,将这10个数存入数组a[10],这个时候我们要设置一个临时变量val = a[0]

    不一定是a[0],随便一个数都可以。

    我们选中这个一个数val 是为了求出在排完序后val的真正位置,即val左边的数都小于val ,val 右边的数都大于val。

    我们要研究的就是这个。

    设定两个位置 i,j。分别存储数组元素的第一个和最后一个。

    步骤:

    让j所指的元素的值和i所指的元素的值比大小,

    情况1:若a[j] < a[i] 则将 a[i]=a[j]

    然后i位置+1

    情况2:若a[j] > a[i] ,则将j位置左移,直到找到一个位置满足a[j] < a[i],将 a[i]=a[j]

    然后再i位置+1

     基于两种情况,我们分析一下:

     初始位置:i = 0,j = 9;

     令val = a[i]

     比较a[j]<val,否,此时将j--

     再次比较,a[j]<val. 否,此时j--

     再次比较,a[j] <val,此时将 a[i] = a[7] = 5;此时 j的位置在7位置

     然后:

     比较a[i] >val ,5不大于6,否,继续i++;

     比较a[i] >val ,1不大于6,否,继续i++;

     比较a[i] >val ,2不大于6,否,继续i++;

     比较a[i] >val ,发现7大于6,a [3] >val,此时将a[j] = a[3],即a[7] = 7;

     此时i的位置为3即a[3] = 7

     

     此时 i =3,j = 7;

     比较a[j] < val  否,继续j--

     比较a[j] <val 是,则将 a[i] = a[6],此时a[3] = a[6] = 4,j的位置此时为6

     然后:

     比较a[i] > val ,4不大于6,否,i++

     比较a[i] >val 是,则将a[j] = a[i] 此时a[j] = 9,i的位置为4

     

     此时 i = 4,j=6 

     同理我们接着比较a[j] <val,9不小于6,j--

     a[j]<val ,3小于6,所以 a[i] = a[j] =3,此时j的位置5,i的位置为4

     然后

     比较a[i] > val ,3不大于6 ,i++

     此时a[i] = a[j] 终止比较。令a[i] = a[j] =val

     比较到现在,我们只进行了一轮就将数组一分为二,确定了val的位置了。也可以发现在val左边的数都是小于val,右边的数都是大于val的。

     利用递归的思想,重复以上步骤就可以将数组进行排序。

     代码:

     1 #include<stdio.h>
     2 #include "stdafx.h"
     3 
     4 int FindPos(int* a,int low,int high);
     5 void QuickSort(int* a,int low,int high);
     6 
     7 void QuickSort(int* a,int low,int high){
     8     
     9     while(low < high){
    10         int pos = FindPos(a,low,high);
    11         //一分为二
    12         QuickSort(a,low,pos-1);
    13         QuickSort(a,pos+1,high);
    14     }
    15 }
    16 
    17 //查找基数位置
    18 int FindPos(int* a,int low,int high){
    19     
    20     int val = a[low];
    21     while(low < high){
    22         while(low < high && a[high] >= val){
    23             high--;
    24         }
    25     a[low] = a[high];
    26     
    27     while(low < high && a[low] <= val){
    28             low++;
    29         }
    30     a[high] = a[low];
    31 
    32     }
    33 
    34     a[high] = val;
    35 
    36     return low;
    37 }
    38 
    39 int main(){
    40 
    41     int a[10] = {2,4,1,5,7,9,11,24,70,12};
    42 
    43     QuickSort(a,0,9);
    44 
    45     for(int i=0;i<10;i++)
    46         printf("%d ",a[i]);
    47     printf("
    ");
    48 
    49     return 0;
    50 }

    快速排序的算法复杂度分析:

    快速排序的时间性能取决于快速排序递归的深度,可以用递归数来描述算法的执行情况。如图9-9-7,它是{50,10,90,30,70,40,80,60,20}快排的递归过程。

    由于我们第一个数是50,正好是待排序序列的中间值,因此递归数是平衡的,此时的性能也比较好。

    最坏的情况是,当待排序列是逆序或或者正序时,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,将是一颗斜树。

    最优的情况下,第一次对整个数组扫一下,做n次比较,然后获取枢纽后将数组一份为二,那么各自还需要T(n/2)的时间,这个时间是最好的情况下的时间,所以平分两半,

    以此类推,我们有了下面不等式的推断:

    平均的情况:设置枢轴的关键位置在k上(1<k<n),那么

    性能的优化

    1、优化选取的枢轴val
    由于上面我们选择的val = a[0] 刚好是整个数列集合的中间位子。所以可以将大小数一分为二。但是如果我们的a[0]不是一个中间位置的数呢?

    如数组{9,1,5,8,3,7,4,6,2},这个时候如果把 val = a[low] ,low = 0,会是怎么样的呢?

    这个时候就发现low++了好多次,但是没有实质性的改变,像是在做无用功。

    所以这种选择枢轴偏大数会影响程序的性能,选择偏小也一样。

    那我们如何解决这个问题呢?我们可以选择介于偏大和偏小的数之间即可。这个数怎么找呢?

    我们可以这样取头,中,尾三个元素进行排序,将中间数作为枢轴。这个办法称为三数取中法。

    即在选择枢轴前面加上代码

     1 //查找基数位置
     2 int FindPos(int* a,int low,int high){
     3     
     4     int m = low +(high-low)/2;  //计算中间下标
     5     /*比较大小*/
     6     if(a[low] > a[high]){
     7         swap(&a[low],&a[high]); //交换左与右端的数据,保证左端较小
     8     }
     9     if(a[m] > a[high]){
    10         swap(&a[high],&a[m]); //交换中间与右端的数据,保证中间较小
    11     } 
    12     if(a[m] > a[low]){
    13         swap(&a[low],&a[m]); //交换中间与左端的数据,保证左端较小
    14     }
    15     /*此时low的位置即为三个数的中间位置的值了*/
    16 
    17     int val = a[low]; //这边存在缺陷
    18     while(low < high){
    19         while(low < high && a[high] >= val){
    20             high--;
    21         }
    22     a[low] = a[high];
    23     
    24     while(low < high && a[low] <= val){
    25             low++;
    26         }
    27     a[high] = a[low];
    28 
    29     }
    30     a[high] = val;
    31     return low;
    32 }

    注意我们求枢轴的方法,但是当数据比较多的时候,那么枢轴的选取将变得更加重要,常见的有9数取中法,原理和3数取中类似。

    这里就把快排的一些知识点写完了,快排是最常见的排序算法之一,也是难点之一,须重点掌握。

    全部代码实现:

     1 #include<stdio.h>
     2 
     3 int FindPos(int* a,int low,int high);
     4 void QuickSort(int* a,int low,int high);
     5 void swap(int* low,int* high);
     6 
     7 void QuickSort(int* a,int low,int high){
     8     
     9     if(low < high){
    10 
    11         int pos = FindPos(a,low,high);
    12         //一分为二
    13         QuickSort(a,low,pos-1);
    14         QuickSort(a,pos+1,high);
    15     }
    16 }
    17 
    18 //查找枢轴位置
    19 int FindPos(int* a,int low,int high){
    20     
    21     int m = low +(high-low)/2;  //计算中间下标
    22     /*比较大小*/
    23     if(a[low] > a[high]){
    24         swap(&a[low],&a[high]); //交换左与右端的数据,保证左端较小
    25     }
    26     if(a[m] > a[high]){
    27         swap(&a[high],&a[m]); //交换中间与右端的数据,保证中间较小
    28     } 
    29     if(a[m] > a[low]){
    30         swap(&a[low],&a[m]); //交换中间与左端的数据,保证左端较小
    31     }
    32     /*此时low的位置即为三个数的中间位置的值了*/
    33 
    34     int val = a[low]; //这边存在缺陷
    35     while(low < high){
    36         while(low < high && a[high] >= val){
    37             high--;
    38         }
    39     a[low] = a[high];
    40     
    41     while(low < high && a[low] <= val){
    42             low++;
    43         }
    44     a[high] = a[low];
    45 
    46     }
    47     a[high] = val;
    48 
    49     return low;
    50 }
    51 
    52 
    53 void swap(int* low,int* high){
    54     int t =0;
    55     t = *low;
    56     *low = *high;
    57     *high = t;
    58 }
    59 int main(){
    60 
    61     int a[10] = {2,4,1,5,7,9,11,24,70,12};
    62 
    63     QuickSort(a,0,9);
    64 
    65     for(int i=0;i<10;i++)
    66         printf("%d ",a[i]);
    67     printf("
    ");
    68 
    69     return 0;
    70 }
    View Code

    小结:

    分治法的基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。

    快排比较复杂,特别是复杂度的计算(这个我还是有点懵)和性能的优化,希望自己学习到后面的时候可以理解(迭代学习),可能是自己的数学功底不好,啧啧啧,先留个吧!!

    借鉴图片:

    https://blog.csdn.net/adusts/article/details/80882649

    参考资料:

    《大话数据结构》

    郝斌视频讲解:https://www.bilibili.com/video/av6159200?from=search&seid=9738629568092698275

  • 相关阅读:
    Two Sum II
    Subarray Sum
    Intersection of Two Arrays
    Reorder List
    Convert Sorted List to Binary Search Tree
    Remove Duplicates from Sorted List II
    Partition List
    Linked List Cycle II
    Sort List
    struts2结果跳转和参数获取
  • 原文地址:https://www.cnblogs.com/liuzeyu12a/p/10464470.html
Copyright © 2011-2022 走看看