交换排序 快速排序 冒泡排序

本文详细介绍了交换排序的基本思想,包括冒泡排序和快速排序的三种版本:hoare版本、挖坑版本和前后指针版本。每种版本都配以动图、思想解析、图文说明和代码实现。此外,还讨论了快速排序的优化策略,如三数取中法和随机选取法,以及其时间复杂度和空间复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

交换排序

1. 基本思想

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排
序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。


2. 冒泡排序

这边查看我往期制作的冒泡排序,有很完整的排序过程。

往期冒泡瞬移区,请点我


3. 快速排序 · 升序(递归)

快速排序有几个比较热门的版本,我这边介绍三种 :

1.hoare版本

2.挖坑版本

3.前后指针版本。


3.1 hoare版本

3.1.1 动图

请添加图片描述


3.1.2 思想(配合动图理解)

我们首先选择一个key值作为基准(这里选择第一个值当),用两个指针left,right,begin,end。left和begin都指向第一个元素,right和end都指向末尾元素。

先让right走,right走到比key小或等于key的地方停下,然后left走,走到比key大的地方停下,最后交换left和right指向的值的位置。

途中如果left遇到right,则交换left(或right,都指向同个位置)和key指向元素的位置,跳出循环,这样单趟排序就完成了。接着开始递归了,将数据分为3份,[begin , key-1] key [key + 1 , end](就是以key为分界线,将key左侧和右侧递归),将[begin , key-1] 和 [key + 1 , end] 递归。这样子单次递归就完成了,我们会发现key(交换后)左边全部会比key小,key右边会比key大。通过不断递归,一直左边比右边大,就完成了排序。


3.1.3 图文详解
我们就拿这个数组剖析。

left,key,begin指向第一个元素,right和end指向末尾元素。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sCwiPSgB-1683181563370)(image-20230426131610244.png)]

我们让right先走(注意一定要让key另一边的先走,不然会出问题),找到<=key或者与left相遇停下。这里是遇到了<key的停下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dT7LZp5N-1683181563371)(image-20230426150343462.png)]

然后到left移动,找>key或者与right相遇停下。这里找到7 比key小。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yT1I16Xu-1683181563371)(image-20230426150512694.png)]

交换left和right指向元素的位置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5q94Iv34-1683181563372)(image-20230426150613634.png)]

继续,right走。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-euqvpLI0-1683181563372)(image-20230426151136192.png)]

left走。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DVwjfc1u-1683181563372)(image-20230426151236400.png)]

交换left和right指向元素的位置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRLNbFY5-1683181563373)(image-20230426151315274.png)]

right走,right遇到了left停下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E7l0BM5u-1683181563373)(image-20230426151449427.png)]

交换left和key指向元素的位置。
在这里插入图片描述

这样循环一轮后,原key(6)的左边比6小,原key(6)的右边比6大。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HzkJmKaU-1683181563374)(image-20230426151746774.png)]

接着,开始递归把left的左边和left右边分别递归,继续排序。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YC4X8brQ-1683181563374)(image-20230426152950224.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TY6OG8fk-1683181563374)(image-20230426153531211.png)]

大概步骤上面已经描述过了,我这边只展示左侧。

一切照旧,第一个元素给到left和begin,最后一个元素给到right和end。因为这里出现指针递归后再次使用的情况,所以我们在给指针赋值的时候,不能只考虑单次,特别是给left赋值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m2XDzpMa-1683181563375)(image-20230426153857834.png)]

一样right先走,然后到left走,left相遇到right。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-52sbVhkn-1683181563375)(image-20230426154243300.png)]

交换位置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xXiBoB3y-1683181563376)(image-20230426154318312.png)]

递归。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0KQ7IeGW-1683181563376)(image-20230426154612501.png)]

right走,遇到left,交换left和key的值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j3NH9K5p-1683181563376)(image-20230426154828669.png)]

递归进来后,会发现只有一个元素(出现left == right的情况)或者没有元素,这种时候就不要进行任何操作,直接返回。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oYVruvDY-1683181563377)(image-20230426155443075.png)]

这样一边的排序就完成了。


3.1.4 代码
//交换
void Swap(int* left, int * right)
{
	int tmp = *left;
	*left = *right;
	*right = tmp;
}


//快速排序key法
void PartSort1(int* a, int left, int right)
{
	//只有一个元素返回
	if (right - left <= 1)
		return;

	int begin = left;
	int end = right;

	//将第一个元素定为keyi
	int keyi = left;

	while (left <right)
	{
		//单趟
		//让right先走,直到right<=keyi才停或者遇到left
		while (left != right && a[right] > a[keyi])
			right--;
		//right走完到left走,直到left遇到>keyi才停下来或者遇到right
		while (left != right && a[left] <= a[keyi])
			left++;

		//不相遇,交换左右
		if (left != right)
		{
			Swap(&a[left], &a[right]);
		}
		else //相遇交换left和keyi
		{
			Swap(&a[left], &a[keyi]);
			break;
		}
	}

	//[left, keyi - 1] keyi		[keyi +1, right]

	PartSort1(a, begin,left-1);
	PartSort1(a, left+1, end);
}


