快速排序&冒泡排序
2017-11-29 13:14
134 查看
交换排序
1.冒泡排序
从前往后两两判断并交换,把最大的交换到最后面,然后向左缩小无序区间。void BubbleSort(int *a, size_t n) { for (size_t j = n-1; j > 0; j--)//无序区间逐渐变小 { bool IsSequence = true; for (size_t i = 0; i < j; i++)//每一趟从无序区间里冒出来一个最大的 { if (a[i]> a[i + 1]) { IsSequence = false; swap(a[i], a[i + 1]);//每比较进来一次就交换一次,所以是交换排序,选择排序每次进来只选出坐标。 } } if (IsSequence) break; } }
时间复杂度
时间复杂度O(n^2) 顺序有序时时间复杂度最好是O(n)冒泡与插入排序比较:
如果差一点点有序,插入排序会更好;因为冒泡是更严格的排序,冒泡在有序后,还要在冒一次,才知道他已经有序,才停止冒泡。但插入排序只需要走一遍,加挪动一次就可以搞定。
2.快速排序
每次选出一个值(key)放在合适的位置,怎样把选出的值放入合适的位置呢?
begin从区间左边选出一个比key大的,end从区间右边开始选出一个比key小的,然后begin和end交换,继续上述操作,直到begin和end相遇,然后把begin或者end和key交换。上面这不操作就是把比比key值大的放在右区间,把比key值小的放在左区间.
然后把左右区间有有序化,如何有序化呢?
看成子问题进行递归,继续选出一个值放在合适的位置,继续把左右区间有序化。直到全部有序,也就是区间只有一个值或者没有值。
(一)左右指针法
注意
key若在左边,选小的先走,key若在右边选大的先走,逆序相反。key若在左边,则end选小的先走,因为最后key和begin交换要把小的换到左边,所以begin和end相遇时一定比key小;若key在右边,则begin选大的先走,因为最后key和begin交换,要把大的换到右边,key换到begin和end相遇的地方。
注意,如果区间里有和key相等的两个数据
这时begin和end在和key比较时"a[begin]<=a[key]和a[end]>=a[key]",这是继续的条件,如果不加等号,begin和end就一直等于key,发生死循环。其实只加一个=也可打破死循环,但是两个=更好,防止end==key时end不--,key就会被修改,但也不影响最终结果。
时间复杂度
时间复杂度:每一层是O(n),高度是lgn,那么时间复杂度就是O(n*lgn)。时间复杂度最坏:O(n^2)
若每次选出的key要么是最大要么是最小,那么递归区间只减了1,这样就要递归n次。每一次是n-1,则就是一个1到n的递增序列次,就是O(n^2)。
注意:end要从最右边开始
如果key选右边,左边的全部比key小,key为最大,所以开始时end就要从key开始,而不是key-1。因为key这个位置也需要参与比较,如果左边的都比他小,那么就不要交换位置了,end如果在key-1,那么begin最终会走到key-1,就会使的a[key-1]和a[key]交换。但实际a[key]比a[key-1]大。
优化
三数取中法求key—解决时间复杂度最坏情况通过三数取中法改善最差时间复杂度的情况
拓展
哈希表和快排的时间复杂度不看最坏情况。因为哈希表有负载因子可调节,若是哈希桶还可以挂红黑树;快排有三数取中法。
小区间优化—减少递归次数
当左右区间里的数据少于一定个数时就不在往下递归,直接进行插入排序
代码链接:https://github.com/TerryZjl/DataStructure/commit/591c43fb1095b7b661e1166a5b46b132a444c508
(二) 挖坑法
对单趟排序进行改造,也是通过左右两个指针,先选出一个左边或者右边的值保存到key,原数组的那个key值的位置相当于没有值,是一个坑,现在左边找到大的往右边坑里扔,左边就又有一个坑,再右边找到小的往左边坑里填,最后把key扔到相遇的坑。
void PartSort(int *a, int left, int right) { int key = a[right]; int begin = left; int end = rigth; while(left<right) { while(begin<end&&a[begin]<=tmp) { ++begin; } a[end] = a[begin]; while(begin<end&&a[end]>=tmp) --end; a[begin] = a[end]; } a[begin] = tmp; }
(三) 前后指针法
左右指针法和挖坑法共同特点都是把左边比key大的往右边扔,把右边比key小的往左边扔;但前后指针法的两个指针都是从一边往另一边走,是交换一前一后两个指针,前面指针找比key小的,找到后和后面的指针交换(这里要防止自交换),前后指针都向前挪动一个,若没找到前指针继续向前找,后指针不动(后指针的下一个肯定大于等于key),单链表可以通过前后指针法完成。
//前后指针法 int PartSort3(int *a, int begin, int end) { int cur = begin; int prev = cur - 1; int key = end; // int a[] = { 10, 1, 5, 8, 0, 8, 9, 5, 7, 5 }; while (cur<end) { if (a[cur] < a[key]&&(++prev)!=cur) swap(a[cur], a[prev]); ++cur; } swap(a[++prev], a[cur]); return prev; }
(四)非递归
通过栈把区间的左右下标存起来。//非递归 [left,right] void QuickSortNR(int *a, int left, int right) { stack<int> s; s.push(left); s.push(right); while (!s.empty()) { int right = s.top(); s.pop(); int left = s.top(); s.pop(); int ret = PartSort3(a, left, right); if (left < right) { s.push(left); s.push(ret - 1); s.push(ret + 1); s.push(right); } } }