算法就是这么一回事(排序)(第三部分)
2014-11-09 16:56
148 查看
六、快速排序
快速排序是通过一种把集合中的元素按照第一个元素(这个是动态过程变化)作为标杆来分为两部分,前面一部分比他小(或等),后面一部分比它大。然后就是通过适当的程序来递归这个过程,当最后没有交换说明需要退出递归。
上图
。
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
步骤为:
从数列中挑出一个元素,称为 "基准"(pivot),
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
代码:
乱数快速排序有一个值得注意的特性,在任意输入数据的状况下,它只需要O(n log n)的期望时间。是什么让随机的基准变成一个好的选择?
假设我们排序一个数列,然后把它分为四个部份。在中央的两个部份将会包含最好的基准值;他们的每一个至少都会比25%的元素大,且至少比25%的元素小。如果我们可以一致地从这两个中央的部份选出一个元素,在到达大小为1的数列前,我们可能最多仅需要把数列分区2log2 n次,产生一个 O(nlogn)算法。
不幸地,乱数选择只有一半的时间会从中间的部份选择。出人意外的事实是这样就已经足够好了。想像你正在翻转一枚硬币,一直翻转一直到有 k 次人头那面出现。尽管这需要很长的时间,平均来说只需要 2k 次翻动。且在 100k 次翻动中得不到 k 次人头那面的机会,是像天文数字一样的非常小。借由同样的论证,快速排序的递归平均只要2(2log2 n)的调用深度就会终止。但是如果它的平均调用深度是O(log n)且每一阶的调用树状过程最多有 n 个元素,则全部完成的工作量平均上是乘积,也就是 O(n log n)。
快速排序是通过一种把集合中的元素按照第一个元素(这个是动态过程变化)作为标杆来分为两部分,前面一部分比他小(或等),后面一部分比它大。然后就是通过适当的程序来递归这个过程,当最后没有交换说明需要退出递归。
上图
。
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
步骤为:
从数列中挑出一个元素,称为 "基准"(pivot),
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
代码:
#include <iostream> #include <iterator> #include <vector> #include <ctime> #include <random> #include <functional> #include <algorithm> using namespace std; int intSwap(int& a,int& b) { int intswaptemp=a; a=b; b=intswaptemp; return 0; } /*---------------------------------------------------- -----------------快速排序(STL版本)--------------------- 参数:迭代器、cmp cmp可以为less、greater等函数 template<typename _Tp> struct less_equal : public binary_function<_Tp, _Tp, bool> { bool operator()(const _Tp& __x, const _Tp& __y) const { return __x <= __y; } }; 这个东西第一次学需要自己拿笔算写,不需要想,因为太费劲了。 ----------------------------------------------------*/ template<typename Conditerator,typename Compare> int quickSortIter(Conditerator begin,Conditerator end,Compare cmp) { if(begin!=end)//递归终止条件 { Conditerator left=begin;//左 Conditerator right=end;//右 Conditerator pivot=left++;//用于作为参考相对大小的数子 while(left!=right) { if(cmp(*left,*pivot))//从begin开始下一个比较是否小于begin, left<begin(pivot) ++left;//如果成立,left移向下一个未和begin比较的值 else { while((left!=right)&&cmp(*pivot,*right))//begin(pivot)<right right--; iter_swap(left,right); } } if(cmp(*pivot,*left))//这里就是为了防止left和right重合 --left;//因为在上面程序中,最后会导致left和right重合,需要分离left iter_swap(begin,left);//保留了pivot,通过交换到前面一组中的最后一位 quickSortIter(begin,left,cmp); quickSortIter(right,end,cmp); } return 0; } template<typename T> inline int quickSort(T begin,T end) { quickSortIter(begin,end, less_equal<typename iterator_traits<T>::value_type>()); return 0; } inline int QuickSortVector(vector<int> &ivec) { quickSort(ivec.begin(),ivec.end()); return 0; } /*---------------------------------------------------- -----------------快速排序(vector版本)------------------ 参数:vector<int> 关键信息:通过合适的交换来实现,以第一个begin值为临时交换的参考值 解释同上 注意:一定要搞清楚>=和<=的逻辑关系,否则error或者死循环 ----------------------------------------------------*/ int quicksort_vector(vector<int>& ivec,int begin,int end) { if(begin!=end) { int left=begin; int right=end; int pivot=left++;//设置参考值(用于比较) while(left!=right) { if(ivec[pivot]>=ivec[left]) ++left; else { while((left!=right)&&(ivec[pivot]<=ivec[right])) --right; intSwap(ivec[left],ivec[right]); } } if(ivec[pivot]<=ivec[left]) left--; intSwap(ivec[begin],ivec[left]); quicksort_vector(ivec,begin,left); quicksort_vector(ivec,right,end); } return 0; } inline int quicksort1(vector<int> &ivec) { quicksort_vector(ivec,0,ivec.size()-1); return 0; } int main() { clock_t start,end; vector<int> ivec,copyivec; srand(14); for(int i=0;i<10000;i++)//10k ivec.push_back((int)rand()); copyivec=ivec; start=clock(); QuickSortVector(ivec); end=clock(); for(int i=0;i<10000;i+=500) cout<<ivec[i]<<'\t'; cout<<endl; cout<<"the time of 1 is "<<end-start<<endl; start=clock(); quicksort1(copyivec); end=clock(); for(int i=0;i<10000;i+=500) cout<<ivec[i]<<'\t'; cout<<endl; cout<<"the time of 2 is "<<end-start<<endl; return 0; }
乱数快速排序有一个值得注意的特性,在任意输入数据的状况下,它只需要O(n log n)的期望时间。是什么让随机的基准变成一个好的选择?
假设我们排序一个数列,然后把它分为四个部份。在中央的两个部份将会包含最好的基准值;他们的每一个至少都会比25%的元素大,且至少比25%的元素小。如果我们可以一致地从这两个中央的部份选出一个元素,在到达大小为1的数列前,我们可能最多仅需要把数列分区2log2 n次,产生一个 O(nlogn)算法。
不幸地,乱数选择只有一半的时间会从中间的部份选择。出人意外的事实是这样就已经足够好了。想像你正在翻转一枚硬币,一直翻转一直到有 k 次人头那面出现。尽管这需要很长的时间,平均来说只需要 2k 次翻动。且在 100k 次翻动中得不到 k 次人头那面的机会,是像天文数字一样的非常小。借由同样的论证,快速排序的递归平均只要2(2log2 n)的调用深度就会终止。但是如果它的平均调用深度是O(log n)且每一阶的调用树状过程最多有 n 个元素,则全部完成的工作量平均上是乘积,也就是 O(n log n)。
数据结构 | 不定 |
---|---|
最差时间复杂度 | |
最优时间复杂度 | |
平均时间复杂度 | |
最差空间复杂度 | 根据实现的方式不同而不同 |
相关文章推荐
- 算法就是这么一回事(排序)(第二部分)
- 算法就是这么一回事(排序)(第一部分)
- 算法就是这么一回事(写在前面)(没内容)
- Mini-Notes: 数据结构与算法-[第三部分]排序
- 原来AGILE就是这么一回事啊!
- 原来AGILE就是这么一回事啊!
- 算法导论 6章堆排序的代码实现和部分课后练习
- 算法和数据结构就是编程的一个重要部分,你若失掉了算法和数据结构,你就把一切都失掉了。
- 原来AGILE就是这么一回事啊!
- std list/vector sort 自定义类的排序就是这么简单
- 算法和数据结构就是编程的一个重要部分,你若失掉了算法和数据结构,你就把一切都失掉了
- 白话算法(3) 哥就是这么自信
- 算法和数据结构就是编程的一个重要部分,你若失掉了算法和数据结构,你就把一切都失掉了
- 算法学习笔记----第二部分:排序和顺序统计量----第6章、堆排序
- 算法学习笔记----第二部分:排序和顺序统计量----第6章、堆排序
- 白话算法(3) 哥就是这么自信
- Atitit 算法之道 attilax著 1. 第二部分(Part II) 排序与顺序统计(Sorting and Order Statistics) 1 2. 第六章 堆排序(Heapsort)
- poj和hdu部分基础算法分类及难度排序
- 算法和数据结构就是编程的一个重要部分,你若失掉了算法和数据结构,你就把一切都失掉了。——佚名
- Java密码学原型算法实现——第三部分:双线性对