常用排序之————交换排序

交换排序

基本思想:所谓交换,就是根据序列中两个 记录键值 的比较结果来对换这两个记录在序列中的位置。
交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动
冒泡排序的特性总结:
  1.  冒泡排序是一种非常容易理解的排序,实践意义十分小。
  2.  时间复杂度:O(N^2)
  3.  空间复杂度:O(1)
  4.  稳定性:稳定

快速排序

快速排序是 一种类似于二叉树结构的交换排序方法,其基本思想为: 任取待排序元素序列中 的某元素(key)作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止
基本框架为
假设按照升序对 array 数组中 [left, right) 区间中的元素进行排序
按照基准值(div)对 array 数组的 [left, right) 区间中的元素进行划分
划分成功后以 div 为边界形成了左右两部分 [left, div) [div+1, right)
递归排 [left, div)
递归排 [div+1, right)

可以发现,运行规则与二叉树的遍历十分相近,因此在写这个框架时;可以想想二叉树遍历的框架。之后可以发现找到如何按照基准值(div)对array数组的 [left, right)区间中的元素进行划分即可。

常见方法

第一种hoare版本

基本思想

以按小序为例

先找key值,左边和右边各向中间找;右边先向中间找小于key的值,左边在向中间找大于key的值,都找到后,交换左边和右边的值;然后重复此过程,知道右边与左边相遇,然后让相遇位置与key交换。至此完成一次循环。

图片解析

到这里,就是与二叉树的遍历类似。从相遇位置分开,然后递归。

当然如果认真思考和观察,一定会想到,

为什么相遇位置一定比key小(相遇位置一定与key交换)或者说达到这个目的有什么要求?

以上述数据为例子

左边做key,右边先走,可以保证相遇位置比key小

右边做key,左边先走,可以保证相遇位置比key大

以左边做key为例子

分析:一共有两种情况

  1. 左边移动与右边相遇,右边先停下找到了比key小的,然后左边与右边相遇(就是上述图片的情况)
  2. 右边移动与左边相遇,一定是之前左边与右边交换完,左边变成比key小的数后,右边移动与左边相遇。

因此无论哪种情况,都能要求的情况。

参考代码

void QuickSort(int* a, int left, int right)
{
	if ( left >= right)
		return;
	int keyi = Mid(a,left,right);
	int begin = left, end = right;
	while (begin < end)
	{
		while (a[end] >= a[keyi])
		{
			end--;
			if (begin >= end)
				break;
		}

		if (begin >= end)
			break;
		while (a[begin] <= a[keyi])
		{
			begin++;
			if (begin >= end)
				break;
		}
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[keyi], &a[begin]);
	int left1 = left, right1 = begin - 1;
	int left2 = begin + 1, right2 = right;
	QuickSort(a, left1, right1);
	QuickSort(a, left2, right2);
	
}
挖坑法
挖坑法是对上述方法的一个进阶;虽然 没有效率上的提升,但是能忽略为什么左边做key,右边先走的问题 和 为什么 相遇位置比key小的问题。
基本思想
先弄一个数据存放在一个临时变量 key= 中。然后就形成了第一个坑位;之后与上面的方法差不多,右边先向中找,找到比key小的,补到第一个坑位,然后又形成了一个新坑位;然后左边先向中间找比key 大的,补到新的坑位,并产生一个新坑位。重复此操作知道左边与右边相遇,把存到key的数,补到相遇位置的坑位。
图片解析
代码参考
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	int keyi = Mid(a, left, right);
	int begin = left, end = right;
	int key = a[keyi];
	while (begin < end)
	{
		while (a[end] >= a[keyi])
		{
			end--;
			if (begin >= end)
				break;
		}
		a[keyi] = a[end];
		keyi = end;
		if (begin >= end)
			break;
		while (a[begin] <= a[keyi])
		{
			begin++;
			if (begin >= end)
				break;
		}
		a[keyi] = a[begin];
		keyi = begin;
	}
	a[keyi] = key;
	int left1 = left, right1 = begin - 1;
	int left2 = begin + 1, right2 = right;
	QuickSort(a, left1, right1);
	QuickSort(a, left2, right2);

}

对快速排序的优化

key的寻找

如果key为最大或者最小时,会导致程序的运行很缓慢,因此设计Mid函数 来寻找key值

int Mid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[mid]> a[left])
	{
		if (a[right] > a[mid])
		{
			return mid;
		}
		if (a[left] > a[right])
		{
			return left;
		}
		else {
			return right;
		}
	}
	else if (a[mid] < a[left])
	{
		if (a[right] < a[mid])
		{
			return mid;
		}
		if (a[left] < a[right])
		{
			return left;
		}
		else {
			return right;
		}
	}
}

小区间优化

经过使用,运算后可以知道,快速排序,在元素数量十分少的情况下运行与其他排序相比是十分缓慢的。因此小区间优化可以大大提升快排的效率

一般会在上述代码中添加

if (right - left + 1<= 10)
{
	InertSort(a, right - left + 1);
}

其中其他排序可以在其他文章中找到;

快速排序的特性总结:
  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速
  2. 时间复杂度:O(N*logN)                                                                                                                       通过这个图片可知,最坏每组会循环 N 次  一共 有 log N 组 ( 与二叉树 层数 算 个数 一样 的算法 不知道的 可以 翻我以前的 文章)
  3. 空间复杂度:O(logN)                                                                                                                     递归层数 log N  则会有 log N 的空间复杂度;
  4. 稳定性:不稳定
  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值