您的位置:首页 > 其它

快速排序&冒泡排序

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);
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: