【排序大餐系列】冒泡,快排(更新中...)

冒泡排序(Bubble Sort)

冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。


冒泡排序算法的步骤:

1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。

2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。

3.针对所有的元素重复以上的步骤,除了最后一个。

4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。


//冒泡排序
void BubbleSort(int arr[], int num)
{
	assert(arr != NULL && num >= 1);

	if (num == 1)
		return;

	bool isChange = true;

	for (int i = 0;i < num - 1 && isChange == true;++i)
	{
		isChange = false;
		for (int j = 0;j < num - 1 - i;++j)
		{
			if (arr[j] > arr[j + 1])
			{
				isChange = true;
				swap(arr[j], arr[j + 1]);
			}
		}
	}
}

设置一个标志位,如果没有交换操作发生,即为排序好的数组。结束即可。


改进版冒泡排序:

采用双向扫描的办法,进行排序


//冒泡排序 改进算法 双向扫描
void DoubleBubbleSort(int arr[], int num)
{
	assert(arr != NULL && num >= 1);

	if (num == 1)
		return;
	int left = 0, right = num - 1;
	int  index = left;

	while (left < right)
	{
		for (int i = left;i < right;++i)
		{
			if (arr[i]>arr[i+1])
			{
				swap(arr[i], arr[i + 1]);
				index = i;
			}
		}
		right = index;
		for (int j = right;j >= left;--j)
		{
			if (arr[j]<arr[j-1])
			{
				swap(arr[j], arr[j - 1]);
				index = j;
			}
		}
		left = index;
	}
}
冒泡排序算法最坏情况和平均复杂度是O(n²),甚至连插入排序(O(n²)算法复杂度)效率都比冒泡排序算法更好。空间复杂度O(1)。


快速排序:

//快速排序的两种方式:
//第一种:两段交替向中间扫描

void QuickSort(int *a, int left, int right)
{
	
	if (left >= right)
		return;

	int i = left;
	int j = right;
	int temp = a[left];
	while (i != j)
	{
		while (i < j && a[j] >= temp)
		{
			j--;
		}
		while (i < j && a[i] <= temp)
		{
			i++;
		}
		if (i <= j)
		{
			swap(a[i], a[j]);
		}
	}
	a[left] = a[i];
	a[i] = temp;
	QuickSort(a, left, i - 1);
	QuickSort(a, i + 1, right);
}
//第二种,单向扫描排序
void QuickSort2(int arr[], int left, int right)
{
	if (left >= right)
		return;

	int small = left-1;

	for (int index = left;index < right;++index)
	{
		if (arr[index]<=arr[right])
		{
			++small;
			if (small != index)
			{
				swap(arr[index], arr[small]);
			}
		}
	}
	++small;
	swap(arr[small], arr[right]);
	QuickSort2(arr, left, small - 1);
	QuickSort2(arr, small+1,right);
}

快速排序算法的几种改进

1.随机化算法

基本的快速排序选取第一个元素作为主元。这样在数组已经有序的情况下,每次划分将得到最坏的结果。一种比较常见的优化方法是随机化算法,即随机选取一个元素作为主元。这种情况下虽然最坏情况仍然是O(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度

  随机化快速排序的唯一缺点在于,一旦输入数据中有很多的相同数据,随机化的效果将直接减弱。对于极限情况,即对于n个相同的数排序,随机化快速排序的时间复杂度将毫无疑问的降低到O(n^2)。

2. 三平均分区法

   与一般的快速排序方法不同,它并不是选择待排数组的第一个数作为中轴,而是选用待排数组最左边、最右边和最中间的三个元素的中间值作为中轴。这一改进对于原来的快速排序算法来说,主要有两点优势:

   (1) 首先,它使得最坏情况发生的几率减小了。

   (2) 其次,未改进的快速排序算法为了防止比较时数组越界,在最后要设置一个哨点。如果在分区排序时,中间的这个元素(也即中轴)是与最右边数过来第二个元素进行交换的话,那么就可以省略与这一哨点值的比较。

关于这一改进还有更进一步的改进,在继续的改进中不仅仅是为了选择更好的中轴才进行左中右三个元素的的比较,它同时将这三个数排好序后按照其顺序放回待排数组,这样就能够保证一个长度为n的待排数组在分区之后最长的子分区的长度为n-2,而不是原来的n-1。也可以在选取中轴值时,可以从由左中右三个中选取扩大到五个元素中或者更多元素中选取,一般的,会有(2t+1)平均分区法(median-of-(2t+1),三平均分区法英文为median-of-three)。

3. 根据分区大小调整算法

    减少递归栈使用的优化,快速排序的实现需要消耗递归栈的空间,而大多数情况下都会通过使用系统递归栈来完成递归求解。对系统栈的频繁存取会影响到排序的效率。

快速排序对于小规模的数据集性能不是很好。没有插入性能高。

快速排序算法使用了分治技术,最终来说大的数据集都要分为小的数据集来进行处理。

当数据集较小时,不必继续递归调用快速排序算法,使用插入排序代替快速排序。STL中sort就是用的快排+插入排序的,使得最坏情况下的时间复杂度也是O(nlgn).这一改进被证明比持续使用快速排序算法要有效的多。

4. 不同的分区方案考虑

    对于快速排序算法来说,实际上大量的时间都消耗在了分区上面,因此一个好的分区实现是非常重要的。尤其是当要分区的所有的元素值都相等是,一般的快速排序算法就陷入了最坏的一种情况,也即反复的交换相同的元素并返回最差的中轴值。无论是任何数据集,只要它们中包含了很多相同的元素的话,这都是一个严重的问题,因为许多“底层”的分区都会变得完全一样。

    对于这种情况的一种改进办法就是将分区分为三块而不是原来的两块:一块是小于中轴值的所有元素,一块是等于中轴值的所有元素,另一块是大于中轴值的所有元素。另一种简单的改进方法是,当分区完成后,如果发现最左和最右两个元素值相等的话就避免递归调用而采用其他的排序算法来完成。

5. 使用多线程:快速排序是分而治之的思想,每个独立的段可以并行进行排序。因此可以利用计算机的并行处理能力来提高排序的性能;

    在大多数情况下,创建一个线程所需要的时间要远远大于两个元素比较和交换的时间,因此,快速排序的并行算法不可能为每个分区都创建一个新的线程。一般来说,会在实现代码中设定一个阀值,如果分区的元素数目多于该阀值的话,就创建一个新的线程来处理这个分区的排序,否则的话就进行递归调用来排序。

 

 

总的来说,对于快速排序算法的改进主要集中在三个方面:

1 选取一个更好的中轴值

2 根据产生的子分区大小调整算法

3 不同的划分分区的方法


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值