学习排序,参考代码、测试程序:
http://tech.ddvip.com/2006-12/116513859112852.html
表示一下感谢~~
交换排序。包括冒泡排序和快速排序,一般说来冒泡是不能给别人说的:怕丢脸,呵呵。所以我们就默认交换排序==快速排序。
1. 思想
a)它采用的是分而治之的策略(divide and conque)。基本思想是:任取待排序列的某个记录(这句话就包含了优化的可能)作为基准,按照该关键码大小,将整个序列分成两个序列——--左侧的所有记录的关键码都比基准小(或者等),右侧的都比基准大,基准则放在两个子序列之间,显然这时基准放在了最后应该放置的位置(这个找pivot的方法,有很多,但是基本上都不对性能产生大的影响。但是好的pivot会很好的避免最坏情况的出现)。分别对左右子序列重复上面的过程,直到最后所有的记录都放在相应的位置(就是递归了,没什么好办法)。
b)整体上说,他的性能是所有排序里面最好的,而且消耗的辅助空间比较小。但是,最大的问题就是:递归,数据量大的时候会把stack爆掉。
c)不稳定排序,性能:O(n*lg(n));
d)当输入源是已经排序好的数据,这时候快速排序会得到最差的性能:每次新的比较都只是比上次少一个元素;
2. 版本
1)经典快速排序(严版)
寻找pivot的过程是整个算法的关键:是while嵌套两个小while:首先把最left元素作为pivot,从右向左,遇到小的放到left;从左向右,遇到大的,放到right;-->中间碰头,把pivot放到对应位置上。找完所有元素结束。
《代码》
- template <class T>
- int Partition2(T a[], int left, int right)
- {
- T pivot = a[left];
- while ( (left < right) )
- {
- while ( (pivot <= a[right]) && (left < right) )
- right--;
- a[left] = a[right];
- while ( (pivot >= a[left]) && (left < right) )
- left++;
- a[right] = a[left];
- }
- a[left] = pivot;
- return left;
- }
这个方法还有一个变形,就是:从左到右,遇到小的停;从右向左,遇到大的停;互换;碰头的时候,把pivot放到对应位置上(通过交换得到)。和这个区别不大
2)简化pivot版(殷版)
实际上寻找pivot不必要那么费劲,从这边到那边,很难记住。殷版的比较实用,好记:在for循环中,让pivot从左到右比较,遇到一个小的,就把pivotpos++(pivotpos从left取值开始),然后pivotpos和当前第i个元素值进行交换。等循环结束之后,pivot与所有的值都进行了一次比较,pivotpos也记录下了这样一个位置:左边的值都小,右边的之都大,然后把pivot值放到这里;OK。
《代码》
- template <class T>
- int Partition(T a[], int left, int right)
- {
- int pivotpos = left;
- T pivot = a[left];
- for (int i = left + 1; i <= right; i++)
- {
- if (a[i] < pivot && ++pivotpos != i)
- {
- swap(a[i], a[pivotpos]);
- }
- }
- swap(a[left], a[pivotpos]);
- return pivotpos;
- }
3)对整个快速排序的性能优化
a)当数据个数为5->25的时候,才能直接插入法的效率比qsort快至少10%。所以,在递归过程中,待排数据个数小于M的时候,使用直接插入法;
b)基本同上,个数小于M的时候,直接忽略。完成之后,得到一个总体上大致已经排好序的序列,然后对整体使用直接插入法
c)对于pivot的选择,除了上述的两种基本方法外,可以提高。当每次划分得到的两个子序列的个数非常接近的时候,效果是最好的;所以我们希望得到一个最接近中值的pivot,至少不能弄成最差情况。所以,可以把pivot的值等于:right、left和中点这三
个值的中间值(参见文章“三个数字的中间值“);
d)综合运用前三种方案,可以把qsort的效率提高20->25%。