啊哈 算法读书笔记 第 1 章 一大波数正在靠近——排序

news2025/7/31 3:55:47

目录

排序算法: 

时间复杂度: 

排序算法和冒泡排序之间的过渡:

冒泡排序

冒泡排序和快速排序之间的过渡:

快速排序


排序算法: 

        首先出场的是我们的主人公小哼,上面这个可爱的娃就是啦。期末考试完了老师要将同
学们的分数按照从高到低排序。小哼的班上只有 5 个同学,这 5 个同学分别考了 5 分、 3 分、
5 分、 2 分和 8 分,哎考得真是惨不忍睹(满分是 10 分)。接下来将分数进行从大到小排序,
排序后是 8 5 5 3 2 。你有没有什么好方法编写一段程序,让计算机随机读入 5 个数然后将这
5 个数从大到小输出?
#include <stdio.h> 
int main() 
{ 
 int a[11],i,j,t; 
 for(i=0;i<=10;i++) 
 a[i]=0; //初始化为0 
 
 for(i=1;i<=5;i++) //循环读入5个数
 {
scanf("%d",&t); //把每一个数读到变量t中
 a[t]++; //进行计数
 } 
 for(i=0;i<=10;i++) //依次判断a[0]~a[10] 
 for(j=1;j<=a[i];j++) //出现了几次就打印几次
 printf("%d ",i); 
 getchar();getchar(); 
 //这里的getchar();用来暂停程序,以便查看程序输出的内容
 //也可以用system("pause");等来代替
 return 0; 
}

#include <stdio.h> 
int main() 
{ 
 int book[1001],i,j,t,n; 
 for(i=0;i<=1000;i++) 
 book[i]=0; 
 scanf("%d",&n);//输入一个数n,表示接下来有n个数
 for(i=1;i<=n;i++)//循环读入n个数,并进行桶排序
 { 
 scanf("%d",&t); //把每一个数读到变量t中
 book[t]++; //进行计数,对编号为t的桶放一个小旗子
 } 
 for(i=1000;i>=0;i--) //依次判断编号1000~0的桶
 for(j=1;j<=book[i];j++) //出现了几次就将桶的编号打印几次
 printf("%d ",i); 
 getchar();getchar(); 
 return 0; 
}

时间复杂度: 

代码中第 6 行的循环一共循环了 m 次( m 为桶的个数),
9 行的代码循环了 n 次( n 为待排序数的个数),第 14 行和第 15 行一共循环了 m + n 次。
所以整个排序算法一共执行了 m + n + m + n 次。我们用大写字母 O 来表示时间复杂度,因此该
算法的时间复杂度是 O ( m + n + m + n ) O (2*( m + n )) 。我们在说时间复杂度的时候可以忽略较小
的常数,最终桶排序的时间复杂度为 O ( m + n ) 。还有一点,在表示时间复杂度的时候, n m
通常用大写字母即 O ( M + N )

排序算法和冒泡排序之间的过渡:

现在分别有 5 个人的名字和分数: huhu 5 分、 haha 3 分、 xixi 5 分、 hengheng 2 分和 gaoshou
8 分。请按照分数从高到低,输出他们的名字。即应该输出 gaoshou huhu xixi haha hengheng 。 发现问题了没有?如果使用我们刚才简化版的桶排序算法仅仅是把分数进行了排序。最终输 出的也仅仅是分数,但没有对人本身进行排序。也就是说,我们现在并不知道排序后的分数 原本对应着哪一个人!这该怎么办呢?不要着急,请看下节——冒泡排序。
简化版的桶排序不仅仅有所遗留的问题,更要命的是:它非常浪费空间!例如需 要排序数的范围是 0~2100000000 之间,那你则需要申请 2100000001 个变量,也就是说要写 成 int a[2100000001] 。因为我们需要用 2100000001 个“桶”来存储 0~2100000000 之间每一个数出现的次数。即便只给你 5 个数进行排序(例如这 5 个数是 1 1912345678 2100000000, 18000000 和 912345678 ),你也仍然需要 2100000001 个“桶”,这真是太浪费空间了!还有,如果现在需要排序的不再是整数而是一些小数,比如将 5.56789 2.12 1.1 3.123 4.1234 这五个数进行从小到大排序又该怎么办呢?

冒泡排序

冒泡排序的基本思想是:每次比较两个相邻的元素,如果它们的顺序错误就把它们交换 过来

