选择排序与冒泡排序简述(持续更新)

选择排序(从小到大)

通俗说法:小时候上体育课排过队伍,体育老师说:你们中间谁最小谁就出列,站到队伍的最前边,然后继续对着剩余的同学说:你们中间谁最小谁就出列,站在刚才那名同学后面,这样的队伍就有了顺序,从小到大。
算法核心:在每趟比较中,找到本趟中最小的元素放在本趟比较的第1个位置,所以选择排序的每趟比较只需要交换一次即可,只要找到本趟比较中最小的元素和本趟比较中第1位置的元素交换即可。
算法简介
选择排序是一种简单直观的排序算法。第一趟从n个元素的数据序列中选出关键字最小的元素并放在最前位置,下一趟从n-1个元素中选出最小的元素并放在最前位置。以此类推,经过n-1趟完成排序,第n个元素不用选择了,因为只剩下它一个最大的元素了。
算法思路
首先在未排序序列中找到最小元素,存放到排序序列的起始位置;
然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾;
以此类推,直到所有元素均排序完毕。
算法描述
1.在一个长度为 N 的无序数组中,第一次遍历 n-1 个数找到最小的和第一个数交换;
2.第二次从下一个数开始遍历 n-2 个数,找到最小的数和第二个数交换;
3.重复以上操作直到第 n-1 次遍历最小的数和第 n-1 个数交换,排序完成。
时间复杂度

  • 比较次数

选择排序的比较次数与序列的初始排序无关。 N 个元素的比较次数总是N (N - 1) / 2。
选择排序的比较次数为O(n^2)。

n−1+n−2+n−3+⋯+1=(n−1+1)(n−1)/2=n(n−1)/2

元素间比较,双重循环。

for (i = 0; i < 15; i++) {
		...
		for (k = j + 1; k < 15; k++) {
			...
		}
		...
}
  • 移动次数

而选择排序的移动次数与序列的初始排序有关。
当序列正序时,移动次数最少,为 0;
当序列反序时,移动次数最多,为3 (N - 1) 。
选择排序的移动次数为O(n)。

因为移动元素需要n-1次,而每次做出移动需要一个辅助空间,即t = a, a = b, b = t,这就是常数3的由来。

元素间移动,三次移动。

if (j != i) {
			tem = a[i];
			a[i] = a[j];
			a[j] = tem;
		}
  • 总结

简单排序的时间复杂度为 O(N^2)。
时间复杂度(最好、最坏、平均)均为 O(n^2)。

无论什么数据进去都是 O(n²) 的时间复杂度,所以用到选择排序的时,数据规模越小越好。

空间复杂度
选择排序需要占用 1 个临时空间,在交换数值时使用。

O(1)

稳定性
不稳定。
选择排序是给每个位置选择当前未排序中元素最小的,假如序列为

5 8 9 5 2 1 7

那么在第一遍选择时,第1个元素5会和2交换,原序列中两个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。

稳定:如果 a 原本在 b 前面,而 a=b,排序之后 a 仍然在 b 的前面。
不稳定:如果 a 原本在 b 的前面,而 a=b,排序之后 a 可能会出现在 b 的后面。

C++代码实现:

#include <iostream>
using namespace std;

//选择排序:从小到大

int main()
{
	int a[15] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
	int tem;
	int i, j, k = 0;
	// 直接输出排序前的数
	cout << "排序前:" << endl;
	for (i = 0; i < 15; i++)
	{
		cout << a[i] << " ";
	}
	cout << endl;
	for (i = 0; i < 15; i++) {
		j = i;
		for (k = j + 1; k < 15; k++) {
			if (a[k] < a[j]) {
				j = k;
			}
		}
		if (j != i) {
			tem = a[i];
			a[i] = a[j];
			a[j] = tem;
		}
		// 输出排序第i次
		cout << "排序第" << i + 1 << "次" << endl;
		for (int x = 0; x < 15; x++)
		{
			cout << a[x] << " ";
		}
		cout << endl;
	}
	// 顺序输出排序后的数
	cout << "排序后:" << endl;
	for (i = 0; i < 15; i++)
	{
		cout << a[i] << " ";
	}
	cout << endl;
	return 0;
}



控制台打印结果如下:

在这里插入图片描述
动图演示:引用菜鸟教程https://www.runoob.com/w3cnote/selection-sort.html
在这里插入图片描述
通过图片可以更好理解选择排序的过程;
在图片里:黄色是已经排好的序列,浅蓝色是未排序的序列;
在代码里我用未排序序列中的第一个数据表示I,红色表示J,绿色表示K。

冒泡排序(从小到大)

通俗说法
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端,就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
算法核心
比较两个相邻的元素,将值大的元素交换到后边
算法简介
冒泡排序是一种简单的排序算法,它也是一种稳定排序算法。
其实现原理是重复扫描待排序序列,并比较每一对相邻的元素,当该对元素顺序不正确时进行交换。
一直重复这个过程,直到没有任何两个相邻元素可以交换,就表明完成了排序。
算法思路
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
时间复杂度

  • 比较次数

最好:若初始元素是正序的,一趟扫描即可完成排序,比较次数n-1,时间复杂度为O(n)。
最坏:若初始元素是反序的,需要进行n-1趟排序。每趟排序要进行n-i次关键字的比较(1≤i≤n-1),时间复杂度为 O(n^2)。

n−1+n−2+n−3+⋯+1=(n−1+1)(n−1)/2=n(n−1)/2

  • 移动次数

最好:若初始元素是正序的,无需移动,时间复杂度为O(1)。
最坏:若初始元素是反序的,每次比较都必须移动记录三次来达到交换记录位置,时间复杂度为 O(n^2)。

3(n−1+n−2+n−3+⋯+1)=3(n−1+1)(n−1)/2=3n(n−1)/2

  • 总结

若初始元素是正序的,比较和移动次数均达到最小值。最好的时间复杂度为O(n)。
若初始元素是反序的,比较和移动次数均达到最大值。最坏的时间复杂度为 O(n^2)。

因此冒泡排序总的平均时间复杂度为O(n^2) 。

空间复杂度
空间复杂度就是在交换元素时那个临时变量所占的内存空间;
最优的空间复杂度就是开始元素顺序已经排好了,则空间复杂度为:0;
最差的空间复杂度就是开始元素逆序排序了,则空间复杂度为:O(n);

平均的空间复杂度为:O(1);

稳定性
冒泡排序就是把大的元素往后调(或者小的元素往前调)。
比较是相邻的两个元素比较,交换也发生在这两个元素之间,所以,如果两个元素相等,是不会再交换的。
如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换。
所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。

稳定:如果 a 原本在 b 前面,而 a=b,排序之后 a 仍然在 b 的前面。
不稳定:如果 a 原本在 b 的前面,而 a=b,排序之后 a 可能会出现在 b 的后面。

C++代码实现:

#include <iostream>
using namespace std;

void bubble_sort(int a[], int n)
{
	int i, j, y, tem = 0;
	bool change;
	//i控制未排序的数据,change标志位来判断是否已经排序好
	for (i = n - 1, change = true, y = 1; i > 1 && change; i--, y++)
	{
		change = false;
		for (j = 0; j < i; j++)
		{
			if (a[j] > a[j + 1])//两个相邻数据中,前面的数据大,则交换数据
			{
				tem = a[j];
				a[j] = a[j + 1];
				a[j + 1] = tem;
			}
			//if判断语句不成立时,也就是两个相邻数据中,前面的数据小。
			//此时不用交换数据,change的布尔值改为true。
			//如果元素已经排序好,那么循环一次就直接退出。
			change = true;
		}

		// 输出每次排序的结果
		cout << "排序第" << y << "次" << endl;
		for (int x = 0; x < 15; x++)
		{
			cout << a[x] << " ";
		}
		cout << endl;
	}
}

int main()
{
	int a[15] = { 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48 };
	// 直接输出排序前的数
	cout << "排序前:" << endl;
	for (int i = 0; i < 15; i++)
	{
		cout << a[i] << " ";
	}
	cout << endl;
	
	bubble_sort(a, 15);

	// 顺序输出排序后的数
	cout << "排序后:" << endl;
	for (int i = 0; i < 15; i++)
	{
		cout << a[i] << " ";
	}
	cout << endl;
	return 0;
}



上述代码是优化后的代码,标志位change变量的存在使最优时间复杂度为O(n)。
优化前的代码是没有标志位change变量的,此时最优时间复杂度为双重循环O(n^2)。

控制台打印结果如下:

在这里插入图片描述

动图演示
图片来源,菜鸟教程:https://www.runoob.com/python3/python-bubble-sort.html

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值