排序算法-nlogn级别
来源:互联网 发布:酒瓶设计软件 编辑:程序博客网 时间:2024/05/20 15:40
对于排序算法中最优秀的莫过于nlogn级别的算法。基本都是采用的分治思想。
比如归并排序和快速排序。
这两个算法可以分为两类。因为思想上有所不同,侧重点也不同。下面来分析下这两种算法。
1.归并排序
思想,使用分治思想,使用递归将数据不停二分。然后再使用O(n)级别的算法进行归并操作。二分的层数将会是logn,而每一层归并操作的效率是n。所以最终复杂度为nlogn。
根据归并排序的思想(重点在于归并,而不是二分)我们有两种选择
(1)一种是利用递归,进行自上而下的二分,然后进行归并。
(2)另一种是直接使用迭代的方式自底向上的进行归并。
分析:
自顶向下的递归的方式,由于涉及到函数参数的入栈和出栈操作。会降低效率。(存在递归调用溢出的风险)
自底向上的迭代的方式,不涉及入栈出栈操作。(不存在递归调用溢出的风险)
优化方式:以上两种算法都可以进行两种优化
(1)当进行小区段的排序时可以使用插入排序法,因为小区段有序的概率更高。区段值需要统计考虑。
(2)让进行merge操作时,可以判断左边的右端是不是小于或等于右边的左端。如果是可以跳过此次的merge操作,从而提高性能。
/*归并排序Remarks:前闭后闭的空间*/template<typename T>void merge(T * arr, int left, int middle, int right) {T * iArrBack = new T[right - left + 1];//copyfor (int i = 0; i < right - left + 1; ++i) {iArrBack[i] = arr[i + left];}for (int i = 0, j = middle - left + 1, k = left; k <= right; ++k) {if (i > middle - left) {arr[k] = iArrBack[j];++j;}else if (j > right - left) {arr[k] = iArrBack[i];++i;}else if (iArrBack[i] > iArrBack[j]) {arr[k] = iArrBack[j];++j;}else {arr[k] = iArrBack[i];++i;}}delete[] iArrBack;}template<typename T>void MergeSrotCall(T * arr, int left, int right) {if (left >= right)return;int middle = (right + left) / 2;MergeSrotCall(arr, left, middle);MergeSrotCall(arr, middle + 1, right);merge(arr, left, middle, right);}template<typename T>void MergeSrot(T * arr, int n) {MergeSrotCall(arr, 0, n - 1);}
/*归并排序:迭代版本*/template <typename T>void MergeSortPro(T * arr, int n) {for (int i = 0; i < n; i += 150)insertionSortPro(arr, i, min(n - 1, i + 150));for (int iGroupSize = 1; iGroupSize <= n; iGroupSize+=iGroupSize)for (int i = 0; i + iGroupSize - 1 < n; i += (iGroupSize + iGroupSize))if(arr[i+iGroupSize-1]>arr[i+iGroupSize])merge(arr, i, i + iGroupSize - 1, min(i + iGroupSize + iGroupSize - 1, n - 1));//insertionSrotPro(arr, n);}
2.快速排序
算法的思想:取一个key值,利用此key值将数据分为两部分,然后再对左右两部分分别递归操作。二分思想能尽可能的减少总的遍历次数从而提高效率。
分析效率:
(1)理想情况下,如果每次取的key刚好是中间的值,那二分层数就是logn,每一次的快排的partition操作为O(n)于是能达到nlogn。
(2)数据有序度较高的情况:key值的选取如果取的位置是固定位置,比如左边第一个,那这种情况下的二分及其不均匀,将会导致左边没有,只有右边,那么层数将会是n层,那么效率将变成n^2级别。
解决方法:随机的获取key值,那么n较大的情况下,第一层取到端值的概率接近n/1.那么这个概率很小。一层一层这样下去,最终取得非端直的概率从统计角度讲,会接近99.99%。
(3)数据大量重复的情况,比如全是0,那么我们的排序会一从左边一直移动到右边,因为相等的情况我并不会去交换。因为这样会大量消耗存储性能(赋值操作)。
解决方法:<1>采用双路的情况,但是相等的情况也进行交换,最终发现存储性能的消耗远小于端值对性能的影响。
<2>更好的方式是采用三路的快排。把等于的部分也单独作为一个区域。这样等于的部分不用进行下一次的快排。(三路情况存储性能消耗较大,所以有序度较低的情况效率会略小于双路,但是差距不大,在100w数据个数内基本无差距)
快速排序(重点在于二分,二分会直接决定算法性能【从(2)分析中可以看出】)。
单路:
/*快速排序*///[left, right]template <typename T>int __partition(T * arr, int left, int right) {//[left, right]swap(arr[left], arr[rand() % (right - left + 1) + left]);//随机取key为了优化,近乎有序的数组int iKey = arr[left];int j = left;for (int i = left + 1; i <= right; ++i)if (arr[i] < iKey)swap(arr[i], arr[++j]);swap(arr[j], arr[left]);return j;}//[left, right]template <typename T>void quickSortCall(T * arr, int left, int right) {//if (left >= right)//return;if (right - left < 30) {//32个insertionSortPro(arr, left, right);//优化return;}//Partitionint iPartition = __partition(arr, left, right);quickSortCall(arr, left, iPartition);quickSortCall(arr, iPartition + 1, right);}template <typename T>void QuickSort(T * arr, int n) {srand(time(NULL));quickSortCall(arr, 0, n - 1);}
2.双路
Remake:这里双路第一种是来源百度百科,经过思考发现,这个算法实现,虽然在数据赋值次数上有优势,但是无法针对多重复值情况进行优化。
所以最好采用下面的那种双路。
1.找到一个后赋值/*在partition操作时,采用双路*/template <typename T>int __partitionPro(T * arr, int left, int right) {//[left, right]swap(arr[left], arr[rand() % (right - left + 1) + left]);//随机取key为了优化,近乎有序的数组int iKey = arr[left];int i = left, j = right;while (i < j) {for (; i < j && arr[j] >= iKey; --j) {}arr[i] = arr[j];for (; i < j && arr[i] <= iKey; ++i) {}arr[j] = arr[i];}arr[i] = iKey;return j;}template <typename T>void quickSortCallPro(T * arr, int left, int right) {//if (left >= right)//return;if (right - left < 150) {//32个insertionSortPro(arr, left, right);//优化return;}//Partitionint iPartition = __partitionPro(arr, left, right);quickSortCallPro(arr, left, iPartition);quickSortCallPro(arr, iPartition + 1, right);}template <typename T>void QuickSortPro(T * arr, int n) {srand(time(NULL));quickSortCallPro(arr, 0, n - 1);}2.找到两个后交换(能针对高度重复的数据较快的排序)/*在partition操作时,采用双路*/template <typename T>int __partitionPro(T * arr, int left, int right) {//[left, right]swap(arr[left], arr[rand() % (right - left + 1) + left]);//随机取key为了优化,近乎有序的数组int iKey = arr[left];int i = left + 1, j = right;while (true) {while (i <= right && arr[i] < iKey)++i;while (j >= left && arr[j] > iKey)--j;if (i > j)break;swap(arr[i], arr[j]);++i;--j;}swap(arr[left], arr[j]);return j;}template <typename T>void quickSortCallPro(T * arr, int left, int right) {//if (left >= right)//return;if (right - left < 150) {//32个insertionSortPro(arr, left, right);//优化return;}//Partitionint iPartition = __partitionPro(arr, left, right);quickSortCallPro(arr, left, iPartition);quickSortCallPro(arr, iPartition + 1, right);}template <typename T>void QuickSortPro(T * arr, int n) {srand(time(NULL));quickSortCallPro(arr, 0, n - 1);}
3.三路
/*在partition操作时,采用三路*/template <typename T>void __partitionThree(T * arr, int left, int right) {//if (left >= right)//return;if (right - left < 150) {//32个insertionSortPro(arr, left, right);//优化return;}//[left, right]swap(arr[left], arr[rand() % (right - left + 1) + left]);//随机取key为了优化,近乎有序的数组int iKey = arr[left];int iL = left + 1, i = left + 1, iR = right;while (true) {if (arr[i] == iKey)++i;else if (arr[i] < iKey) {swap(arr[i], arr[iL]);++iL;++i;}else {swap(arr[i], arr[iR]);--iR;}if (i > iR)break;}swap(arr[left], arr[iL - 1]);__partitionThree(arr, left, iL - 2);__partitionThree(arr, iR + 1, right);}template <typename T>void QuickSortThree(T * arr, int n) {srand(time(NULL));__partitionThree(arr, 0, n - 1);}
经过测试不管是双路经过重复值优化的快排还是三路快排,在任何情况下都会比归并快。
总结
- 排序算法-nlogn级别
- nlogn级别的排序算法(1)归并排序
- nlogn级别的排序算法(2)快速排序
- O(nlogn)排序算法--QuickRank
- 数据结构之排序算法之O(nlogn)
- 两种O(nlogn)级别的排序,归并排序和快速排序
- 排序算法之快速排序(O (NlogN))
- 排序算法-n^2级别
- 基于比较的排序算法的最优下界---NlogN
- 排序算法-冒泡排序(入门级别)
- nlogn排序-归并排序
- nlogn排序-快速排序
- 归并排序{nlogn}
- hdu1800 排序二分法nlogn
- 排序 O(nlogn)
- 希尔排序-nlogn
- 堆排序nlogn
- 归并排序-nlogn
- C++程序设计案例实训教程第1章
- path
- Python 编程进阶经典算法逻辑编程
- 导向滤波小结:从导向滤波(guided filter)到快速导向滤波(fast guide filter)的原理,应用及opencv实现代码
- shell基础
- 排序算法-nlogn级别
- 第二课 Netty服务端
- shell基础命令
- 线程安全及解决机制简介
- oAuth2 feign 授权模式
- shell 展开
- 拆分窗口
- python中列表的基本操作
- Wind River workbench介绍