【算法4总结】第一章:基础
- 目录
- 备份
第一章:基础
我认为这一章主要介绍的是如何使用工具。
一共五节,前两节主要是对 Java 语法的回顾,第三节则是三个数据结构,背包,队列和栈的API讲解。
而第四节是讲解的是如何分析算法。第五节则是针对具体的例子 union-find 算法,也就是并查集进行了实践。
算法分为算法思想和实现细节。而实现细节如果采用具体的语言来实现,会使得思想和实现细节混到一起难以剥离难以理解。本书采用的则是 Java 来直接描述算法。
由此也可以明白《算法导论》为什么采用伪码来描述算法。本书采用的是 Java 的子集,大部分语法和其他编程语言是通用的,很少使用 Java 的特性语言。
此处默认你已经学过 Java 或者其他语言的基本语法。前三节不再叙述。工具而已使用好就行!
算法分析
大多数的教材都是直接抛出模型,而没有直接提及模型是怎么来的。
从时间方面来看。首先从观察实际程序的运行时间出发,然后建立数学模型,之后在对模型进行分类。
而空间方面则是对内存进行了分析。
观察
可以从直接现实出发,利用时间 API 统计程序的运行时间。
根据数据规模和运行所需要的时间二者结合得到一个增长曲线。根据曲线粗略的得到一个增长函数。
例如一个程序的实际运行时间如图(横坐标是数据规模,纵坐标是运行时间):
从上图中并不能看出什么有效的信息,但是对横坐标和纵坐标同去对数就会得到一个斜率为 3 的直线。这就说明原来的函数为 T(N)=aN3T(N) = aN^3T(N)=aN3 。取对数后就是 lg(T(N))=3lgN+lgalg(T(N)) = 3 lgN + lgalg(T(N))=3lgN+lga
这样根据公式就可以从输入数据的规模计算出程序运行的时间了。
由此也可以得出程序的运行时间符合幂次法则 T(N)=aNbT(N) = aN^bT(N)=aNb 。
数学模型
程序运算的总时间和每条语句的耗时以及执行每条语句的频率有关。
前者和计算机,Java 编译器和操作系统有关。而后者取决于程序本身和输入。
对程序进行多次输入后得出,执行最频繁的指令决定了程序执行的总时间。这些指令也称为程序的内循环。
所以许多程序的运行时间都只取决于其中一小部分指令。
这样就使得程序的运行时间同具体的计算机剥离开来,只需要考虑程序本身即可。换台计算机对时间的影响是常数级别可以忽略。
增长数量级的分类
主要分为以下 7 种:
注意
- 不能忽略常数比较大的项。
- 计算机对于每条指令的执行时间可能并不是相同的,有些指令因为缓存的缘故很快就执行完毕。但是有些指令需要消耗不少时间。
- 系统不能对算法程序的运行时间产生影响。
- 程序在不同的场景下效果可能不一样。
- 对输入数据存在依赖,存在有些数据直接就是想要的结果,而有些则需要遍历全部也不一定能够找到想要的结果。
并查集 union-find
这一节讲的是 union-find 算法,也就是并查集。同时也是对应课程的第一节。
定义问题
我们需要明白这个算法解决了什么问题?也就是定义问题,然后是怎么解决的,之后就是改进了。
在现实世界中,如何判断两个人直接或间接的认识,间接认识表示二人通过第三方甚至更多人的联系而认识。
除此之外还有判断两地之间是否有必要建立通信线路,如果存在间接的联系则没必要。等等有很多类似的问题。
将以上的问题抽象起来,从集合的角度来看。要解决的问题则是判断两个点是否存在于同一集合之中。此处集合也称为连通分量。
如图所示,存在 7 个点,点和点之间的联系形成了 3 个连通分量。
当新加入一个联系时,点和点之间的联系随之发生改变。如图,2 和 5 之间建立了联系,变成了 2 个连通分量。
定义 API
为了解决这类问题,将其中的流程抽象化,自顶向下思考。可以写成五个函数:
- 首先需要初始化每个点
UF(int N)
- 最后就是建立联系了
void union(int p, int q)
- 判断时需要找到根节点
int find (int p)
- 根据根节点是否一致来判断已经在一个连通分量中
boolean connected (int p, int q)
将 API 组织起来:
public static void main(String[] args) {int N = StdIn.readInt();UF uf = new UF(N);while (!StdIn.isEmpty()) {int p = StdIn.readInt();int q = StdIn.readInt();if (uf.connected(p,q)) {continue;}uf.union();StdOut.println(p + " " + q);}StdOut.println(uf.count() + "components");
}
完整代码如下:
public class UF {private int[] id;private int count;public UF(int N) {count = N;id = new int[N];for (int i = 0; i < N; i++) {id[i] = i;}}public int count() {return count;}public boolean connected(int p, int q) {return find(p) == find(q);}public int find(int p) {}public void union(int p, int q) {}public static void main(String[] args) {int N = StdIn.readInt();UF uf = new UF(N);while (!StdIn.isEmpty()) {int p = StdIn.readInt();int q = StdIn.readInt();if (uf.connected(p,q)) {continue;}uf.union(p,q);StdOut.println(p + " " + q);}StdOut.println(uf.count() + "components");}
}
下面就是如何实现了!
细节实现
首先需要思考如何表示点与点之间的关系。
可以采用数组来表示,索引代表点本身,而存储的值代表指向的点。
起初一条联系都没有,都是孤立的点,所以将其指向的点都标识为自己,也就是数组中存储的值都改为其下标。
public UF(int N) {// 表示连通分量的个数,初始为 Ncount = N; id = new int[N];for (int i = 0; i < N; i++) {id[i] = i;}
}
数组的定义就是父节点,所以直接返回父节点即可。(注意此处的父节点同时也是根节点)
public int find(int p) {return id[p];}
首先判断是否已经连接,也就是父节点是否一致。已经连接的话就不需要在进行后续操作了,反之需要进行后续操作。
public void union(int p, int q) {int pID = find(p);int qID = find(q);if (pID == qID) {return;}for (int i = 0; i < id.length; i++) {if (id[i] == pID) {id[i] = qID;}}count--;}
为什么要遍历?假设存在两个连通分量,其中一个需要合并成一个,那么就需要将涉及到其中的所有点的父节点都修改为指向另外一个连通分量的父节点。
改进 find
每次进行 union 都需要执行一遍 for 循环,是一个线性增长。
此外 N 个点就有 N 个联通分量,假设要变成 1 个联通分量的话最多需要进行 N - 1 次 uoion 操作。
所以这个时间复杂度就是 O(N2)O(N^2)O(N2) 了。
union 的修改如下:
public void union(int p, int q) {int pRoot = find(p);int qRoot = find(q);if (pRoot == qRoot) {return;}id[pRoot] = qRoot;count--;}
直接赋值干掉了一个 for 循环,消除了线性增长。
但是 find 也要随之修改。find 需要拿到根节点,根节点的修改才能代表整个连通分量的合并。
可以修改 find 为如下:
public int find(int p) {while(p != id[p]) {p = id[p];}return p;}
通过循环找到根节点。如图,假设 3 和 5 之间要连接。那么首先找到 3 的根节点 9 ,5 的根节点 6 ,将 9 和 6 的指向修改即可。
修改前:
修改后:
这样就使得和 3 在同一连通分量下的所有阶段都合并到和 5 相关的连通分量中。而之前则是需要一个循环来修改所有与 3 相关的连通分量中的所有值。
注意每次拿到的是根节点,而非父节点。之前虽然得到的也是父节点,但因为只有两层的关系,所以也是根节点。此处修改 unoin 后,就变为了多层的关系。
这样操作带来的坏处就是如果层数过多, find 需要循环多次才能拿到根节点。而且在也与输入的数据有关,在一些情况下甚至还不如之改进前的速度快。
增加加权判断
find 操作次数多是因为层数过深,进一步修改就是降低层数。可以将其想象成一颗倒立的树,每次合并之时都是小树指向大树,这样就降低了树高。
假设有两颗树,树高分别为 5 和 3 。如果 5 指向 3 ,树高就会变为 8 。反之 3 指向 5 ,因为 3 没有 5 大,所以高度还是 5 ,3 则是挂在了 5 上。
加权就是判断树高,使得小树挂在大树上。
首先增加一个数组,用于存储树高。其高度均为 1 。
public WeightedQuickUnion(int N) {count = N;id = new int[N];for (int i = 0; i < N; i++) {id[i] = i;}sz = new int[N];for (int i = 0; i < N; i++) {sz[i] = 1;}}
union 修改如下:
public void union(int p, int q) {int i = find(p);int j = find(q);if (i == j) {return;}if (sz[i] < sz[j]) {id[i] = j; sz[j] += sz[i];}else {id[j] = i; sz[i] += sz[j];}count--;}
压缩路径
最初因为需要修改大量连接而低效。
将其优化后又因为当 find 操作需要循环多次而低效。
加权后的效果已经很好了,但是依旧存在 find 循环问题,最好不要让其循环,其实也就是压缩路径了。
可以在其循环之时顺便将父节点直接指向根节点。例如 CPP 版递归时进行压缩路径的实现。
int find(int x) {if (f[x] == x) {return x;}return f[x] = find(f[x]);
}
总结
这样优化后已经是最好的了,但依旧不是常数级别。事实上常数是不可能的,加权加上压缩路径已经是最优的了。
【算法4总结】第一章:基础相关推荐
- yxc_第一章 基础算法(三)_区间合并
截止2022.1.19,y总的基础算法网课已经全部听完.明天上午开始学习数据结构. 接下来还剩:第一章其他剩余的打卡题目.解决完这个问题之后,y总第一章基础算法就算暂时告一段落了. 未来算法学习的路还 ...
- Algorithms学习笔记-第一章 基础
#第一章 基础 标签(空格分隔): Algorithms学习笔记 文章目录 @[toc] 1.1 基础编程模型 1.1.1-1.1.8 主要是简述基本的java语法与概念 1.1.9 讲解了本书中自行 ...
- 【python第一章 基础捋顺,第二章 python基础语法】
第一章 基础捋顺,第二章 python基础语法 第一章 基础捋顺 第二章 python基础语法 2.1输入输出 2.2代码注释 2.3代码缩进 2.4命名规范 2.5变量 2.6基本数据类型 2.7数 ...
- HttpClient 中文官方教程----第一章基础知识-只收录,未测试
2019独角兽企业重金招聘Python工程师标准>>> 第一章基础知识 英文链接:http://hc.apache.org/httpcomponents-client-ga/tuto ...
- 汇编怎么从内存地址写入连续的数字_汇编语言 第一章 基础知识
系列文章见:Neptune 第一章 基础知识 1.1 机器语言 机器语言是机器指令的集合,机器指令展开来讲就是一台机器可以正确执行的命令. 由图可见,机器语言是十分晦涩难懂和不易查错的. 1.2 汇编 ...
- 【Python学习笔记】第一章基础知识:格式化输出,转义字符,变量类型转换,算术运算符,运算符优先级和赋值运算符,逻辑运算符,世界杯案例题目,条件判断if语句,猜拳游戏与三目运算符
Python学习笔记之[第一章]基础知识 前言: 一.格式化输出 1.基本格式: 2.练习代码: 二.转义字符 1.基本格式: 2.练习代码: 3.输出结果: 三.输入 1.基本格式: 2.练习代码: ...
- 【睿慕课点云处理】第一章-基础知识
[睿慕课点云处理]第一章-基础知识 作业 答 ROS对齐多种传感器数据的时间戳message_filters
- 小吴学汇编之第一章基础知识(1)
本人小白一枚,刚入职场,职业嵌入式开发.在学校学习Linux操作系统与ARM编程的一些基础知识.也不是什么985,211名校毕业,只是一个普通的本科生,在生活中为了那么一丝生存而努力着. 由于工作需要 ...
- Spring入门到精通:第一章 基础入门:1.Spring框架概述
Spring入门到精通:第一章 基础入门:1.Spring框架概述 前言:为什么要学习Spring框架? 为什么要学习Spring框架呐? (1)使用广泛:现在很多的企业都有Spring的影子,不管是 ...
- Python第一章-基础知识
第一章:基础知识 1.1 安装python. 直接官网下载最新的python然后默认安装就可以了,然后开始菜单里找到pyhton *.*.* Shell.exe运行python的交互shell ...
最新文章
- 计算机基础知识同步训练,计算机应用基础:全国高等教育自学考试同步辅导/同步训练(非计算机专业公共基础课)最新版...
- bat脚本登陆ftp服务器
- 使用 Node.js 开发简单的脚手架工具
- linux 内核参数 杨,Linux 内核参数
- 详解Linux-I2C驱动
- JavaScript 初学者应知的 24 条最佳实践
- css-样式重构-代码分享
- 移动端REM布局方案
- 在UE中自由绘制基本图元的几种方法
- 收录批量查询神器 bluecattools
- 状态机 复杂逻辑问题_状态管理中的逻辑
- php 企业邮箱,PHPMailer可能被全球邮(企业邮箱提供)拉黑
- 跟我学MVC系列(Repository模式、LINQ、EF、IOC框架Castle、JQuery、AJAX)(六)(应用IOC模式)...
- c语言黑色星期五公式,求黑色星期五问题~
- (C/C++) string / *char / int 基本轉換
- 打开计算机管理提示找不到文件,Win8系统打开计算机管理提示找不到文件怎么解决...
- 爆红Github!再来一个小白练手项目,20个项目随你造!
- python3帮助文档-中文
- windows7打不开html文件格式,mht是什么文件?Win7打不开mht格式文件的解决办法
- 结构光扫描仪(维基百科全翻译版)