[排序篇] 冒泡排序

news2024/12/22 9:43:23

目录

一、概念

二、冒泡排序

2.1 冒泡降序(从大到小排序)

2.2 冒泡升序(从小到大排序)

三、冒泡排序应用 

总结 


一、概念

冒泡排序核心思想:每次比较两个相邻的元素,如果它们不符合排序规则(升序或降序)则把它们交换过来。

二、冒泡排序

2.1 冒泡降序(从大到小排序)

冒泡降序:每次比较相邻的两个数,如果后面的数比前面的数大,则交换这两个数的位置。 

假设将 12 18 76 35 99 这 5 个数进行从大到小的排序(即,越小的越靠后)

如上图所示,从左往右逐列看,5 个数总共需要遍历 4 次(即 n - 1 次);而每列从上往下逐行看,每遍历一次总共需要排序 n - i 次(i 代表遍历的次数)。

1. 首先看第一列: 

1.1 第一行:比较第 1 位和第 2 位的大小,发现 12 比 18 要小,因为是降序,所以需要交换这两个数的位置。交换之后这 5 个数的顺序是 18 12 76 35 99;

1.2 第二行:比较第 2 位和第 3 位的大小,发现 12 比 76 要小,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 18 76 12 35 99;

1.3 第三行:比较第 3 位和第 4 位的大小,发现 12 比 35 要小,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 18 76 35 12 99;

1.4 第三行:比较第 4 位和第 5 位的大小,发现 12 比 99 要小,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 18 76 35 99 12; 

遍历第一次并经过 4 次排序后,5 个数中最小的一个 12 已经归位到队列的最后一位了(即第 5 位)。

2. 再看第二列: 

2.1 第一行:比较第 1 位和第 2 位的大小,发现 18 比 76 要小,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 76 18 35 99 12

2.2 第二行:比较第 2 位和第 3 位的大小,发现 18 比 35 要小,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 76 35 18 99 12

2.3 第三行:比较第 3 位和第 4 位的大小,发现 18 比 99 要小,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 76 35 99 18 12

遍历第二次并经过 3 次排序后,5 个数中倒数第二小的一个 18 已经归位到队列的倒数第二位了(即第 4 位)。 

3. 再看第三列: 

2.1 第一行:比较第 1 位和第 2 位的大小,发现 76 比 35 要大,则不需要交换这两个数的位置。并且这 5 个数的顺序仍然是 76 35 99 18 12

2.2 第二行:比较第 2 位和第 3 位的大小,发现 35 比 99 要小,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 76 99 35 18 12

遍历第三次并经过 2 次排序后,5 个数中倒数第三小的一个 35 已经归位到队列的倒数第三位了(即第 3 位)。  

3. 最后看第四列: 

2.1 第一行:比较第 1 位和第 2 位的大小,发现 76 比 99 要小,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 99 76 35 18 12

遍历第四次并经过 1 次排序后,5 个数中倒数第四小的一个 76 已经归位到队列的倒数第四位了(即第 2 位)。 

最后总结一下:如果有 n 个数进行排序,只需将 n-1 个数归位,也就是说要进行 n-1 次操作。而 “每一次” 都需要从第 1 位开始进行相邻两个数的比较,将较小的一个数放在后面,比较完毕后向后移一位继续比较下面两个相邻数的大小,重复此步骤,直到最后一个尚未归位的数,已经归位的数则无需再进行比较。

冒泡降序例程1(推荐): 

#include <stdio.h>