#include <stdio.h> 
int main() 
{ 
 int a[100],i,j,t,n; 
 scanf("%d",&n); //输入一个数n,表示接下来有n个数
 for(i=1;i<=n;i++) //循环读入n个数到数组a中
 scanf("%d",&a[i]); 
//混混藏书阁:http://book-life.blog.163.com
//啊哈!算法

 //冒泡排序的核心部分
 for(i=1;i<=n-1;i++) //n个数排序,只用进行n-1趟
 { 
 for(j=1;j<=n-i;j++) //从第1位开始比较直到最后一个尚未归位的数,想一想为什
么到n-i就可以了。
 { 
 if(a[j]<a[j+1]) //比较大小并交换
 { t=a[j]; a[j]=a[j+1]; a[j+1]=t; } 
 } 
 } 
 for(i=1;i<=n;i++) //输出结果
 printf("%d ",a[i]); 
 
 getchar();getchar(); 
 return 0; 
}
将上面代码稍加修改,就可以解决第 1 节遗留的问题,如下。
#include <stdio.h> 
struct student 
{ 
 char name[21]; 
 char score; 
};//这里创建了一个结构体用来存储姓名和分数
int main() 
{ 
 struct student a[100],t; 
 int i,j,n; 
 scanf("%d",&n); //输入一个数n 
 for(i=1;i<=n;i++) //循环读入n个人名和分数
 scanf("%s %d",a[i].name,&a[i].score); 
 //按分数从高到低进行排序
 for(i=1;i<=n-1;i++) 
 { 
 for(j=1;j<=n-i;j++) 
 { 
 if(a[j].score<a[j+1].score)//对分数进行比较
 { t=a[j]; a[j]=a[j+1]; a[j+1]=t; } 
 } 
 } 
 for(i=1;i<=n;i++)//输出人名
 printf("%s\n",a[i].name); 
 getchar();getchar(); 
 return 0; 
}

冒泡排序的核心部分是双重嵌套循环。不难看出冒泡排序的时间复杂度是 O(N 2 )。

冒泡排序和快速排序之间的过渡:

上一节的冒泡排序可以说是我们学习的第一个真正的排序算法,并且解决了桶排序浪费
空间的问题,但在算法的执行效率上却牺牲了很多,它的时间复杂度达到了 O ( N 2 ) 。假如我
们的计算机每秒钟可以运行 10 亿次,那么对 1 亿个数进行排序,桶排序只需要 0.1 秒,而冒
泡排序则需要 1 千万秒,达到 115 天之久,是不是很吓人?那有没有既不浪费空间又可以快
一点的排序算法呢?

快速排序

  快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候
设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全
部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样只能在相邻的数之间进
行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。当
然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和
冒泡排序是一样的,都是 O ( N 2 ) ,它的平均时间复杂度为 O ( N log N ) 。其实快速排序是基于一
种叫做“二分”的思想。
#include <stdio.h> 
int a[101],n;//定义全局变量,这两个变量需要在子函数中使用 
void quicksort(int left,int right) 
{ 
 int i,j,t,temp; 
 if(left>right) 
 return; 
 
 temp=a[left]; //temp中存的就是基准数 
 i=left; 
 j=right; 
 while(i!=j) 
 { 
 //顺序很重要,要先从右往左找 
 while(a[j]>=temp && i<j) 
 j--; 
 //再从左往右找 
 while(a[i]<=temp && i<j) 
 i++; 
 //交换两个数在数组中的位置 
 if(i<j)//当哨兵i和哨兵j没有相遇时
 { 
 t=a[i]; 
 a[i]=a[j]; 
 a[j]=t; 
 } 
 } 
 //最终将基准数归位 
 a[left]=a[i]; 
 a[i]=temp; 
 
 quicksort(left,i-1);//继续处理左边的,这里是一个递归的过程 
 quicksort(i+1,right);//继续处理右边的,这里是一个递归的过程 
} 
int main() 
{ 
 int i,j,t; 
 //读入数据 
 scanf("%d",&n); 
 for(i=1;i<=n;i++) 
 scanf("%d",&a[i]); 
 quicksort(1,n); //快速排序调用 
 
 //输出排序后的结果 
 for(i=1;i<=n;i++) 
 printf("%d ",a[i]); 
 getchar();getchar(); 
 return 0; 
}
书上对冒泡排序法的拓展介绍:
快速排序由 C. A. R. Hoare (东尼·霍尔, Charles Antony Richard Hoare )在 1960 年提出,
之后又有许多人做了进一步的优化。如果你对快速排序感兴趣,可以去看看东尼·霍尔
1962 年在 Computer Journal 发表的论文“ Quicksort ”以及《算法导论》的第七章。快速排序
算法仅仅是东尼·霍尔在计算机领域才能的第一次显露,后来他受到了老板的赏识和重用,
公司希望他为新机器设计一种新的高级语言。你要知道当时还没有 PASCAL 或者 C 语言这
些高级的东东。后来东尼·霍尔参加了由 Edsger Wybe Dijkstra 1972 年图灵奖得主,这个
大神我们后面还会遇到的,到时候再细聊)举办的 ALGOL 60 培训班,他觉得自己与其没有
把握地去设计一种新的语言,还不如对现有的 ALGOL 60 进行改进,使之能在公司的新机器
上使用。于是他便设计了 ALGOL 60 的一个子集版本。这个版本在执行效率和可靠性上都在
当时 ALGOL 60 的各种版本中首屈一指,因此东尼·霍尔受到了国际学术界的重视。后来他
ALGOL X 的设计中还发明了大家熟知的 case 语句,也被各种高级语言广泛采用,比如
PASCAL C Java 语言等等。当然,东尼·霍尔在计算机领域的贡献还有很多很多,他在
1980 年获得了图灵奖。

 啊哈算法---小哼买书(练习快速排序)_慢慢走比较快k的博客-CSDN博客

