直接选择排序和快速排序

大家好!这篇我们讲一下直接选择排序和快速排序。
在这里插入图片描述

1 直接选择排序

1.1 基本思想

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

1.2 思想流程

在这里插入图片描述
我们在这里写一个比较优的解法:就是一次找两个数(最大的,最小的),放到合适的位置。
在这里插入图片描述
我们把最小的放到最左边,最大的放到最右边。
在这里插入图片描述
然后再从下一位开始,依次类推。

1.3 具体实现

根据上面的思路,我们转换成代码:
在这里插入图片描述
我们进行测试,可以发现出现了问题:
在这里插入图片描述
这是为什么呢?我们调试来看一下:
在这里插入图片描述
我们可以看到此时最小的下标为1,最大的下标为0,然后交换第一个后,我们再看:
在这里插入图片描述
此时交换结束后,maxi指向的还是0,但是此时最大的数9已经到下标为1的位置了。所以就会发生错误,我们要在这里做一下调整。
在这里插入图片描述

1.4 时间复杂度

这里,我们写的是直接选择排序的优化方法,首次是N,然后是N-2,然后是N-4,然后是N-6…所以时间复杂度是O(N^2),而冒泡排序是N,N-1,N-2,N-3…所以直接选择排序比冒泡排序好点。
但是在顺序有序的情况下:直接选择排序不能判断它是否顺序有序,还是要一层一层的去选择,而冒泡排序会很快。

2 快速排序

2.1 hoare基本思想

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

2.2 hoare思想流程

首先,我们来看一下单趟的,单趟的也分为三个版本:
第一个版本:hoare版本
选出一个key,一般是第一个数或者是最后一个数。
要求左边的值都比key小,右边的值都比key大,和key相等的随便在哪。

我们来看一下动图流程:
在这里插入图片描述
从动图我们可以看到:R找比key小,L找比key大,找到后交换,再继续。相遇以后,把相遇位置的值跟key位置的值交换。

在这里,可能会有同学问一个问题:
如果左边的key值比相遇时的值小,该怎么办?

这个算法只要满足一个条件就可以避免这个问题:
右边先走就可以保证。

它分为两种情况:
第一种情况:R先停下来,L走过去遇到R
在这里插入图片描述
两者交换之后,R先走,R找比key小的,也就是3停下
在这里插入图片描述
然后L走,要找比key大的,但走一步就和R相等了。
在这里插入图片描述
相遇的3比key小。
第二种情况:刚交换完,R先走,R没有找到比key小的直接跟L相遇。
在这里插入图片描述
这种情况,也是比key小的。

上面的例子我们是以左边为key,那么我们要右边为key该怎么办呢?
在这里插入图片描述
同样的道理,我们左边先走,就可以保证相遇位置的值比key大。
在这里插入图片描述

2.3 hoare法具体实现

首先,我们实现单趟的:
在这里插入图片描述
但是,这样写会有两个问题。
第一个问题:
在下面的这组数据,它会出现死循环。
在这里插入图片描述
可以看到key和right都是5,不满足循环条件,不进入循环。
然后key和left都是5,也不满足循环条件,不进入循环。
交换left和right都没有发生改变,所以发生了死循环。
我们可以这样去修改:
在这里插入图片描述
第二个问题:
修改之后还是有问题。我们看下面的这组数据:
在这里插入图片描述
这里key为1,right一直减减,当right=1时,满足循环条件,又减了一次,就越界了。
所以,我们还要加一个条件:
在这里插入图片描述
单趟排完以后,key已经放在正确的位置了。
如果左边有序,右边有序,那么我们整体就有序了,那么左边和右边如何有序呢?
分治解决子问题。
单趟排序的结果如下:
在这里插入图片描述
现在6已经在正确的位置上了,现在我们要让6的左边和右边也有序。
在这里插入图片描述
在进行一次单趟排序后,你会发现3也到了正确的位置上。然后我们在分治3的左边和右边。
完整流程如下:
在这里插入图片描述
完整代码如下:
在这里插入图片描述

2.4 挖坑法思想流程

首先,我们来看一下动图展示:

然后,我来详细说一下挖坑法的流程:
在这里插入图片描述
首先,我们将最左边的放到key里,这样最左边就形成了一个坑位。然后,R先走找比key小。
在这里插入图片描述
找到之后,我们将5放到坑位里。这样5的位置就变成了新的坑位。
在这里插入图片描述
然后L再走找比key大,我们找到7后,将7放到坑位。
在这里插入图片描述
R再找小,找到4放到坑位里。
在这里插入图片描述
L再找大,找到9放到坑位里。
在这里插入图片描述
R再找小,把3放到坑位里。
在这里插入图片描述
L再找大,但和R相遇了,就把key放到坑位里,就结束了。
在这里插入图片描述
这样,挖坑法单趟的流程就结束了。
我们看一下它的动态流程图:
在这里插入图片描述
那么挖坑法和hoare法有什么区别呢?
本质上没有什么区别。只是挖坑法更好理解。
1.不需要理解为什么最终相遇位置比key小。
2.不需要理解为什么左边做key,右边先走。
因为开始我们选的左边为坑,那么肯定就会自然的认为,R先走找小,把坑补上,最后相遇时把坑补上就行。

2.5 挖坑法具体实现

和上面的思路类似:
在这里插入图片描述

2.6 前后指针法思想流程

首先,我们来看一下动图过程:

