• 理解快速排序(有图有真相)


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

    快速排序(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

  • 相关阅读:
    [转]ThinkCMF框架任意内容包含漏洞分析复现
    nodejs环境安装
    ffmpeg+nginx-rtmp-module
    activemq安装
    mysql审计插件
    sql优化工具SQLAdvisor的安装
    Harbor安装(docker-compose) -- 企业级Registry仓库
    在CentOS上部署kubernetes1.9.0集群
    3-基础概念(数据模型、时序 4 种类型、作业和实例)
    1-什么是 Prometheus
  • 原文地址:https://www.cnblogs.com/liuzeyu12a/p/10464470.html
Copyright © 2020-2023  润新知