运用大O来给代码提速(冒泡排序)

news2025/2/3 20:49:59

本文内容借鉴一本我非常喜欢的书——《数据结构与算法图解》。学习之余,我决定把这本书精彩的部分摘录出来与大家分享。  

本章内容

写在前面

1.冒泡排序

2.冒泡排序实战

3.冒泡排序的实现

4.冒泡排序的效率

5.二次问题

6.线性解决

7.总结


写在前面

大 O记法能客观地衡量各种算法的时间复杂度,是比较算法的利器。我们也试过用它来对比二分查找和线性查找的步数差异,发现二分查找的步数为 O(log N),比线性查找的 O(N)快得多。

然而,写代码的时候并不总有这样明确的二选一,更多时候你可能就直接采用首先想到的那种算法了。不过有了大 O的话,你就可以与其他常用的算法比较,然后问自己:“我的算法跟它们相比,是快还是慢?”

如果你通过大 O 发现自己的算法比其他的要慢,你就应该退一步,好好想想怎样优化它,才能使它变成更快的那种大 O。

本章我们会写些代码来解决一个实际问题,并且会用大 O 来测量算法的性能,然后看看是否能对算法做些修改,使得性能提升。


1.冒泡排序

排序算法是计算机科学中被广泛研究的一个课题。历时多年,它发展出了数十种算法,这些算法都着眼于一个问题:

如何将一个无序的数字数组整理成升序?

冒泡排序是一种很基本的排序算法,步骤如下。

(1) 指向数组中两个相邻的元素(最开始是数组的头两个元素),比较它们的大小。

(2) 如果它们的顺序错了(即左边的值大于右边),就互换位置。 

如果顺序已经是正确的,那这一步就什么都不用做。

(3) 将两个指针右移一格。

重复第(1)步和第(2)步,直至指针到达数组末尾。 

(4)重复第(1)至(3)步,直至从头到尾都无须再做交换,这时数组就排好序了

这里被重复的第(1)至(3)步是一个轮回,也就是说,这个算法的主要步骤被“轮回”执行直到整个数组的顺序正确。


2.冒泡排序实战

下面来举一个完整的例子。假设要对 [4, 2, 7, 1, 3] 进行排序,产生一个升序的数组。

开始第 1次轮回。

数组一开始如下图所示。

第 1步:首先,比较 4和 2。如图可见它们的顺序是错的

第 2步:交换它们的位置。 

第 3步:比较 4和 7。

它们的顺序正确,所以不用做什么交换。 

第 4步:比较 7和 1。

第 5步:顺序错误,于是进行交换。

第 6步:比较 7和 3。

第 7步:顺序错误,于是进行交换。

 

因为我们一直把较大的元素换到右边,所以现在最右侧的 7正处于其正确位置上。这也正是此种算法名为冒泡排序的原因:每一次轮回过后,未排序的值中最大的那个都会“冒”到正确的位置上。 

因为刚才那次轮回做了不止一次的交换,所以得继续轮回。

下面来第 2次轮回。

此时 7已经在正确的位置上了。

第 8步:从比较 2和 4开始。

它们已经按顺序排好了,所以直接进行下一步。

第 9步:比较 4和 1。

第 10步:它们的顺序错误,于是交换。

第 11步:比较 4和 3。

第 12步:顺序错误,进行交换。

因为 7 已经在上一次轮回里排好了,所以无须比较 4 和 7。此外,4 移到了正确的位置,本次轮
回结束。 因为这次轮回也做了不止一次的交换,所以得继续轮回。

下面来第 3次轮回。

第 13步:比较 2和 1。

第 14步:顺序错误,进行交换。

第 15步:比较 2和 3。

顺序正确,不用交换。 

这时 3也“冒”到其正确位置了。因为这次轮回做了不止一次的交换,所以还要继续。

于是开始第 4次轮回。

第 16步:比较 1和 2。

顺序正确,不用交换。而且剩下的元素也都排好序了,轮回结束。

因为刚才的轮回没有任何交换,可知整个数组都已排好序。 


3.冒泡排序的实现

以下是用 C语言写的冒泡排序。