然后,我说一下具体的流程:
一开始,我们定义一个prev指针指向序列开头,定义一个cur指针指向prev指针的后一个位置。
在这里插入图片描述
然后判断cur指针指向的数据是否小于key,若小于,则prev指针后移一位,然后和cur指针指向的内容交换。然后cur指针++
从上组数据,我们可以看出1,2,都是小于key的,和prev交换也不改变什么,当cur一直走到3的位置:
在这里插入图片描述
此时,prev指针应该后移一位,然后和cur指针指向的内容交换,然后cur指针++
在这里插入图片描述
此时,cur指向的4是小于6的,然后怕prev向后移动一位,交换内容后,cur++
在这里插入图片描述
此时cur指向的还是比key小的,prev++后,交换cur的内容和prev的内容。
在这里插入图片描述
然后cur++,此时10比key大,cur再++,8比key大,再++
在这里插入图片描述
当cur出序列时,交换key和prev的内容。
在这里插入图片描述
这样单趟的就结束了。

在这里,prev和cur的关系是:
1.cur还没遇到比key大的值时,prev紧跟着cur,一前一后。
2.cur遇到比key大的值以后,prev和cur之间间隔一段比key大的值的区间。

我们看一下它的动态流程图:
在这里插入图片描述

2.7 前后指针法具体实现

在这里插入图片描述
有人会问,如果key选的是最右边要怎么写?
我们需要将prev和cur错位一下:
在这里插入图片描述
我们将cur给left,prev给left-1
其它还是一样的,cur找小,6比8小,++prev,此时prev和cur指向的数6相等。所以cur再++
在这里插入图片描述
依次类推下去…
在这里插入图片描述
此时,cur指向的3比key小,++prev,交换
在这里插入图片描述
cur++依次下去。
在这里插入图片描述
当cur指向key时,就结束。
但是这里,我们不能直接交换prev和cur,需要prev++后再交换key
在这里插入图片描述
代码如下:
在这里插入图片描述

2.8 时间复杂度

快速排序的时间复杂度是多少呢?
最好的情况下:每次选key都是中位数
意思就是:每次单趟排序后,key在中间

它就会形成这样的:
在这里插入图片描述
每层选key的都需要走n次,一共有logN层,所以时间复杂度就会为:O(N*logN)
最坏的情况下:每次选的key是最小或者最大的,也就是顺序有序的情况下
也就是这样的:
在这里插入图片描述
那么就是一个等差数列了,它的时间复杂度为:O(N^N)

2.9 如何优化快速排序

2.9.1 三数取中法选key

在这里插入图片描述
选不是最大的,也不是最小的那个。
这样,如果有序,我们也不会选到最大的和最小的了。
所以,我们先写一个三数取中的一个函数:
在这里插入图片描述
然后,把选到中间的数和最左边或最右边的交换,其它的思路不变。
在这里插入图片描述

2.9.2 小区间优化

什么叫做小区间优化呢?
就是区间很小时,不再使用递归划分的思路让它有序,而是直接使用插入排序对小区间排序,减少递归调用。
什么意思呢?我们来看一下快排递归调用展开简化图:
在这里插入图片描述
假设,我们有1000个数,也就是有10层,最后一层都是1个数,也有2^9个。倒数第二层都是3个数,有2 ^8个。所以,我们可以把后面几层的递归改成直接插入排序:

void QuickSort(int* a, int begin, int end)
{
	// 小区间直接插入排序控制有序
	//当区间个数小于等于10个数时,就直接插入排序
	if (end - begin + 1  <= 10)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int keyi = PartSort3(a, begin, end);
		// [begin, keyi-1]keyi[keyi+1, end]
		QuickSort2(a, begin, keyi - 1);
		QuickSort2(a, keyi + 1, end);
	}
	
}

因为是闭区间,个数为二者相减+1
在这里插入图片描述
每次的插入排序的开头,我们应该是a+begin

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我来为您介绍一下 C 语言中的快速排序直接选择排序快速排序是一种常用的排序算法,其基本思想是采用分治策略,将待排序序列分成两个子序列,其中一个子序列的所有元素都小于另一个子序列的所有元素,然后再对这两个子序列分别进行快速排序,以此类推,直到整个序列有序为止。快速排序的时间复杂度为 O(nlogn),是一种比较高效的排序算法。 以下是 C 语言中实现快速排序的代码: ```c void quick_sort(int arr[], int left, int right) { if (left < right) { int i = left, j = right, x = arr[left]; while (i < j) { while (i < j && arr[j] >= x) j--; if (i < j) arr[i++] = arr[j]; while (i < j && arr[i] < x) i++; if (i < j) arr[j--] = arr[i]; } arr[i] = x; quick_sort(arr, left, i - 1); quick_sort(arr, i + 1, right); } } ``` 直接选择排序是一种简单的排序算法,其基本思想是每次找到最小的元素并将其放到序列的起始位置,然后再在剩余的元素中找到最小的元素并将其放到已排序部分的末尾,以此类推,直到整个序列有序为止。直接选择排序的时间复杂度为 O(n^2),是一种比较低效的排序算法。 以下是 C 语言中实现直接选择排序的代码: ```c void select_sort(int arr[], int len) { int i, j, min, temp; for (i = 0; i < len - 1; i++) { min = i; for (j = i + 1; j < len; j++) { if (arr[j] < arr[min]) min = j; } if (min != i) { temp = arr[i]; arr[i] = arr[min]; arr[min] = temp; } } } ``` 希望这些代码能够帮助您了解快速排序直接选择排序的基本思想和实现方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学代码的咸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值