解决这个问题的方法大致有两种。第一种方法:先将这 n 个图书的 ISBN 号去重,再进
行从小到大排序并输出;第二种方法:先从小到大排序,输出的时候再去重。这两种方法都
可以

方法一:

#include <stdio.h> 
int main() 
{ 
 int a[1001],n,i,t; 
 for(i=1;i<=1000;i++) 
 a[i]=0; //初始化
 
 scanf("%d",&n); //读入n 
 for(i=1;i<=n;i++) //循环读入n个图书的ISBN号
 { 
 scanf("%d",&t); //把每一个ISBN号读到变量t中
 a[t]=1; //标记出现过的ISBN号
 } 
 
 for(i=1;i<=1000;i++) //依次判断1~1000这个1000个桶
 { 
 if(a[i]==1)//如果这个ISBN号出现过则打印出来
 printf("%d ",i); 
 } 
 getchar();getchar(); 
 return 0; 
}
这种方法的时间复杂度就是桶排序的时间复杂度,为 O ( N + M )
方法二:
第二种方法我们需要先排序再去重。排序我们可以用冒泡排序或者快速排序。
20 40 32 67 40 20 89 300 400 15
将这 10 个数从小到大排序之后为 15 20 20 32 40 40 67 89 300 400
接下来,要在输出的时候去掉重复的。因为我们已经排好序,所以相同的数都会紧挨在 1 章 一大波数正在靠近 —— 排序
一起。只要在输出的时候,预先判断一下当前这个数 a [ i ] 与前面一个数 a [ i 1] 是否相同。如
果相同则表示这个数之前已经输出过了,不用再次输出;不同则表示这个数是第一次出现,
需要输出这个数。
#include <stdio.h>
int main()
{
int a[101],n,i,j,t;

scanf("%d",&n); //读入n
for(i=1;i<=n;i++) //循环读入n个图书ISBN号
{
scanf("%d",&a[i]);
}

//开始冒泡排序
for(i=1;i<=n-1;i++)
{
for(j=1;j<=n-i;j++)
{
if(a[j]>a[j+1])
{ t=a[j]; a[j]=a[j+1]; a[j+1]=t; }
}
}
printf("%d ",a[1]); //输出第1个数
for(i=2;i<=n;i++) //从2循环到n
{
if( a[i] != a[i-1] ) //如果当前这个数是第一次出现则输出
printf("%d ",a[i]);
}
getchar();getchar();
return 0;
}

