(三)几种排序算法的学习总结(快速排序)

来源:互联网 发布:威盾软件 编辑:程序博客网 时间:2024/05/29 08:16

快速排序:“二十世纪最伟大的算法”
核心思想:每次从数组中选择一个元素作为基点,之后把该元素移动到排好序时应该所处的位置,使得基点之前的元素都小于它、之后的元素都大于它。之后对小于它、大于它的子数组分别递归进行快速排序
将元素移动到合适位置的函数:Partition
通常使用第一个元素作为分界的标志点:索引——l,元素值——v
大于v与小于v的分界点索引:j
当前访问的元素索引:i,元素值:e
最终目的,使得:
arr[l+1…j]< v
arr[j+1….i-1]> v
分为两种情况:
1.e > v
2.e < v

再交换l与j位置的元素,使得基点在合适的位置上

//对arr[l...r]部分进行partition操作 //返回p,使得arr[l...p-1]<arr[p];arr[p+1...r]>arr[p] template<typename T>int __partition(T arr[],int l,int r){    //优化1:随机选取一个基数,而非最左端的元素    //swap(arr[rand()%(r-l+1)+l],arr[l]);     //基数、标准     T v=arr[l];    //i是当前考虑的位置     //arr[l+1...j]<v;arr[j+1...i)>v     int j=l;        //使得两个子数组初始都为空     for(int i=l+1;i<=r;i++){        if(arr[i]<v){            swap(arr[++j],arr[i]);            //j++;        }    }     swap(arr[l],arr[j]);    return j;} template<typename T>void __quickSort(T arr[],int l,int r){    //索引非法处理    if(l>=r)        return;    int p=__partition(arr,l,r);    __quickSort(arr,l,p-1);    __quickSort(arr,p+1,r);}template<typename T>void quickSort(T arr[],int n){    //优化1:设置随机种子     //srand(time(NULL));    __quickSort(arr,0,n-1);}

缺点1:对于近乎有序的数组排序效率低下,运行过程中形成的递归树平衡度较差,对于完全有序的数组,算法复杂度直接退化为O(n*n)。
优化1:随机选取排序的基准元素
缺点2:对于有大量重复键值的数组排序效率低下,同样可能造成算法复杂度退化为O(n^2)。
优化2:

//双路快速排序:template<typename T> int __partition2(T arr[],int l,int r){    swap(arr[rand()%(r-l+1)+l],arr[l]);         T v=arr[l];    //arr[l+1...i]<=v;arr[j...r]>=v     int i=l+1,j=r;    while(true){        while(i<=r&&arr[i]<v)i++;        while(j>=l+1&&arr[j]>v)j--;        if(i>j)break;        swap(arr[i],arr[j]);        //索引移动到下一个要考察的位置        i++;        j--;     }    //循环结束时三个索引:    //i: 从前向后看第一个大于等于v的位置     //j: 从后向前看第一个(数组中最后一个)小于等于v的元素位置    //v: 小于等于v一端     swap(arr[l],arr[j]);    return j; }

优化3:考虑到可能会有很多“等于v”的键值,还可以进一步优化出三路快速排序。增设一个存放“等于v”元素的子数组,直接省去这部分递归
索引含义如下:
lt——指向“小于v”部分的最后一个元素,使得arr[l+1…lt] < v

gt——指向已经处理过的后面的第一个“大于v”部分的元素,使得arr[gt…r] > v

则arr[lt+1…i-1]==v
分情况讨论索引为i的元素:
1. e == v:该元素直接纳入“等于v”部分,i++
2. e < v :将该位置元素和“等于v”部分的第一个元素交换,lt++
3. e > v :将该位置元素直接与“gt-1”位置元素进行交换,gt–,i索引无需进行维护,依然指向了一个没有经过处理的元素

整个处理过程结束后,lt、gt分别指向了“小于v”部分和“大于v”部分的最后一个(第一个)元素,i与gt重合,此时交换l位置元素与lt位置元素即可

最后结果:
“小于v”部分:arr[l…lt-1] < v;
“等于v”部分:arr[lt…gt-1] ==v;
“大于v”部分:arr[gt…r] > v;

三路快速排序实现:

//三路快速排序处理:arr[l...r]//将arr[l...r]分为<v,==v,>v三个部分 //之后递归对<v,>v 两部分继续进行三路快速排序 template<typename T>void __quickSort3Ways(T arr[],int l,int r){    //递归结束判断    if(l>=r)        return;    //partition    swap(arr[rand()%(r-l+1)+l],arr[l]);    T v=arr[l];    //注意初始值的设定,必须满足三个子数组开始都为空     int lt=l; //arr[l+1...lt]<v     int gt=r+1; //arr[gt...r]>v     int i=l+1; //arr[lt+1...i)==v     while(i<gt){        if(arr[i]<v){            swap(arr[i],arr[lt+1]);            lt++;            i++;        }        else if(arr[i]>v){            swap(arr[i],arr[gt-1]);            gt--;            //i索引无需处理,依然指向了一个未经过处理的元素         }        else{//arr[i]==v             i++;        }    }    swap(arr[l],arr[lt]);    __quickSort3Ways(arr,l,lt-1);    __quickSort3Ways(arr,gt,r);}template<typename T>void quickSort3Ways(T arr[],int n){    srand(time(NULL));    __quickSort3Ways(arr,0,n-1);}

对于快速排序就介绍到这里了,更基础的选择、插入排序详见前两篇文章:http://blog.csdn.net/draper__qyt/article/details/77980407
http://blog.csdn.net/draper__qyt/article/details/77984775
注:本文的内容来自慕课网的课程《c++,java算法与数据结构》,主讲人为刘宇波老师~极力推荐!
这里附上课程链接:http://coding.imooc.com/class/71.html

阅读全文
0 0
原创粉丝点击