void main()
{
	int arr[5];
	int n;
	int tmp;

	scanf("%d", &n); //输入一个数n,表示接下来有n个数
	for (int i = 0; i < n; i++) //循环读入n个数到数组arr中
		scanf("%d", &arr[i]);
		
	//冒泡降序的核心代码
	for (int i = 1; i <= n - 1; i++) { //n个数排序,只需进行 n-1 次(i从1开始,因此i需要包含 n-1)
		for (int j = 0; j < n - i; j++) { //i从1开始,则j从第1位(数组0下标)开始与后面一个数比较,直到最后一个尚未归位的数(已归位的数无需再比较,因此只需排序 n-i 次)
			if (arr[j] < arr[j+1]) { //相邻两个数比较大小并交换
				tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
	}
	
	//输出结果
	for (int i = 0; i < n; i++)
		printf("%d ", arr[i]);
	printf("\n");
}

冒泡降序例程2: 

#include <stdio.h>

void main()
{
	int arr[5];
	int n;
	int tmp;

	scanf("%d", &n); //输入一个数n,表示接下来有n个数
	for (int i = 0; i < n; i++) //循环读入n个数到数组arr中
		scanf("%d", &arr[i]);
		
	//冒泡降序的核心代码
	for (int i = 0; i < n - 1; i++) { //n个数排序,只需进行 n-1 次(i从0开始,因此i不能包含 n-1)
		for (int j = 1; j < n - i; j++) { //i从0开始,则j从第2位(即数组1下标)开始与前面一个数比较,直到最后一个尚未归位的数(已归位的数无需再比较,因此只需排序 n-i 次)
			if (arr[j-1] < arr[j]) { //相邻两个数比较大小并交换
				tmp = arr[j-1];
				arr[j-1] = arr[j];
				arr[j] = tmp;
			}
		}
	}
	
	//输出结果
	for (int i = 0; i < n; i++)
		printf("%d ", arr[i]);
	printf("\n");
}

冒泡降序例程3:  

#include <stdio.h>

void main()
{
	int arr[5];
	int n;
	int tmp;

	scanf("%d", &n); //输入一个数n,表示接下来有n个数
	for (int i = 0; i < n; i++) //循环读入n个数到数组arr中
		scanf("%d", &arr[i]);

	//冒泡降序的核心代码
	for (int i = 0; i < n - 1; i++) { //n个数排序,只需进行 n-1 次 (i从0开始,因此i不能包含 n-1)
		for (int j = 0; j < n - i - 1; j++) { //从第1位(数组0下标)开始与后面一个数比较,直到最后一个尚未归位的数(已归位的数无需再比较,然而i和j都是从0开始,因此只需排序 n-i-1 次)
			if (arr[j] < arr[j+1]) { //相邻两个数比较大小并交换
				tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
	}
	
	//输出结果
	for (int i = 0; i < n; i++)
		printf("%d ", arr[i]);
	printf("\n");
}

2.2 冒泡升序(从小到大排序)

冒泡升序:每次比较相邻的两个数,如果后面的数比前面的数小,则交换这两个数的位置。 

假设将 99 35 18 76 12 这 5 个数进行从小到大的排序(即,越大的越靠后)

如上图所示,从左往右逐列看,5 个数总共需要遍历 4 次(即 n - 1 次);而每列从上往下逐行看,每遍历一次总共需要排序 n - i 次(i 代表遍历的次数)。

1. 首先看第一列: 

1.1 第一行:比较第 1 位和第 2 位的大小,发现 99 比 35 要大,因为是升序,所以需要交换这两个数的位置。交换之后这 5 个数的顺序是 35 99 18 76 12;

1.2 第二行:比较第 2 位和第 3 位的大小,发现 99 比 18 要大,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 35 18 99 76 12;

1.3 第三行:比较第 3 位和第 4 位的大小,发现 99 比 76 要大,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 35 18 76 99 12;

1.4 第三行:比较第 4 位和第 5 位的大小,发现 99 比 12 要大,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 35 18 76 12 99

遍历第一次并经过 4 次排序后,5 个数中最大的一个 99 已经归位到队列的最后一位了(即第 5 位)。

2. 再看第二列: 

2.1 第一行:比较第 1 位和第 2 位的大小,发现 35 比 18 要大,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 18 35 76 12 99

2.2 第二行:比较第 2 位和第 3 位的大小,发现 35 比 76 要小,则不需要交换这两个数的位置。并且这 5 个数的顺序仍然是 18 35 76 12 99

2.3 第三行:比较第 3 位和第 4 位的大小,发现 76 比 12 要大,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 18 35 12 76 99

遍历第二次并经过 3 次排序后,5 个数中第二大的一个 76 已经归位到队列的倒数第二位了(即第 4 位)。 

3. 再看第三列: 

2.1 第一行:比较第 1 位和第 2 位的大小,发现 18 比 35 要小,则不需要交换这两个数的位置。并且这 5 个数的顺序仍然是 18 35 12 76 99

2.2 第二行:比较第 2 位和第 3 位的大小,发现 35 比 12 要大,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 18 12 35 76 99

遍历第三次并经过 2 次排序后,5 个数中第三大的一个 35 已经归位到队列的倒数第三位了(即第 3 位)。  

3. 最后看第四列: 

2.1 第一行:比较第 1 位和第 2 位的大小,发现 18 比 12 要大,因此需要交换这两个数的位置。交换之后这 5 个数的顺序是 12 18 35 76 99

遍历第四次并经过 1 次排序后,5 个数中第四大的一个 18 已经归位到队列的倒数第四位了(即第 2 位)。 

最后总结一下:如果有 n 个数进行排序,只需将 n-1 个数归位,也就是说要进行 n-1 次操作。而 “每一次” 都需要从第 1 位开始进行相邻两个数的比较,将较大的一个数放在后面,比较完毕后向后移一位继续比较下面两个相邻数的大小,重复此步骤,直到最后一个尚未归位的数,已经归位的数则无需再进行比较。

冒泡升序例程1(推荐): 

#include <stdio.h>

void main()
{
	int arr[5];
	int n;
	int tmp;

	scanf("%d", &n); //输入一个数n,表示接下来有n个数
	for (int i = 0; i < n; i++) //循环读入n个数到数组arr中
		scanf("%d", &arr[i]);

	//冒泡降序的核心代码
	for (int i = 1; i <= n - 1; i++)) { //n个数排序,只需进行 n-1 次(i从1开始,因此i需要包含 n-1)
		for (int j = 0; j < n - i; j++) { //i从1开始,则j从第1位(数组0下标)开始与后面一个数比较,直到最后一个尚未归位的数(已归位的数无需再比较,因此只需排序 n-i 次)
			if (arr[j] > arr[j+1]) { //相邻两个数比较大小并交换
				tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
	}
	
	//输出结果
	for (int i = 0; i < n; i++)
		printf("%d ", arr[i]);
	printf("\n");
}

冒泡升序例程2:  

#include <stdio.h>

void main()
{
	int arr[5];
	int n;
	int tmp;

	scanf("%d", &n); //输入一个数n,表示接下来有n个数
	for (int i = 0; i < n; i++) //循环读入n个数到数组arr中
		scanf("%d", &arr[i]);
		
	//冒泡降序的核心代码
	for (int i = 0; i < n - 1; i++) { //n个数排序,只需进行 n-1 次(i从0开始,因此i不能包含 n-1)
		for (int j = 1; j < n - i; j++) { //i从0开始,则j从第2位(即数组1下标)开始与前面一个数比较,直到最后一个尚未归位的数(已归位的数无需再比较,因此只需排序 n-i 次)
			if (arr[j-1] > arr[j]) { //相邻两个数比较大小并交换
				tmp = arr[j-1];
				arr[j-1] = arr[j];
				arr[j] = tmp;
			}
		}
	}
	
	//输出结果
	for (int i = 0; i < n; i++)
		printf("%d ", arr[i]);
	printf("\n");
}

冒泡升序例程3: 

#include <stdio.h>

void main()
{
	int arr[5];
	int n;
	int tmp;

	scanf("%d", &n); //输入一个数n,表示接下来有n个数
	for (int i = 0; i < n; i++) //循环读入n个数到数组arr中
		scanf("%d", &arr[i]);

	//冒泡降序的核心代码
	for (int i = 0; i < n - 1; i++) { //n个数排序,只需进行 n-1 次 (i从0开始,因此i不能包含 n-1)
		for (int j = 0; j < n - i - 1; j++) { //从第1位(数组0下标)开始与后面一个数比较,直到最后一个尚未归位的数(已归位的数无需再比较,然而i和j都是从0开始,因此只需排序 n-i-1 次)
			if (arr[j] > arr[j+1]) { //相邻两个数比较大小并交换
				tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
	}
	
	//输出结果
	for (int i = 0; i < n; i++)
		printf("%d ", arr[i]);
	printf("\n");
}

三、冒泡排序应用 

假设一个班有 5 个学生,需要将这 5 个学生期末考试的分数,从高到低排序,并且输出对应的学生姓名和性别。大家可以思考一下,该如何实现?

根据 2.1 冒泡降序原理,在此,我们只需要声明一个结构体,其成员包含学生的姓名、性别和分数(假设满分为 100,并分数只有整数)。下面是实际的例子。

#include <stdio.h>

struct student {
	char name[24];
	char sex[8];
	int score;
};

void main()
{
	struct student stu[5];
	struct student tmp;
	int n;

	scanf("%d", &n); //输入班级总人数
	for (int i = 0; i < n; i++) //循环读入学生总数到数组stu中
		scanf("%s %s %d", stu[i].name, stu[i].sex, &stu[i].score);

	//开始对学生进行分数排序
	for (int i = 1; i <= n - 1; i++) {
		for (int j = 0; j < n - i; j++) {
			if (stu[j].score < stu[j+1].score) { //比较相邻的两个数,分数高的排前面
				tmp = stu[j];
				stu[j] = stu[j+1];
				stu[j+1] = tmp;
			}
		}
	}

	//输出结果
	for (int i = 0; i < n; i++)
		printf("%s %s %d", stu[i].name, stu[i].sex, &stu[i].score);
	printf("\n");
}

可以输入以下数据进行验证:

5

李文 男 80
韩飞 男 50
晓晓 女 86
胡峰 男 78
陈肖 女 66

运行结果是: 

晓晓 女 86

李文 男 80
胡峰 男 78
陈肖 女 66
韩飞 男 50

总结 

1. 冒泡降序:每次比较相邻的两个数,如果后面的数比前面数大,则交换这两个数的位置;

2. 冒泡升序:每次比较相邻的两个数,如果后面的数比前面数小,则交换这两个数的位置;

3. 从例程代码来看,可知冒泡排序有很多种方法,但是万变不离其宗,都是围绕 “如果有 n 个数进行排序,则需遍历 n-1 次,而 “每一次” 需要排序 n-i 次,并且都是从第 1 位开始进行相邻两个数的比较,将较小或较大的一个数放在后面,如此重复,直到最后一个尚未归位的数” 展开。

4. “冒泡降序” 与 “冒泡升序” 例程代码的唯一差异是:相邻的两个数较小或较大的放在后面。例如,if (arr[j] < arr[j+1]) 或 if (arr[j] > arr[j+1]);

5. 冒泡排序的核心部分是双重嵌套循环。不难看出冒泡排序的时间复杂度是 O(N^{2})。这是一个非常高的时间复杂度。正如 Donald E. Knuth 所说:“冒泡排序除了它迷人的名字和导致了某些有趣的理论问题这一事实之外,似乎没有什么值得推荐的。”

那还有没有更好的排序算法呢?有,请看下章节——快速排序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1303519.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

.NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)

WebAppDbTest 项目准备 项目准备1、.net cli 创建项目2、nuget 包引用和项目结构2.1、项目添加相关 nuget 包2.2、WebAppDbTest 项目结构 3、项目代码说明3.1、CSharp/C# 类文件说明3.2、json 配置文件说明 4、项目运行预览 数据库 .db 文件准备1、创建 SQLite 数据库1.1、在 W…

赛宁网安多领域亮相第三届网络空间内生安全发展大会

2023年12月8日&#xff0c;第三届网络空间内生安全发展大会在宁开幕。两院院士、杰出专家学者和知名企业家相聚南京&#xff0c;围绕数字经济新生态、网络安全新范式进行广泛研讨&#xff0c;为筑牢数字安全底座贡献智慧和力量。 大会围绕“一会、一赛、一展”举办了丰富多彩的…

【华为数据之道学习笔记】3-11元数据管理

1. 产生元数据 &#xff08;1&#xff09;明确业务元数据、技术元数据和操作元数据之间的关系&#xff0c;定义华为公司元数据模型。 &#xff08;2&#xff09;针对找数据及获取数据难的痛点&#xff0c;明确业务元数据、技术元数据、操作元数据的设计原则。 1&#xff09;业务…

虚拟化逻辑架构:KVM虚拟机通过OVS端口组实现网络连接

目录 一、实验 1.CentOS 7 安装 OpenVSwitch(构建RPM安装包&#xff09; 2.KVM虚拟机通过OVS端口组实现网络连接 二、问题 1.安装openvswitch-2.5.10报错 2.virt-install未找到命令 3.如何删除自定义网络 一、实验 1.CentOS 7 安装 OpenVSwitch(构建RPM安装包&#xff…

蓝桥杯周赛 第 1 场 强者挑战赛 6. 小球碰撞【算法赛】(思维题/最长上升子序列LIS)

题目 https://www.lanqiao.cn/problems/9494/learning/?contest_id153 思路来源 Aging代码 题解 二分时间t&#xff0c;第i个小球对应一个起点pi、终点pit*vi的区间&#xff0c;问题转化为&#xff0c; 选最多的区间&#xff0c;使得不存在区间包含&#xff08;即li<l…

不同路径dp问题

1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 从上往下填写每一行 每一行从左往右 5.返回值 dp[m][n] ------------------------------------------------------------------------------------------------------------------------------ 1.状态表示 2.状态转移方程 3…

每日一题,头歌平台c语言题目

任务描述 题目描述:输入一个字符串&#xff0c;输出反序后的字符串。 相关知识&#xff08;略&#xff09; 编程要求 请仔细阅读右侧代码&#xff0c;结合相关知识&#xff0c;在Begin-End区域内进行代码补充。 输入 一行字符 输出 逆序后的字符串 测试说明 样例输入&…

DevOps:自动化、持续交付和持续集成实践

DevOps&#xff1a;自动化、持续交付和持续集成实践 一、DevOps 简介1.1 DevOps 模式定义1.2 DevOps 的工作原理1.3 DevOps 的优势1.4 DevOps 的意义1.5 DevOps 最佳实践 二、持续集成简介2.1 持续集成的意义2.2 持续集成的工作原理2.3 持续集成的优势 三、持续交付简介3.1 持续…

解决夜神模拟器与Android studio自动断开的问题

原因&#xff1a;夜神模拟器的adb版本和Android sdk的adb版本不一致 解决办法&#xff1a; 1.找到android的sdk &#xff08;1&#xff09;File--->Project Structure (2)SDK Location:记下sdk的位置 2.找到sdk中的adb文件 SDK-->platform-tools-->adb.exe 3.复制…

单片机(STM32,GD32,NXP等)中BootLoader的严谨实现详解

Bootloader(引导加载程序)的主要任务是引导加载并运行应用程序&#xff0c;我们的软件升级逻辑也一般在BootLoader中实现。本文将详细介绍BootLoader在单片机中的实现&#xff0c;包括STM32、GD32、NXP Kinetis等等的所有单片机&#xff0c;因为无论是什么样的芯片&#xff0c;…

大模型应用_ChatGPT-Next-Web

1 用后感 这个工具&#xff0c;我也是用了好长时间&#xff0c;就是感觉如果不点亮一颗星&#xff0c;自己就不是人了的那种。 一开始在国内用 ChatGPT 非常麻烦&#xff0c;就买了一个套壳的服务&#xff0c;他使用的界面就是 ChatGPT-Next-Web&#xff0c;我和朋友们都觉得这…

深度学习 Day13——P2彩色图片分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 文章目录 前言1 我的环境2 代码实现与执行结果2.1 前期准备2.1.1 引入库2.1.2 设置GPU&#xff08;如果设备上支持GPU就使用GPU,否则使用C…

基于JavaWeb+BS架构+SpringBoot+Vue图书个性化推荐系统的设计和实现

基于JavaWebSpringBootVue图书个性化推荐系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图 源码获取入口 Lun文目录 目 录 摘 要 I 1 绪论 1 1.1研究背景 1 1.2研究现状 1 1.3研究内容 2 2 系统关键技术 3 2.1 Spring Boot框架 3 2.2 JAVA技术 3 2.3 MY…

实现安装“自由化”!在Windows 11中如何绕过“您尝试安装的应用程序未通过微软验证”

这篇文章描述了如果你不能安装应用程序,而是当你在Windows 11中看到消息“您尝试安装的应用程序未通过微软验证”时该怎么办。完成这些步骤将取消你安装的应用程序必须经过Microsoft验证的要求。 使用设置应用程序 “设置”应用程序提供了绕过此警告消息的最简单方法,以便你…

051:vue项目webpack打包后查看各个文件大小

第050个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

centos7部署docker

文章目录 &#xff08;1&#xff09;安装前准备&#xff08;2&#xff09;卸载旧版Docker&#xff08;3&#xff09;安装docker&#xff08;4&#xff09;配置镜像加速 &#xff08;1&#xff09;安装前准备 在开始安装之前&#xff0c;首先需要检查内核版本。使用 uname -r 命…

RPG:一种面向Rust库的模糊测试目标自动生成技术(ICSE‘24)

- 这是我们在ICSE’24的论文RPG: Rust Library Fuzzing with Pool-based Fuzz TargetGeneration and Generic Support [1] 的科普版本。- 插播一条广告&#xff1a;西安电子科技大学广州研究院 ICTT(GZ) 实验室长期接收硕士、博士、博士后、教师岗位申请&#xff0c;欢迎勤奋、…

gitlab动态流水线

文章目录 1. 说明2. 官方样例2.1 在作业中生成配置文件&#xff0c;保存为产物2.2 将触发器作业配置为在生成配置文件的作业之后运行。 3. 实战应用3.1 背景介绍3.2 项目介绍3.3 公共项目配置3.4 测试项目配置3.5 测试 4. 总结 1. 说明 顾名思义&#xff0c;动态流水线就是一种…

青少年CTF-Misc(持续更新中)

FLAG&#xff1a;当觉得自己很菜的时候&#xff0c;就静下心来学习 专研方向:Web安全&#xff0c;CTF 每日emo&#xff1a;听一千遍反方向的钟&#xff0c;我们能回到过去吗&#xff1f; 1.StegoTXT&#xff1a; 解压缩文件。发现字母中存在覆盖。使用0宽隐写在线解密得到flag…

音乐制作工具 Ableton Live 12中文最新 for Mac

Ableton Live 12 Mac具有直观的界面和强大的功能&#xff0c;使得音乐制作变得更加简单和高效。它支持实时录制、编辑和混音&#xff0c;用户可以在创作过程中随时进行修改和调整。此外&#xff0c;该软件还提供了各种音频效果、虚拟乐器和采样器&#xff0c;使用户可以创建出更…