《算法艺术与信息学竞赛》这本书我详细看了1.1、1.2、1.3,之后就看得不是很懂了,所以还是把摘要写出来,方便让我知道到底能学到什么。
第一章 算法与数据结构
“数据结构+算法=程序设计”
从理论分析和实际应用两方面阐述了算法与数据结构的基本知识。
1.1 概括的叙述了算法、数据结构、以及计算理论的一些概念。
1.2从实例出发,概括的介绍了一些基本算法,包括美剧、贪心、递归、递推。
1.3介绍基本数据结构,包括线性表队列、栈、树、二叉树、以及图遍历与拓扑排序。
1.4介绍了一些实用数据结构,包括哈希表、二叉搜索树、Trie结构、线段树、堆、并查集。
1.5介绍动态规划方法和一些经典动态规划问题
1.6介绍搜索算法。包括盲目搜索、启发式搜索、博弈算法和搜索的优化问题。
1.1
算法思想
算法分析
时空开销增长
基本操作
复杂度分析的常用符号
简化法则
复杂度的等级
伪多项式
不同情况下运行时间
复杂度和实际运行时间
时空辩证关系
计算模型
随机存取模型
并行计算机
非确定机
P类问题和NP问题
NP完全问题
NP-难度问题
不可解问题
随机算法、近似算法
1.2基本算法
枚举 在很多时候,无法立刻得出某个问题的可行解或者最优解,但是可以通过列举所有情况然后逐一判断来得到结果。
缺点 稍不注意就有遗漏
枚举可以用来作为其他算法的辅助算法
贪心法 不考虑所有可能的方案,而是每次选择当前的最优策略
每次选择一个局部最优策略惊醒实施,而不去考虑对今后的影响。一般来说它的时间复杂度较低,算法较易实现
缺点 往往得不到最优解,也难于证明解的最优性。
随机贪心法
递归与分治法
递归方程、主定理
递归式 T(n) = aT(n/b) + f(n)
其中a和b是常数,而f(n)是n的某个函数。如果把递归过程写成一棵树,如图所示
因此所求的结果应为 : T(n) = f(n) + af(n/b) + a2f(n/b2) +a3f(n/b3) + … + aLf(n/bL)
n/bL = 1 , L = logbn
简化的主定理:对于常数 a≥1, b>1, f(n) 是一个函数,T(n)递归定义如下:
T(n) = aT(n/b) + f(n)
T(n)可以通过比较 aT(n/b)和 f(n) 来计算。 T(n) = f(n) + af(n/b) + a2f(n/b2) +a3f(n/b3) + … + aLf(n/bL)
情形1 如果存在常数 K >1 使得 af(n/b) ≤ f(n)/k , 则 T(n) = Θ(f(n))
情形2 如果存在常数 K >1 使得 af(n/b) ≥ Kf(n) , 则 T(n) = Θ( nlogba)
情形3 如果af(n/b) = f(n) ,则 T(n) = Θ( f(n) logbn)
递推
根据已知信息不断计算未知信息,直到得到结果。
询问式交互问题
1.3 数据结构(1)
数据结构 数据结构是由某一数据对象及该对象中所有数据成员之间的关系组成 。 数据对象与数据关系。
线性结构,线性表,插入,删除,查找。
实现:顺序表、链表。
顺序表在物理上市一个连续的内存区域,数组的位置与元素相对应,这说明顺序表是适合随机访问的。在顺序表的最后加入或删除一个元素的复杂度为O(1),但是在中间插入一个匀速可能会涉及到很多元素的移动,在最坏的情况下会移动n次。
顺序表需要预先分配空间,所以适合于元素个数不作较大变动的场合。
在链表中,每个元素的位置是随意的,但是为了能遍历整个表,每个元素出了包括它本身数据,还应包含它后继元素的位置。如果元素还包含它前驱元素的位置,那么我们还可以进行反向遍历。链表的随机访问是比较麻烦的,我们不得不从第一个元素开始沿着链表一个个找到所需要的元素。链表的另一个缺点是有附加的空间占用。
顺序表一般用数组实现,链表一般用指针动态分配内存来实现。
栈:FILO
实现:一般用顺序表stack和栈顶指针top来实现。
入栈 push 出栈 pop
队列 :FIFO 插入只在队尾进行,删除只在队头进行,
实现: 用顺序表 queue、队首指针front和队尾指针rear来实现队列。
入队 出队
环形队列
链队
种子填充法 floodfill 。
填充过程就行是一滴墨水滴在了智商。开始知识一个小店,然后慢慢的扩散开来,最后填满一个封闭区域。借由队列地知识,可以实现:
floodfill算法
建立一个队列Q。初始时,Q仅包含最开始的点,对于每个出队的元素,把它周围扩散到的元素加入队列中。为了避免重复访问,需要记录每个元素是否已经访问过,即访问标志。这个算法的时间复杂度为O(m),m是需要扩散到的元素总数;发夹空间为O(m),这是它访问标志所用空间。如果访问标志可以和原始数据公用同一块内存区域,则附加空间为O(1)。
floodfill的应用
Floodfill 市一中很常用的预处理方法。当“连在一起很大一块东西可以一并处理”的时候,可以做一次floodfill,分离出一个个“连在一起”的块,然后再做处理。
串 string
串中的所以字符的集合构成了字母表,通常用符号∑来表示。和串有关的一些算法时间复杂度和∑的大小有关。
模式匹配问题
对于两个串s1(主串 text)和s2(模版串 pattern),问s1中是否包含串s2?
若是,给出位置?如何高效的做模式匹配
把对于主串中的每个位置i,检测从它开始的串是否以模版串为前缀。主串长度为n,模版串长度为m。
Knuth-Morris-Pratt(KMP)算法
前缀函数
也就是说,如果在匹配到s2 第i个元素的时候失败,那么通过刚才的方法计算出一个前缀函数值pre[i],然后把s1 的当前指针往后移动pre[i]个位置,s2的当前指针不变。这个方法就叫KMP算法。
pre函数的计算实际上是自己匹配自己。在匹配的过程中利用了已经计算的出的pre值来计算新的pre值。
Rabin-Karp 算法
有限状态自动机
字符串自动机进行模式匹配。
1.3.3
数和二叉树
二叉树(binary tree)可以采用如下递归定义:二叉树要么为空,要么由根节点(root),左子树(left subtree)和右子树(right subtree)组成。左子树和右子树分别是一棵二叉树。
实现 : 通常用指针实现,每个结点包含数据域,左子树指针和右子树指针。
完全二叉树 设二叉树的高度为h,则共有h+1层。除第h层外,其他各层的结点数都达到最大个数,第h层从右向左连续缺若干结点,这就是完全二叉树(completet binary tree).
完全二叉树的数组实现。
树和森林 树要么为空,要么是由根结点(root)和n棵字数组成。森林有m棵树组成。
树的表示法 二叉树并不是树的一种,因为二叉树的子树中有严格的左右之分,而树是没有的。这样以来,可以用每个节点的父结点来表示一棵树,即父亲表示法。
但这样一来,很难进行遍历操作,所以通常使用“左儿子右兄弟”表示法,这实际上是把树转换成了二叉树:某结点在树中的左儿子作为二叉树中该结点的左儿子,它在树中右兄弟作为二叉树中他的右儿子。此外,树还可以用广义表表示。
遍历
在线性结构中,很容易对表按顺序惊醒便利,但是在非线性结构中,遍历要复杂一些,方法也不唯一。对于二叉树来说,通常有三种方式:先序遍历、中序遍历和后序遍历。
重建二叉树
最近公共祖先问题(LCA) 树的最基本的问题之一。
LCA问题 给出一棵有根树T,对于任意两个结点u,v,求出LCA(T,u,v),即离根最远的结点x,使x同时是u,v的祖先。
在线算法
离线算法
(不了解树,不懂LCA)
1.3.4 图及其基本算法
图 顶点和边的集合组成的二元组。
简单图 任意两个顶点最多只有一条边,且每个点都没有连接到它自身的边,每个点没有自己连自己的图叫简单图。
完全图 若有n个顶点的无向图n(n-1)/2 条边,则此图为完全图
权
路径
无向图 边条数
带权图 权相加
顶点不重复 简单路径
首点和尾点重合 回路或者环
连通性
两点有路径,则两点是连通的。
图中任意一对顶点都是连通的,则此图是连通图。
非连通图的极大连通子图叫做连通分量(connected component)
有向图中,任意两个顶点既有去又有来的路径,则称此图是强联通图。
非强连通图的极大连通子图叫做强连同分量。
一个连同图的生成树(spanning tree)是它的极小连通子图,在n个顶点的情形下,有n-1条边。
图的实现
图在计算机中存储主要有两种方式,一般有n表示顶点数,m表示边数。
邻接矩阵
在图的邻接矩阵(adjacent matrix)表示中,有一个记录各个顶点信息的顶点表,还有一个表示各个顶点之间关系的邻接矩阵。
设图A=(V,E)是一个有n个顶点的图,则图的邻接矩阵是一个二维数组gr,当(i,j)为图中的边时gr[i,j] = 1,否则gr[i,j] = 0。
容易发现,无向图的邻接矩阵是对称的,有向图的邻接矩阵可能是不对称的。
如果存储带权图,那么把gr[i,j]的值改为权w(i,j)的大小。
带权的邻接矩阵无法保存重边。
邻接表
图的邻接表,把同一个顶点发出的边链接在同一个边链表中,链表的每一个结点代表一条边,叫做边结点,结点中保存有与该边相关联的另一个顶点的顶点下标dest和指向同一个链表中下一个边结点的指针link。
前向星 forward start
图的遍历
广度优先遍历 BFS
从一个顶点出发,按照他的最短路路径长度从小到大的顺序遍历。用一个队列实现。
好处:如果遍历一个很大的图,需要找到某两地的最短距离,可以在遍历到这个顶点的时候立刻停止遍历。时间复杂度为O(n+m)。
深度优先遍历 DFS
从一个顶点出发,沿着边尽量走到没有访问过的顶点。如果没有未访问过的顶点,就沿着与边相反的方向退一步。
好处:实现简单,用空间较少
拓扑排序
1.3.5 排序与检索基本算法
二分检索(折半查找)
优化问题和判定问题
除了检索有序线性表,二分检索还可以把一类最优化问题转换成判定问题。 这类问题的共同特点是,给定一个条件C,求满足该条件的最大树k,其中l≤k≤u(l,u已知)。
有时候,这个问题还满足这样的条件,“对于两个数 a<b, 如果b满足该条件,则a一定也满足”。把最优化问题转化为判定问题是很有用的。
在连续型问题中,常常使用二分的方法逐步逼近最优解。
常用算法
插入
选择
冒泡
快速排序和归并排序
排序网络
1.4 数据结构(2)
1.4.1 并查集
设想需要对不相交集合(disjoint set)进行两种操作:
1)检索某元素属于哪个集合
2)合并两个集合
我们最常用的数据结构是并查集的森林实现。在森林中,每棵树达标一个集合。用树根来标识一个集合。
树的形态并不重要,重要的是每棵树里有哪些元素。
合并操作 为了把两个集合S1和S2并起来,只需要把S1的跟的父亲设置为S2的根就可以了。(让深度较小的树成为深度较大的树的子树)
查找操作 路径压缩
1.4.2 堆极其变种
堆是一棵完全二叉树,每个结点有一个权。它的特点是根的权最小,且根的两个子树也各是一个堆。
堆的插入 由于需要维持完全二叉树的形态,需要先将结点x插入到新的位置,然后把x上升调整(做元素交换)到合适的位置,即当x比它父亲小时,把x和它父亲交换。
堆的删除 由于需要维持完全二叉树的形态,需要先用“最后一个结点”x把要删除的结点覆盖掉,再把x下降调整到合适的位置,即当x比它的某个儿子大时,把x和它的较小儿子交换。
堆排序算法
杨氏图表和笛卡尔树
1.4.3 字典的两种实现方式:哈希表、二叉搜索树
哈希表 思路是根据关键码值直接访问。
二叉搜索树
它要么是一棵空树,要么它的左子树的所有结点比根结点小,右子树的所有结点比根结点大,而且它的左子树和右子树分别是一棵二叉搜索树。
查找和插入算法 二叉搜索树是递归结构,查找和插入可以用递归的方法,根据关键字和根的大小关系递归在子树中查找。
删除
1.4.4 两个特殊树结构:线段树和Trie
树结构的基本思想是分改。普通二叉搜索树是按照对象来进行划分,因此效果往往和数据结构内队形有关;本届的两个数据结构深根据关键码的可能范围来分的,这种技术叫做关键空间分解。
线段树的处理对象是线段(一般意义上的区间也可以看成是线段),它把线段组织成利于检索和统计的形式,它的本质 是线段的二叉搜索树。但是线段可以分解和合并,线段树又有一些一般二叉搜索树所没有的特殊操作。另外,线段树操作的是整个区间,它的渐进时间效率不依赖于数据结构中的对象。
线段树
线段树是一棵二叉搜索树,最终将一个区间[1,n]划分为一些[i,i+1]的单元区间,每个单元区间对应线段树中的一个叶节点。每个结点用变量country来记录覆盖该结点的线段条数。
线段树处理对象 线段树的处理对象是一段较狭窄的区间,区间上的各点对应有限个固定的变量,好像是线性的数组用完全二叉排序树的形式表达。
trie 公用串
1.5 动态规划
动态规划的两种动机
1 利用递归的重叠子问题,进行记忆化求解,即先用递归发解决问题,再利用重叠子问题转化成动态规划。
最优化原理和最优子结构
状态和状态转移方程
无后效性。决策只取决于当前状态的特征因素,二和到达此状态的方式无关。
无后效性是应用动态规划的重要条件
决策和多阶段决策问题
向前递推法和向后递推法
向后递推,它的最后结果和结束状态的指标函数值;
向前递推,它的最后结果是起始状态的指标函数值
动态规划思路产生的两种方法
用递归思路建立模型,容易算出状态的前驱,适合顺推
用多阶段决策建立模型,容易算出状态后继,适合逆推
无后效性和最优子结构
它们的本质一样,即利用无后效性定义状态,进一步根据最优子结构定义状态的指标函数饼进行状态转移。重叠子问题越多,动态规划的优越性也就越明显。状态和状态转移是动态规划的精髓。
状态表示 动态规划方法的核心是状态表示。状态的可定义性来源于问题的无后效性。由于状态是人为规定的,可以通过正佳维数的方法来消除后效性,也可以在保持无后效性的前提下改变状态定义,以得到更多的重叠子问题。
状态转移 有了状态定义之后,思考的重点在于状态转移。状态转移的可定义性来源与问题的最优子结构。当问题不具备最优子结构的时候,通常通过增加维数的办法来解决。
动态规划的两种实现方式 即递归和记忆化搜索。
1.5.2 常见模型分析
线性模型 线性模型是最容易导致动态规划算法的。只要把线性结构分成两个波分,往往可以用递归来解决。另一个想法是每次增加一个元素,逐步扩大考虑范围
串模型 串模型也是一种线性模型,但是由于串有特殊的“匹配”、“取前缀后缀”;
状态压缩模型
1.5.3若干经典问题和常见优化方法
1.6 状态空间搜索
搜索被称为“通用解题法”,在算法和人工智能中占有重要地位。
人工智能搜索
1.6.1 状态空间
考虑这样问题,在枚举算法中,是按照生成方案→验证方案这样的两步曲来进行的。出了事前分析之外,两步之间没有任何联系,是孤立的、
一个很自然的想法产生了:
能不能在生成方案的中途也做一些验证,从而避免一些肯定不会是解的方案生成呢?
肯定的,不过需要把接的生成分成若干步骤,这样才给每个步骤的验证提供了可能。
状态 状态转移 智能体
状态(state)是对问题在某一时刻的进展情况的数学描述,状态转移(state-transition)就是问题从一种状态转移到另一种(或几种)状态的操作。如果只有一个智能体(agent)可以实施这种状态转移,则目的是单一的,也就是从确定的起始状态(start state)经过一系列状态转移而到达一个(或多个)目标状态(goal state)。
简单的多智能体系统
如果不止一个智能体可以操纵状态转移,例如两人下国际象棋,那么他们可能会朝向不同的,甚至是对立(例如所有的对战游戏)的目标进行状态转移。
状态空间
搜索的过程实际上是在遍历一个隐式图,它的结点是所有的状态,有向边对应状态转移,而一个可行解就是一条从其实结点出发到目标状态集中任意一个结点的路径。这个图称为状态空间,这样的搜索称为状态空间搜索(Single-Agent Search),得到的遍历树称为解答树。
1.6.2盲目搜索算法
每个算法都有其适用范围
纯随机搜索(Random Generation and Random Walk)
广度优先搜索(BFS)和深度优先搜索(DFS)
重复式搜索 这些搜索通过对搜索树扩展式做一些限制,用逐步放宽条件的方式进行重复搜索。
迭代加深搜索 (Iterative Deepening)
限制搜索树的最大深度Dmax,然后进行搜索
1.6.3 启发式搜索算法
1.6.4 博弈问题算法
1.6.5 剪枝
1.6.6 路径寻找问题
1.6.7 约束满足问题