基于《啊哈!算法》的算法笔记
以《啊哈!算法》,进行相关算法的记录和积累
RetenQ
C'est-la-vie
展开
-
7.3 并查集搜索
并查集算法并查集算法是一个利用结点关系,进行分类合组的算法简介并查集可以通过一个一维数组来实现我们把每一个点视作一个"独立的,只有一个结点"的树之后我们可以通过一些条件,逐渐将这些树合并成一棵大树合并的过程,其实就是找统一的父节点的过程,我们可以自定两条原则:1.相异的情况下,把右边的父节点改为左边的父节点。视作二者合并成了一组2.通过两者最高的父节点来进行比较另外,既然我们通过层层推进找到了某个点的父节点,那么我们在"统一"之后,也可以顺便的把路上的其它结点修改,这样会方便我们的二次寻找原创 2021-11-19 11:27:54 · 451 阅读 · 0 评论 -
7.2 二叉树与堆
二叉树与堆二叉树是一种特殊的、常见的树简介二叉树的特点在于每个结点最多只有两个儿子如果要使用更严格的递归定义,则是:二叉树要么为空,要么由根结点、左子树、右子树组成而左子树、右子树分别是一棵二叉树二叉树是使用范围极广的树,一棵多叉树也可以转换为二叉树二叉树类型满二叉树:如果二叉树中每个内部结点都有两个儿子,这样的二叉树叫做满二叉树完全二叉树:如果一棵二叉树除了最右边的位置上有一个或几个叶节点缺少外,其他都是丰满的,那么它就是完全二叉树我们可以把满二叉树理解为一种特殊的,极其完美的完原创 2021-11-19 11:24:39 · 426 阅读 · 0 评论 -
7.1 树的基本介绍
树的基本介绍介绍树其实就是不包含回路的连通无向图因为这个特点,我们为树赋予了这些特性:1.一棵树中的任意两个结点,有且仅有唯一的一条路径连通2.一棵树如果有n个结点,那么它一定恰好有n-1条边3.在一棵树中,加上一条边,那么就会得到一个回路树是指任意两个结点间有且仅有一条路径的无向图只要是没有回路的连通无向图,就是树结点节点我们把树中的每一个点叫做结点,当然也要叫节点的,无伤大雅根又叫做根节点,即最开始的节点。一棵树有且仅有一个根节点父亲结点简称父结点,儿子结点简称子结点如果一原创 2021-11-19 11:17:36 · 474 阅读 · 0 评论 -
6.4 最短路径:Bellman-Ford优化
Bellman-Ford优化我们知道,在最开始的算法中,我们每一次操作后就会进行一次松弛的判断实际上,这浪费了我们的时间:每次操作后有些顶点的最短路就不会变化了实际上我们可以这样做:每次仅仅对最短路的估计值发生了变化的顶点的所有出边执行松弛操作实操我们可以利用队列来维护这些点我们每次都选取队首的顶点u,对顶点u的所有出边进行松弛操作如果通过u->v这条边,可以使得源点到顶点v的最短路径变短,且顶点v不在当前队列中那么我们就把顶点v放入队尾在对顶点u的所有出边松弛完成之后,就将u出队原创 2021-11-19 11:14:58 · 216 阅读 · 0 评论 -
6.3 最短路径:Bellman-Ford算法
Bellman-Ford算法很强的一个算法,无论是思路、思想、代码实现都很优秀而且,它可以解决负权边的问题简介一句话概括这个算法就是:“对所有的边进行n-1次松弛操作”一样的,我们用uvw三者表示“从顶点u[i]到顶点v[i]的这条边,权值为w[i]”随后检查,新的距离会不会比原本的距离短1.用dis数组初始化估计值,并且把除了起始点之外的,都设置为正无穷大(解释见后)2.按边的图的数组中的顺序,遍历检查"这条路会不会距离变短"3.由于起始点到自身的距离是0,所以一定存在它到相邻区域的更小原创 2021-11-19 11:06:24 · 380 阅读 · 0 评论 -
6.2 最短路径:Dijkstra算法与单源最短路径
Dijkstra算法——单源最短路径Dijkstra算法是用来处理"指定一个点,计算该点到其余各个顶点的最短路径"这件事简介和上文有点像,因为当我们讨论一点到各个点的距离的时候,我们就不得不计算各种中转站我们这里得到了一个新定义,松弛,我们认为它是:如果两点距离通过中转点缩短了距离,我们就把这个过程叫松弛Dijkstra算法实际就是 “选点,松弛,更新,选点” 的不断循环,直到得到结果核心我们这里使用两个数组二维数组e来存储两点之间的路径关系一维数组dis来存储一个点到其余各个点的初原创 2021-11-19 11:01:27 · 167 阅读 · 0 评论 -
6.1 最短路径:Floyd-Warshall算法
Floyd-Warshall算法只有五行核心的算法简介假设我们有四个点。每个点之间都有一定的距离,或者甚至没有路现在我们想要知道如何获得两点之间的最短路径使用之前说的深度优先或者宽度优先当然是可以的,不过有没有更好的办法?于是我们使用了Floyd-Warshall,先进了一些的算法算法核心首先我们要知道,有的时候,通过n个点而从A->B,是有可能比直达得到更短的路径基于这个思路,我们逐步推进1.首先是直达,这个就不用说了2.然后我们假设“如果允许在点1中转”,得到新的结果比较,更原创 2021-11-19 10:52:41 · 593 阅读 · 0 评论 -
5.0 图与遍历搜索图
图的简介与二维数组图就是由顶点和连接顶点的边组成的表示图我们利用二维数组表示图二维数组的两个参数行列,均表示顶点,二者相交得到的即是距离我们用正无穷(一般可以用999999代替)表示二者之间没有边,0是某点到自身的距离比如e[a][b] = 3 ;就代表a到b的距离是3图还分为有向图和无向图顾名思义,有向图是有方向属性的,无向图则没有换言之,对于无向图,我们有:e[a][b] = e[b][a]图的遍历我们上一章学习了深度优先算法和广度优先算法现在我们就利用它们来完成图的遍历原创 2021-11-19 10:46:33 · 71 阅读 · 0 评论 -
4.3 深度广度搜索
实战:海岛面积例子我们有一个海岛,海岛有一个主岛和附属岛屿,总地图大小为10*10我们使用0代表海洋,1-9代表路段,我们由一个点(比如(6,8)开始)算出所在岛的面积我们想计算所在岛屿的面积解决我们直接利用广度搜索来进行这这件事,我们直接上了 #include <stdio.h> struct node{ int x ; // 横坐标 int y ; // 纵坐标 }; int main(){ /原创 2021-11-19 10:32:34 · 65 阅读 · 0 评论 -
4.2 bfs 广度优先搜索
广度优先算法 Breadth First Search BFS深度优先算法的兄弟,包含另外一种搜索思维也叫宽度优先算法概述与深度优先算法不同,广度优先算法注重于"对所有情况的分析搜索"如果是深度优先算法是刨根问底地分析每种情况,广度优先就是在在层层扩展中找到题解例还是之前的问题,我们想在n*m的迷宫中找到起点到终点的最短路径分析我们的核心思想是分析扩展时每发现一个点,就将这个点加入到队列中,直到到达终点另外,为防止一个点被多次走到,我们还要一个数组来记录一个点是否被走到队列与搜索路径原创 2021-11-18 20:40:17 · 195 阅读 · 0 评论 -
4.1dfs 深度优先搜索
深度优先搜索 Depth First Search DFS深度优先是比较基础的一种方法,还有一种是它的兄弟广度优先深度优先顾名思义:先深入搜索到一种情况的"底部"(原谅我用了这么抽象的词),然后再返回搜索其它情况例题我们依旧举例说明,比如我们现在想要在A,B,C三个箱子中放入1,2,3三张牌,想知道一个有多少种情况循环举穷首先我们一步步尝试每一次,每一个箱子中的放牌情况我们约定,对于每个箱子,我们都优先放入1,然后是2,最后是3当然,如果手上一张牌都没有,也就说明某种情况被列举完了我们用一原创 2021-11-18 20:38:39 · 361 阅读 · 0 评论 -
3.0暴力枚举
枚举枚举,暴力的一种算法,典型利用"计算机算力大大高于人力"这件事的做法枚举,顾名思义:有序的尝试每一种可能在书里,枚举被单独列为一章,不过每一节都是各种花式实战因此只用了一个MD笔记来记录它鬼畜奥数题我们现在有一道奥数题,要求使用1-9九个数字填入下方的等式:[][][]+[][][]=[][][]注意:每个数字只能用一次请问一共有多少种组合 #include <stdio.h> int main(){ int a,b,c,d,e,f,g,原创 2021-11-18 20:36:31 · 100 阅读 · 0 评论 -
2.3.2 使用数组模拟链表
链表(数组模拟)链表也可以使用数组来实现,操作和基础知识比指针简单但是个人觉得就思路和操作的清晰,以及对链表的理解而言,还是用指针好模拟链表介绍我们可以利用两个数组,分别记录链表要的两个东西:数据和地址我们使用一个数组data,来存储每个结点的数据使用另外一个数组right,来存储序列中每一个数右边的数的位置比如现在二者如下:位置 1 2 3 4 5 06 07 08 09data: 2 3 5 8 9 10 18 26 32ri原创 2021-11-18 20:33:16 · 380 阅读 · 0 评论 -
2.3.1 使用指针实现的链表
链表(指针)在存储一大波数的时候,如果使用数组,有时会感到数组显得不太灵活我们可以在C语言中使用指针和动态分配函数malloc来实现链表关于指针,这里就不赘述了,默认已经了解相关知识指针实现mallocmalloc 函数的作用就是从内存中申请分配指定字节大小的内存空间malloc(4); //这样就申请了四个字节大小的内存空间如果不知道字节大小,那么使用sizeof()查看就好了malloc 函数的返回值是void*,也就是未确定类型的指针,它可以被强制转换为任何其它类型的指针原创 2021-11-18 20:31:06 · 1000 阅读 · 0 评论 -
2.2 栈
栈栈是一种后进先出的数据结构,它就是栈栈限定了它只能在一端进行插入和删除的操作,这决定了它“后进先出”的性质栈的实现也比较简单:利用一个一维数组和一个指向栈顶部的变量(称它为top)即可,我们前面所讲的“插入”和“删除”的操作,就是通过这个"top"来实现的入栈入栈的操作很简易top++ ; s[top] = x ; //s是定义出来储存的数组/char组简化一下,就可以化成一行s[++top] = a[i]; 代码实战比如我们想要利用程序来检测一个字符串是否是回文//首先我们需原创 2021-11-18 20:24:37 · 237 阅读 · 1 评论 -
2.1 队列
队列队列是一种特殊的线性结构,它只允许在队列的首部head进行删除,以及在尾部tail进行插入这两种操作分别被称为“出队”和“入队”。而当队列中没有元素即head=tail时,称其为“空队列”我们现在可以将队列的三个基本元素(一个数组,两个变量)封装为一个结构体类型struct queue{ int data[100] ; //存储内容的主体 int head ; //队首 int tail ; //队尾};//——————————————————————————原创 2021-11-18 09:52:23 · 57 阅读 · 0 评论 -
1.3 快速排序
#快速排序快速排序的核心是基准数,基准数理论上是可以随机的一个数,这里我们每次都选择第一个/最左边的数作为基准数我们可以认为,快速排序的过程,就是不断选择基准数排序的过程。我们大致可以这么描述它的步骤:【比如我们排序 6 1 2 7 9 3 4 5 10 8 】1.选择一个数作为基准数,我们这里就是62.派出两个变量去“搜索”比6大的数,和比6小的数。这里我们设置变量为i与j3.i和j从两头开始搜索,i从最左边(第0位),而j从最右边(最后一位)4.i从左向右搜索,j从右向左进行搜索。当二者都原创 2021-11-18 09:46:25 · 81 阅读 · 0 评论 -
1.2 冒泡排序
冒泡排序核心思想在于“每次比较两个相邻的元素,如果它们顺序错误就把它们交换过来”,那什么是所谓顺序错误呢?比如,我们想要进行从大到小的排序,则把小的数字往后靠比如有五个数字12 35 99 18 76 ,我们试图将它们从大到小排序1.我们首先比较第一12和第二35,显然35大。依据核心思想,替换二者。那么现在五个数的顺序就是: 35 12 99 18 762.接着我们去比较现在的第二位12和第三位99,显然12又是更小的那个数,现在顺序变为35 99 12 18 763.重复上面的步骤直到比较原创 2021-11-18 09:45:43 · 92 阅读 · 0 评论 -
1.1 桶排序(简陋版)
#桶排序(简版)这是一种对于n个数据排序的方式这个算法是假设了有n个桶,编号便是0-*(n-1),共n个桶来装数据每当出现了一个数,我们就在对应编号的桶中放置一个标志查看数据时只要看每个桶中有多少数据即可//比如我们对0-10的数进行排序 int a[11] ; int i,j,t ; for(i = 10 ; i >= 0 ; i++){ a[i] = 0 ; //讲每个桶的原始值设为0 } for(i =1 ; i <=原创 2021-11-18 09:00:20 · 64 阅读 · 0 评论