void BubbleSort(int arr[], int n) //n为数组的大小
{
	for (int i = 0; i < n-1; i++)//n个数字,把n-1个排好,剩下一个自然在正确的位置
	{
		for (int j = 0; j < n - 1 - i; j++)//每一个轮回之后,末端的数字已经到了正确的位置上,不需要比较
		{
			//比较相邻数字
			//升序为 >,降序为 <
			if (arr[j] > arr[j + 1])
			{
				//交换两个数字的位置
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

解释:
这里的实现用了两层循环。最外层的循环用i来控制,表示的是一个轮回。

i<n-1,其含义为有n个数,只需要走n-1个轮回。因为,我们把n-1个数排好之后,剩下的那个数自然在正确的位置上了。

内层循环由j来控制,进行的是一次轮回中,把一个数“冒”到正确的位置上。

此处的代码就是对上一小节冒泡排序实战的翻译。


4.冒泡排序的效率

冒泡排序的执行步骤可分为两种。

比较:比较两个数看哪个更大。

交换:交换两个数的位置以使它们按顺序排列。

先看看冒泡排序要进行多少次比较。

回顾之前那个 5个元素的数组,你会发现在第 1次轮回我们为 4对元素进行了 4次比较。

到了第 2次轮回,则只做了 3次比较。这是因为第 1次轮回已经确定了最后一个格子的元素,所以不用再比较最后两个元素了。

第 3次轮回,只比较 2次;第 4次,只比较 1次。

算起来就是:4 + 3 + 2 + 1 = 10 次比较。

推广到 N个元素,需要

(N - 1) + (N - 2) + (N - 3) + … + 1 次比较。

分析过比较之后,再来看看交换。

如果数组不只是随机打乱,而是完全反序,在这种最坏的情况下,每次比较过后都得进行一次交换。因此N个元素需要(N - 1) + (N - 2) + (N - 3) + … + 1 次交换。

那么总共花费的步数为:比较数*交换数,即

[ (N - 1) + (N - 2) + (N - 3) + … + 1 ] * 2

运用数学知识计算可得结果为 N^2/2-N+1/2,保留最高次为N^2

因此描述冒泡排序效率的大 O记法,是 O(N^2)

O(N^2)算法是比较低效的,随着数据量变多,其步数也剧增,如下图所示。

最后一点:O(N^2)也被叫作二次时间


5.二次问题

假设我们现在要写一个函数来检查数组中是否有重复值。

首先想到的可能是这样的循环嵌套:

bool CheakDUP(int arr[], int n)
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
            //有重复值返回true
			if (i != j && arr[i] == arr[j])
			{
				return true;
			}
		}
	}
    //没有返回false
	return false;
}

此函数运用了两层循环进行比较,其中 i 先指向一个数字不动,然后让 j 从开头走到最后并比较以它俩为索引处的值。如果相同就返回 true 。

我们考虑最坏情况,N个元素的数组中的每个元素都俩俩比较过,所花费的步数为 N^2。

此函数所用算法的时间复杂度为O(N^2)

虽然 CheakDup 是我们目前唯一想到的解决方法,但在确定采用之前,应意识到它的 O(N^2 )意味着低效。当遇到低效的算法时,我们都应该花些时间思考下有没有更快的做法。特别是当数据量巨大的时候,优化不足的应用甚至可能会突然挂掉。尽管这可能已经是最佳方案,但你还是要确认一下。


6.线性解决

以下是 CheakDup 的另一种实现,它没有嵌套循环。看看它是否会比之前的更加高效。

bool CheakDUP(int arr[], int n, int MAX)
{
	int existingnums[MAX] = { 0 };//MAX为数组中最大的值;将数组初始化为0
	for (int i = 0; i < n; i++)
	{
		if (existingnums[arr[i]] == 0)//如果该位置为0,则说明此处此索引所对应的数字并未重复
		{
			existingnums[arr[i]] = 1; //将0改为1做为标记
		}
		else  //若该索引处的值不为0,则说明之前已被标记。即意味着有重复值
		{
			return true;
		}
	}
	return false;
}

此次我们只用了一层循环便解决了问题。其原理为,以原数组arr中的值为新数组existingnums的索引,并通过该索引将该索引处的值改为1,也就是标记此处已被占用。

若下一次又通过新的索引找到了该位置,并且发现该位置已被标记(指0变为1)那么就意味着该索引为重复值。

同样的,最坏的情况就是无重复,因为你得跑完整个循环才能发现。可见 N 个元素就要 N 次比较。因为这里只有一个循环,数组有多少个元素,它就要迭代多少次。

因此其大 O记法是 O(N)

我们知道 O(N)远远快于 O(N^2 ),所以采用第二种算法能极大地提升 CheakDup的效率。如果这个程序处理的数据量很大,那么性能差别是很明显的(其实第二种算法有一个缺点,不过我们在最后一章才会讲到)。


7.总结

毫无疑问,熟悉大 O记法能使我们发现低效的代码,有助于我们挑选出更快的算法。然而,偶尔也会有两种算法的大 O相同,但实际上二者快慢不一的情况。下一章我们就来学习当大 O记法太过粗略的时候,如何识别两种算法的效率高低。

本章完! 

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

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

相关文章

Diffusion Model合集 part2

扩散模型原理介绍2五&#xff0c;逆扩散过程(Reverse Process)六&#xff0c;扩散过程中的后验的条件概率q(xt−1∣xt,x0)q(x_{t-1}|x_{t},x_{0})q(xt−1​∣xt​,x0​)七&#xff0c;目标数据分布的似然函数八&#xff0c;Diffusion Probabilistic Model的算法代码五&#xff…

“专利费用减缓”怎么申请?

在专利申请时&#xff0c;很多申请人对“专利费用减缓”的概念并不了解&#xff0c;或者不太清楚。甚至有很多申请人一听到专利申请可以请求费用减缓&#xff0c;就以为申请专利是不要钱的。当然&#xff0c;这样的理解就存在了很大的偏差了&#xff0c;所以&#xff0c;我们今…

Java Arrays类

JavaArrays类\huge{Java \space Arrays类}Java Arrays类 概述 Arrays类Arrays类Arrays类&#xff1a;本质就是一个工具类&#xff0c;用于操作数组元素的。 常用API ①. toString()&#xff08;重写过的toString方法&#xff09; toString()toString()toString()&#xff1a…

人才招聘网 招聘系统源码找工作源代码 各行业的招聘网站门户源码

运行环境&#xff1a;VS2015SqlServer2008R2.NET4.0 系统介绍&#xff1a; 适用于各地方或者各行业的招聘网站门户&#xff0c;相当强大的人才招聘网站&#xff0c;可登陆注册、支持第三方登陆&#xff0c;强大额后台管理&#xff0c;功能全面&#xff0c;界面美观 职位和简历…

如何用yolov5 训练自己的数据

文章目录 说明数据准备划分数据集转换数据label 格式训练前准备修改图片路径修改训练配置文件 voc.yaml修改 yolo配置文件开始训练没有GPU 或显存不够的说明 利用yolov5 训练自己的数据集通常需要利用自己标注的数据进行训练 接下来记录下如何训练自己的数据 数据准备 我这用的…

【数据结构】- 面试题

面试题1. 删除链表中的节点2. 反转一个链表&#xff08;非递归解法&#xff09;3. 判断一个链表是否有环&#xff08;快慢指针&#xff09;问题&#xff1a;[快慢指针为什么一定会相遇](https://blog.csdn.net/Leslie5205912/article/details/89386769)4. 获取单链表的节点个数…

【轻量级开源ROS 的机器人设备(5)】--(3)拟议的框架——µROS节点

前文链接&#xff1a;(2条消息) 【轻量级开源ROS 的机器人设备&#xff08;5&#xff09;】--&#xff08;2&#xff09;拟议的框架——ROS节点 五、静态栈分析 在处理运行多个资源的严格受限平台时线程&#xff0c;重要的是将堆栈使用保持在最低限度。这甚至 在利用具有同质堆…

技嘉电脑怎么开启vt模式?

电脑开启vt模式后&#xff0c;可以提高主板的运行速率&#xff0c;提高性能。那就有一些使用技嘉电脑的用户问技嘉主板怎么开启vt模式&#xff1f;下面小编就来教教大家技嘉电脑开启vt模式的方法。 Intel芯片组的技嘉主板 1、一般情况下&#xff0c;也就是在电脑开机的时候&…

计算机网络、操作系统刷题笔记15

计算机网络、操作系统刷题笔记15 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle&#xff0c;尤其sq…

差分详细讲解(C++)

每日一句:平凡的我在人多的地方曾极力小心翼翼&#xff0c; 但不知从何时起 &#xff0c;我不太在意别人的目光了。比起被人觉得是个怪人&#xff0c;我现在更害怕浪费时间。 差分一、一维差分二、二维差分一、一维差分 差分就是前缀和的逆运算,如果你不懂什么是前缀和,看这里…

移动技术在仓库运营管理中的作用

作者&#xff1a;Mike Kay&#xff0c;Mendix公司合作伙伴The Config Team渠道客户经理 市面上出现越来越多的仓库管理系统&#xff08;WMS&#xff09;以满足企业更好地管理供应链的需求。想要充分挖掘WMS的优点&#xff0c;一般可以通过移动解决方案来将关键的供应链运作进…

浪潮信息工程师:带你了解设备透传虚拟机的快速启动技术优化方案

编者按&#xff1a;将物理设备通过 vfio 透传给虚拟机是虚拟化常用的技术&#xff0c;但当为虚拟机分配比较大的内存时&#xff0c;虚拟机的启动时间会明显变慢&#xff0c;可能由十几秒延长至数分钟&#xff0c;严重影响用户使用体验。本文整理自龙蜥大讲堂 51 期&#xff0c;…

小林coding阅读笔记:计算机网络基础篇-TCP\IP模型

前言 参考/导流&#xff1a; 小林coding - 2.1 TCP/IP 网络模型有哪几层&#xff1f;学习意义 学习分层设计思想构建网络层次以及各层协议作用知识体系为网络编程奠定理论基础&#xff0c;对于RPC框架or分布式系统通信都是极为重要的一节&#xff0c;是提升整个系统效率的关键…

ubunt配置samba服务器,匿名访问

1. 环境 ubuntu14.04 2. 安装samba服务器 sudo apt-get install samba 3. 配置samba文件 vim /etc/samba/smb.conf 在最后添加如下内容 [muchx]comment Shared Folder with username and passwordpath /home/muchx/sharepublic yeswritable yesvalid users muchxcre…

二、基于kubeadm安装kubernetes1.25集群第一篇

1、概述 Kubeadm 是一个提供了 kubeadm init 和 kubeadm join 的工具&#xff0c; 作为创建 Kubernetes 集群的 “快捷途径” 的最佳实践。 kubeadm 通过执行必要的操作来启动和运行最小可用集群。 按照设计&#xff0c;它只关注启动引导&#xff0c;而非配置机器。同样的&…

虹科分享 | 硬件加密U盘 | 管理密码安全的四大工具

随着网络攻击变得越来越突出&#xff0c;密码安全变得越来越重要。为了为每个帐户创建安全密码&#xff0c;许多人正在转向密码管理工具来帮助防止敏感数据的泄露。 考虑到这一点&#xff0c;以下是一些顶级密码管理工具。 领先的密码管理工具 Keeper密码管理器和数字保险库 …

kali linux手动编译网卡驱动(以mt7612u为例)

〇、linux常用命令 《Linux入门与基础》课程教案_小王小王指定辉煌的博客-CSDN博客_linux入门与基础教案 linux常用命令笔记&#xff08;二&#xff09;_小王小王指定辉煌的博客-CSDN博客 一、下载源码 查看系统版本号 uname -r uname -a cat /proc/version 或/lib/module…

手撕红黑树的构建与验证

上篇文章我们介绍了AVL树的构建与适用场景&#xff0c;我们知道了AVL树虽然查找效率很高&#xff0c;但是不适合频繁插入或删除的场景。为了解决这个问题又诞生了新的数据结构&#xff1a;红黑树 那么本篇文章就着重介绍红黑树的性质与如何构建。 1.红黑树的性质 1.结点颜色非黑…

邓俊辉 《数据结构》笔记1 绪论

邓俊辉 《数据结构》笔记1 绪论 CSDN转图床总是崩&#xff0c;如果全写完再上传一次要调好多&#xff0c;感觉很麻烦&#xff0c;所以写一点更新一点&#xff0c;会持续更新 提前发出来还有个好处就是push自己更新不会咕咕咕&#xff0c;哈哈 参考资料 MOOC 数据结构上MOOC 数…

【计算机考研408】磁盘的初始化过程

该图是磁盘物理图 关于磁盘存储器&#xff0c;[柱面号盘面号扇区号]⇔外存块号 注&#xff1a;柱面是相对位置相同的磁道所构成的面 磁盘初始化 低级初始化&#xff08;也称物理格式化&#xff09; &#xff08;1&#xff09;分扇区 &#xff08;2&#xff09;用特殊数据结构…