快速排序&冒泡排序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dream_1996/article/details/78664147

交换排序

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);
		}
	}
}


猜你喜欢

转载自blog.csdn.net/dream_1996/article/details/78664147
今日推荐