3.2 挖坑版本

3.2.1动图

请添加图片描述


3.2.2 思想(配合动图理解)

其实挖坑版本本质是和hoare版本(key法)时候相同的。同样是right先走找小,left后走找大。不同的是它提前将key的值存起来,left和right在走到符合条件的状况时,采取的是将指向的元素赋值给另一个指针,自己就变成了坑位,最后相遇将存起来的key值给到坑位。递归也是相同的。


3.2.3 代码
//快速排序挖坑法
void PartSort2(int* a, int left, int right)
{
	//只有一个元素返回
	if (right - left <= 1)
		return;

	int begin = left;
	int end = right;

	//key存储初始坑位值
	
	int key = a[left];
	int hole = left;

	while (left < right)
	{
		//单趟
		//让right先走,直到right<=keyi才停或者遇到left
		while (left != right && a[right] > key)
			right--;

		//让right变成hole
		if (left != right)
		{
			a[hole] = a[right];
			hole = right;
		}
		else
			break;

		//right走完到left走,直到left遇到>keyi才停下来或者遇到right
		while (left != right && a[left] <= key)
			left++;

		if (left != right)
		{
			a[hole] = a[left];
			hole = left;
		}
		else
			break;

	}

	//这里就相遇了,坑位填上
	a[hole] = key;
	//[left, keyi - 1] keyi [keyi +1, right]

	PartSort1(a, begin, left - 1);
	PartSort1(a, left + 1, end);
}

3.3 前后指针版本

3.3.1 动图

请添加图片描述


3.3.2 思想(配合动图理解)

同样,首先定义一个基准key(这里用首元素),利用两个指针prev和cur。cur去找比key小的值,prev在比key大的值的前面等候,只要cur符合情况,prev++,然后交换cur和prev指向元素的位置,不断重复直至cur到数据末尾,最后交换prev和key指向元素的位置,就完成单趟排序。其实这个思想不难理解,就是将比key大的值不断向后走,比key小的值不断向左走。prev后一个位到cur之间的值是大于key的,cur走到末尾了,就会变成[key] [比key小] [prev] [比key大] 这样组合,然后通过不断递归,小的在左边,大的在右边,最后也就完成了排序。


3.3.3 代码
//交换
void Swap(int* left, int * right)
{
	int tmp = *left;
	*left = *right;
	*right = tmp;
}


//快速排序前后指针法
void PartSort3(int* a, int left, int right)
{
	//只有一个元素返回
	if (right - left < 1)
		return;

	//三数取中
	//int mid = (left + right) / 2;
	//selectKey(&left, &right, &mid);

	int key = left;
	int prev = left;
	int cur = left+1;

	//cur先找到第一个比key大的值
	while (cur <= right && a [cur] <= a[key])
	{
		prev++;
		cur++;
	}

	while (cur <= right)
	{
		//然后cur找小于key的值,或者遇到
		while (cur <= right && a[cur] > a[key])
		{
			cur++;
		}
		if (cur > right)
			break;


		//交换prev+1和cur的位置
		prev++;
		Swap(&a[prev], &a[cur]);
		cur++;

	}

	//到这里cur已经跳出来了,交换key和prev的位置
	Swap(&a[key], &a[prev]);

	//a[key,prev-1] prev a[prev+1,cur-1]
	PartSort3(a, left, prev - 1);
	PartSort3(a,prev + 1, cur - 1);
}

3.4 一些小优化

快速排序其实是有点小弊端的,就是key值的选取。快速排序在面对一些比较有序的数组的时候,选出key值会比较极端(很大或者很小),也就是面临最坏情况的时候,他的时间复杂度是O(n^2)。所以我们可以对他采取一些小优化,如三数取中法,随机选key。


3.4.1 三数取中法

顾名思义,就是取三个数字折中选取。这样key值成为极端数的概率会变小很多,效率也就提高了。

int SelectMidKey(int* a, int begin, int end)
{
	int mid = begin + (end - begin) / 2;
	if (a[begin] < a[mid])
	{
		if (a[mid] < a[end])
			return mid;
		else
		{
			if (a[begin] > a[end])
				return begin;
			else
				return end;
		}
	}
	else
	{
		if (a[mid] > a[end])
			return mid;
		else
		{
			if (a[begin] < a[end])
				return begin;
			else
				return end;
		}
	}
}

3.4.2 随机选取法

随机选取法,就是在这组数据中随机选择一个数当做key值。这也是一种方法,但随机也有概率选取最大或者最小的值,所以往往大多数会用三数取中。


4. 时间和空间复杂度

4.1 快速排序时间复杂度

O(n * log n) 。
最坏情况是O(n^2),当我们key值选择最数据中最大或者最小的情况。

4.2 快速排序空间复杂度

快排空间用于递归嘛,递归一次空间就得增加,也就是O(n * log n)。
最坏情况如上,O(n^2)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值