这种方法的时间复杂度由两部分组成,一部分是冒泡排序的时间复杂度,是 N ( N 2 ) ,另
一部分是读入和输出,都是 O ( N ) ,因此整个算法的时间复杂度是 O (2* N + N 2 ) 。相对于 N 2
说, 2* N 可以忽略(我们通常忽略低阶),最终该方法的时间复杂度是 O ( N 2 )
原书中的总结:
接下来我们还需要看下数据范围。每个图书 ISBN 号都是 1~1000 之间的整数,并且参
加调查的同学人数不超过 100 ,即 n 100 。之前已经说过,在粗略计算时间复杂度的时候,
我们通常认为计算机每秒钟大约运行 10 亿次(当然实际情况要更快)。因此以上两种方法都
可以在 1 秒钟内计算出解。如果题目中图书的 ISBN 号范围不是在 1~1000 之间,而是
-2147483648~2147483647 之间的话,那么第一种方法就不可行了,因为你无法申请出这么
大的数组来标记每一个 ISBN 号是否出现过。另外如果 n 的范围不是小于等于 100 ,而是小
于等于 10 万,那么第二种方法的排序部分也不能使用冒泡排序。因为题目要求的时间限制
1 秒,使用冒泡排序对 10 万个数进行排序,计算机要运行 100 亿次,需要 10 秒钟,因此
要替换为快速排序,快速排序只需要 100000×log 2 100000≈100000×17≈170 万次,这还不到
0.0017 秒。是不是很神奇?同样的问题使用不同的算法竟然有如此之大的时间差距,这就是
算法的魅力!
我们来回顾一下本章三种排序算法的时间复杂度。桶排序是最快的,它的时间复杂度是
O ( N + M ) ;冒泡排序是 O ( N 2 ) ;快速排序是 O ( N log N )

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

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

相关文章

安装Ffmpeg音视频编解码工具和搭建EasyDarwin开源流媒体服务器

目录 一&#xff0c;安装Ffmpeg音视频编解码工具 1&#xff0c;简介 2&#xff0c;开发文档 3&#xff0c;安装部署 二&#xff0c;搭建EasyDarwin开源流媒体服务器 1&#xff0c;简介 2&#xff0c;主要功能特点 3&#xff0c;安装部署 4&#xff0c;效果图 三&…

「mysql是怎样运行的」第17章 调节磁盘和CPU的矛盾---InnoDB的BufferPool

「mysql是怎样运行的」第17章 调节磁盘和CPU的矛盾—InnoDB的Buffer Pool 文章目录「mysql是怎样运行的」第17章 调节磁盘和CPU的矛盾---InnoDB的Buffer Pool[toc]一、缓存的重要性二、InnoDB的Buffer Pool2.1 啥是Buffer Pool2.2 Buffer Pool内部组成2.3 free链表的管理2.4 缓…

Sallen-Key二阶低通滤波器——设计问题浅析

目录前言1 Sallen-Key二阶低通滤波器结构2 截止频率3 结语前言 这两天接了个简单的活&#xff0c;关于设计一个Sallen-Key二阶低通滤波器&#xff0c;有一些体会。 1 Sallen-Key二阶低通滤波器结构 这个结构很简单&#xff0c;优势就是在于简易实现二阶低通。这种类电压跟随器…

第九届蓝桥杯省赛 C++ B组 - 日志统计

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4da;专栏地址&#xff1a;蓝桥杯题解集合 &#x1f4dd;原题地址&#xff1a;日志统计 &#x1f4e3;专栏定位&#xff1a;为想参加蓝桥杯的小伙伴整理常考算法题解&#xff0c;祝大家…

电脑技巧:分享六个非常实用的资源网站

今天小编给大家分享六个非常实用的资源网站&#xff0c;大家一起来看看吧&#xff01; 1、高清壁纸&#xff1a;Wallhaven 一个免费的高清壁纸下载网站&#xff0c;里面的壁纸资源丰富&#xff0c;更新速度也快&#xff0c;各种类型的壁纸都能找到&#xff0c;尤其是动漫壁纸。…

mars3d常用参数备用

1.多个包围盒计算飞行定位 map.camera.flyToBoundingSphere(boundingSphere) 可以一个数组记录下center&#xff0c;再用http://mars3d.cn/api/Map.html#flyToPositions 2.轨迹漫游实现沿着原轨迹倒退 把播放速率改为负数 3.自带的图层管理里面这个noCenter属性跟flyTo属性区…

动态网站开发讲课笔记02:Java Web概述

文章目录零、本讲学习目标一、 XML基础&#xff08;一&#xff09;XML概述1、XML2、XML与HTML的比较&#xff08;二&#xff09;XML语法1、XML文档的声明2、XML元素的定义3、XML属性的定义4、XML注释的定义5、XML文件示例&#xff08;三&#xff09;DTD约束1、什么是XML约束2、…

做出改变:农业科技和区块链在为地球的未来而战中的力量

