续论秋招中的排序(排序法汇总-------上篇)(因为篇幅过大),下面咱们继续学习。html
待续学习
(原创,转发须注明原处)ui
五、快速排序spa
快速排序在面试中常常被问到(包括各类改进思路),此排序算法能够说是用得最为普遍的排序算法,是对冒泡排序的一种改进,每次交换是跳跃式的。其比较易于实现,同时它能够处理多种不一样的输入数据,许多状况下所消耗的资源也较其余排序算法少。理想状况下,其只使用一个小的辅助栈,对N个数据项排序的平均时间复杂度为O(NlogN),而且它内部的循环很小,缺点就是不稳定的排序算法,在最坏的状况下时间复杂度为O(N^2)。其实过程很简单,想一想就明白了,因此不想再细说,图解比语言更明确更精准,下面给出一轮划分的图解,接着递归两边的部分便可,图以下:3d
即划分后,key=32的最终位置的左边的全部数据值都小于(或等于)key,右边的全部数据值都大于(或等于)key。指针
代码以下:code
int partion(int a[], int left, int right)//划分操做
{ int lleft = left, rright = right, key = a[lleft]; while (lleft < rright) { while (lleft < rright && a[rright] >= key)//找右边第一个小于key的
rright--; a[lleft] = a[rright]; while (lleft < rright && a[lleft] <= key)//找左边第一个大于key的
lleft++; a[rright] = a[lleft]; } a[lleft] = key; return lleft;//key的最终位置
}
递归:htm
void Qsort(int a[], int left, int right) { if (left >= right)//递归结束条件 return; int index = partion(a, left, right); Qsort(a, left, index - 1);//递归key左边区域 Qsort(a, index + 1, right);//递归key右边区域 }
非递归:
void qsort(int a[], int left, int right) { std::stack<int> st; if (left < right) { int mid = partion(a, left, right); if (left < mid - 1) //将左边边界入栈 { st.push(left); st.push(mid - 1); } if (mid + 1 < right)//将右边边界入栈 { st.push(mid + 1); st.push(right); } while (!st.empty()) //若是栈不为空,即排序未结束 { int q = st.top(); st.pop(); int p = st.top(); st.pop(); mid = partion(a, p, q);//继续划分 if (p < mid - 1) { st.push(p); st.push(mid - 1); } if (mid + 1 < q) { st.push(mid + 1); st.push(q); } } } }
在最坏的状况下,若是要排序的序列是有序的,则快速排序将退化为冒泡排序,时间复杂度变为O(N^2);在最好的状况下,每次划分过程都刚好把文件分割成两个大小彻底相等的部分,则时间复杂度为O(NlogN)。
改进算法:
上面咱们也提到了,在最坏的状况下,若是待排序列已经有序了,则快排会变得很是低效。下面将会介绍改进的方法:
改进选取的参考枢纽元素:一、选取随机数做为枢轴。可是随机数的生成自己是一种代价,根本减小不了算法其他部分的平均运行时间。二、使用左端,右端和中心的中值作为枢轴元(即三者取中)。三、每次选取数据集中的中位数作枢轴。
改进代码:
void exchange(int*a, int *b) { int tmp; tmp = *a; *a = *b; *b = tmp; } void compexch(int* a, int* b) { if (*a > *b) exchange(a, b); } int partion(int a[], int left, int right)//划分操做 { int lleft = left + 1, rright = right - 1; int key; //三者取中法 int mid = (left + right) / 2; compexch(&a[left], &a[mid]); compexch(&a[left], &a[right]); compexch(&a[mid], &a[right]); key = a[mid]; while (lleft < rright) { while (lleft < rright && a[rright] >= key)//找右边第一个小于key的 rright--; a[lleft] = a[rright]; while (lleft < rright && a[lleft] <= key)//找左边第一个大于key的 lleft++; a[rright] = a[lleft]; } a[lleft] = key; return lleft;//key的最终位置 } void quicksort(int a[], int left, int right) //小区域排序采用插入排序方法(对于有序序列的排序效率较高O(N)),三者取中 { if (left >= right)//递归结束条件 return; if (right - left <= 5)//当区域段长度小于5时,改用插入排序法 { insertion(a, right-left+1); return; } int i; i = partion(a, left, right); quicksort(a, left, i - 1); quicksort(a, i + 1, right); }
下面咱们继续考虑另外方面的改进,当待排序的序列中有大量的重复元素时,标准的快排又变得极其低效(哎呀,问题怎么这么多啊,烦不烦啊。。。。)。嘿嘿,固然,有解决的方法了,最直观的想法是将序列划分为三部分(三路划分)而再也不是两部分了,即比划分元素小的部分、比划分元素大的部分和与划分元素相等的部分。
三路划分仅仅在标准的快排下稍做改动:遍历时将遇到的左边区域中的与划分元素相等的元素放到序列的最左边,将遇到的右边区域中的与划分元素相等的元素放到序列的最右边。继而,当两个扫描的指针相遇时,序列中与划分元素相等的元素的位置就精肯定位了。对于重复的元素的额外工做量只与所找到的重复的元素的个数呈线性相关;即便在没有重复的元素的状况下,在方法没有额外的开销,效果也很好。
代码:
void quicksort(int a[], int left, int right) //小区域排序采用插入排序方法,三者取中,三路划分 { if (left >= right) return; int lleft = left - 1, rright = right , k, p=left-1, q=right; int key; if (right - left <= 1) { insertion(a, right-left+1); return; //递归返回条件 } int mid = (left + right) / 2; compexch(&a[left], &a[mid]); compexch(&a[left], &a[right]); compexch(&a[mid], &a[right]); key = a[right]; while(true) { while (a[++lleft]< key); while (key< a[--rright]) { if (rright == left) { break; } } if (lleft >= rright) { break; } exchange(&a[lleft], &a[rright]); if (a[lleft]== key) { p++; exchange(&a[p], &a[lleft]); } if (a[rright]== key) { q--; exchange(&a[q], &a[rright]); } } exchange(&a[lleft], &a[right]); lleft = lleft - 1; rright = lleft + 1; for (k = left; k <= p; k++, lleft--) { exchange(&a[k], &a[lleft]); } for (k = right - 1; k >= q; k--, rright++) { exchange(&a[k], &a[rright]); } quicksort(a, left, lleft); quicksort(a, rright, right); }