到2050年&#xff0c;全球有100亿人需要养活&#xff0c;全世界都在关注区块链和农业信息化&#xff0c;以推动发展中国家的技术革新。 自成立以来&#xff0c;区块链技术已经找到了多样化和有价值的应用&#xff0c;以帮助提高效率和激励社区在不同领域和行业的参与。 农业是…

【华为OD机试模拟题】用 C++ 实现 - 去除多余空格(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

《Linux运维实战:Centos7.6基于ansible一键离线部署rabbitmq3.9.16镜像模式集群》

一、部署背景 由于业务系统的特殊性&#xff0c;我们需要针对不同的客户环境部署 rabbitmq镜像模式集群&#xff0c;由于大都数用户都是专网环境&#xff0c;无法使用外网&#xff0c;为了更便捷&#xff0c;高效的部署&#xff0c;针对业务系统的特性&#xff0c;我这边编写了…

git cherry-pick could not apply fb2cde669...问题解决

最近多个分支修复bug&#xff0c;在使用git cherry-pick进行小功能合并时经常会出现类似could not apply fb2cde669...的错误。具体如下图&#xff1a;具体原因是cherry-pick指定的commit内容中和当前分支有冲突导致的。具体解决分以下步骤&#xff1a;1&#xff1a;首先使用gi…

DeepLabV3+:对预测处理的详解

相信大家对于这一部分才是最感兴趣的&#xff0c;能够实实在在的看到效果。这里我们就只需要两个.py文件&#xff08;deeplab.py、predict_img.py&#xff09;。 创建DeeplabV3类 deeplab.py的作用是为了创建一个DeeplabV3类&#xff0c;提供一个检测图片的方法&#xff0c;而…

数据结构与算法入门

目录数据结构概述逻辑结构存储结构算法概述如何理解“大O记法”时间复杂度空间复杂度数据结构概述 数据结构可以简单的理解为数据与数据之间所存在的一些关系&#xff0c;数据的结构分为数据的存储结构和数据的逻辑结构。 逻辑结构 集合结构&#xff1a;数据元素同属于一个集…

Codeforces Round #848 (Div. 2)A-C

传送门 目录 A. Flip Flop Sum 代码&#xff1a; B. The Forbidden Permutation 代码&#xff1a; C. Flexible String 代码&#xff1a; A. Flip Flop Sum 题意&#xff1a;给你一个长度为n的数组&#xff08;数组元素只为1或者-1&#xff09;&#xff0c;你要且只能进行…

掌握lombok简化Java编码完成后端提效

Lombok安装 –>添加依赖 <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.16</version><scope>provided</scope> </dependency>scopeprovided&#xff0c;说…

LinkSLA智能运维技术派-Redis的监控

Redis是一个开源&#xff0c;内存存储的数据服务器&#xff0c;可用作数据库、高速缓存和消息队列代理等场景。 首先我们对内存进行监控&#xff0c;主要指标如下&#xff1a; - used_memory:使用内存 - used_memory_rss:从操作系统分配的内存 - mem_fragmentation_ratio:内…

如何成为一名黑客?小白必学的6个基本步骤

黑客攻防是一个极具魅力的技术领域&#xff0c;但成为一名黑客毫无疑问也并不容易。你必须拥有对新技术的好奇心和积极的学习态度&#xff0c;具备很深的计算机系统、编程语言和操作系统知识&#xff0c;并乐意不断地去学习和进步。 如果你想成为一名优秀的黑客&#xff0c;下…

交叉编译 acl

交叉编译 acl 概述 访问控制列表&#xff08;Access Control Lists&#xff0c;ACL&#xff09;是应用在路由器接口的指令列表。在 Linux 系统中&#xff0c;ACL 用于设定用户针对文件的权限&#xff0c;而不是在交换路由器中用来控制数据访问的功能&#xff08;类似于防火墙…

跑步耳机怎么选、最好用的跑步专用耳机分享

跑步时候戴着的耳机一直往下滑&#xff0c;跑步的步伐也不敢快起来&#xff0c;生怕耳机掉下去。除此之外&#xff0c;还担心跑步时流的汗水渗入到耳机里面&#xff0c;生怕因此被电到。因为没有合适的耳机在跑步时听歌&#xff0c;不但没能缓解跑步时的枯燥还徒增了些烦恼&…

力扣-市场分析

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;1158. 市场分析二、解题1.错误示范①提交SQL运行结果2.正确示范①提交SQL运行结果3.错误示范②提交SQL运行结果4.正确示范②提交SQL运行结果5.其他总结前…