市赛前第二次培训讲义
An-Li Alt Ting October 6, 2014
Contents
1 Data Structure 数据结构 2
2 Array 阵列 2
3 Linked list 链表 3
3.1 Problems on OJs . . . 4
4 Stack 堆栈 4 4.1 实作细节 . . . 4
4.2 Algorithms those depend on stack . . . 5
4.3 Problems on OJs . . . 5
5 Queue 队列 5 5.1 实作细节 . . . 6
5.2 Algorithms those depend on queue . . . 6
5.3 Problems on OJs . . . 6
6 Binary Search 二分搜寻 6 6.1 Ternary Search 三分搜寻法 . . . 7
6.2 Problems . . . 7
6.3 Problems on OJs . . . 7
7 Sorting Algorithms 排序算法 8 7.1 Algorithms those depend on Sorting Algorithms . . . 8
7.2 Problems on OJs . . . 8
8 Depth-first Search 深度优先搜索 8 8.1 虚拟码 . . . 9
8.2 生成树、生成路径与时间戳记 . . . 9
8.3 Algorithms those depend on DFS . . . 10
8.4 Problems . . . 10
8.5 Problems on OJs . . . 10
9 Breadth-first Search 广度优先搜索 11 9.1 Problems on OJs . . . 11
10 C 概论 11 10.1 输入输出与档案. . . 12 10.2 指标 . . . 12 10.3 References . . . 12
1 Data Structure 数据结构
数据结构(data structure、资料结构、结构)是一系列用以解决的资料存取的相关问 题的算法。
结构经常对资料的存取进行一些约束,这些约束通常有助于资料安全。甚至有几 乎只有约束的结构,如堆栈。
结构经常统计一些与输入相关的数据,这些数据通常有助于更有效率的搜寻资料。
结构经常将一些常用的存取算法抽象为操作,这有助于让算法设计者专注于解决 更高层面的问题,这在设计中被称为封装(encapsulation)。
常见的结构都是解决基本而普遍的问题,它们经常专注于资料的顺序与大小关系,
或者提供一个统计的模型。
在竞赛中,除了认识数据结构的常见定义外,更重要的是了解其中道理,方能活 用资料结构。
2 Array 阵列
阵列(array、数组)是一种数据结构。
阵列是定长的(fixed-size)。阵列的长度在被建立时就已经被指定,长度被指定以 后即不能更改。
一个长度为 n 的阵列有 n 个相异的索引值(index value),每一个索引值都被映射 到一个键值(key value)。每一个键值都可以被改变。
阵列支援随机存取(random access)。对于一个资料结构来说,随机存取是指可以 直接存取到结构中的任何一个元素,一般要求要在常数时间内完成。
多维阵列被允许可以有单个或复数个维度。在某些编程语言中被视为多维阵列的 阵列,某些则否。二维阵列经常被用来实作数学中的矩阵或行列式。
动态阵列(dynamic array)是一种可变长度的阵列。注意到动态阵列的阵列经常 不被认为是多维阵列。
现代的计算模型都十分依赖整列的一些特性。许多算法与资料结构在不加以说明 时,都是指在阵列上实作。以下列举一些明显依赖的例子:字串、二分搜寻法。
C/C++ 中,阵列是用指标建立的,并使用 pointer to array 建立多维阵列;C++
STL 中的 <vector> std::vector 是动态阵列的一个模版实作;C++11 STL 中的 <ar- ray> std::array是定长阵列的一个模版实作;C++ STL 中的<map> std::map 是一个 外部行为与阵列有些相似的模版实作。
3 Linked list 链表
链表(linked list、list、串、链结、连结串列)是一种数据结构。
一个链表维护数个节点的顺序,对每一个节点记录它在顺序上的相邻节点(通常 是记录索引值、指标、或参考)。根据问题的需求,可能串成一列,或者串成一环。通 常串成一列的链表还会记录头尾节点。注意到同一组节点可以有多个链表在维护他们 不同的顺序。
这些统计资讯明显表达了一个顺序。注意到这经常不是指节点们在记忆体中的实 际顺序。
这些统计资讯也方便我们查询一个结点的下一个结点,或者是下下个结点。如果 要查询下下个节点,我们不能直接的查到,而必须要间接地查下一个结点的下一个节 点,这样的存取结构被称为循序存取(sequential access)。
由于记录的是每一个节点的下一个节点,所以在这样的顺序中删除其中一段,或 者插入另外一段也是十分容易的。如果要插入的资料本身就是链表,那插入 n个也只 要 O(1) time,删除也是一样。
双向链表是一种链表,这可以看作两个链表,一个是维护元素之间的正序,一个 是维护元素之间的反序。但是
跳表是一种链表,它统计的不只是于一个结点相邻的元素,根据问题的需求,会 统计更远的结点位置。
许多资料结构都建立于在特定关系的元素间建立连接,链表的主要精神也是为连 接。广义来说链表可以不是容器,只要统计一份类似的资讯,可以改进复杂度就好。
3.1 Problems on OJs
HOJ 050 幸福幼稚園
HOJ 107 程式順序問題
4 Stack 堆栈
堆栈(stack、堆叠、栈)是一种数据结构,是一种容器。
堆栈有四种基本操作:查看体积(size),查看顶端元素(top element),推入(push),
弹出(pop)。
体积是指内容的元素个数。一个堆栈的初始体积是 0。每次推入会增加 1,每次弹 出会减少 1。一个堆栈的体积为 0若且唯若它是空(empty)的。
对于一个非空堆栈来说,顶端元素是指其中最晚被推入的元素。总是只有顶端元 素可以被查看。
推入 x 是指新增一个元素 x;x 在推入完成以后会成为顶端元素。
对于一个非空堆栈来说,弹出是指删除当下的顶端元素。
对与所有曾在堆栈中进出过的元素来说,任两个元素之间,比较晚入栈的总是比 较早出栈;这样的性质被称为后进先出(Last In, First Out(LIFO))。
另外还有叫做单调栈的特殊堆栈。
4.1 实作细节
堆栈可以用阵列或链表实作。
用阵列或链表实作的话:每一个操作的时间复杂度都是 O(1);空间复杂度都是 O(n)。
4.2 Algorithms those depend on stack
Depth-first Search 深度优先搜索
4.3 Problems on OJs
HOJ 002 要我寫毛阿 HOJ 034 海報
TIOJ 1012 Rails TIOJ 1176 Cows TIOJ 1063 最大矩形 UVaOJ 514 Rails UVaOJ 10858
5 Queue 队列
队列(queue、伫列、佇列)是一种数据结构,是一种容器。
队列有四种基本操作:查看体积(size),查看前端元素(front element),推入(push),
弹出(pop)。
体积是指内容的元素个数。一个队列的初始体积是 0。每次推入会增加 1,每次弹 出会减少 1。一个队列的体积为 0若且唯若它是空(empty)的。
对于一个非空队列来说,前端元素是指其中最早被推入的元素,后端元素(back
element)是指最晚被推入的元素。总是只有前端元素可以被查看。
推入 x 是指新增一个元素 x;x 在推入完成以后会成为后端元素。
对于一个非空队列来说,弹出是指删除当下的前端元素。
对与所有曾在队列中进出过的元素来说,任两个元素之间,比较早入列的总是比 较早出列;这样的性质被称为先进先出(First In, First Out(FIFO))。
5.1 实作细节
堆栈可以用阵列或链表实作。
用阵列或链表实作的话:每一个操作的时间复杂度都是 O(1);空间复杂度都是 O(n)。
如果只能使用定长阵列的话,可以对索引值模除,模拟串接成环的阵列。
5.2 Algorithms those depend on queue
Breadth-first Search 广度优先搜索 Shortest Path Faster Algorithm (SPFA)
5.3 Problems on OJs
TIOJ 1012 Rails UVaOJ 514 Rails
6 Binary Search 二分搜寻
二分搜寻(binary search、二分搜)是一种算法。它的用途是在单调的布林函数中,逼 近或寻找一个界。
给定一布林函数 f(x),∃! b | f(x) ⇐⇒ x < b ∀ x。我们已经知道 b 属于一个区 间 r = [h, t)。而我们发现 f(h+t2 ) ⇐⇒ b ∈ [h+t2 , t)。所以由观察 f(h+t2 ),可以将范围 缩小至原来的一半。重复这个方法多次,O(n)大的范围大小就会在 O(logn)time 里缩 小成 O(1)。
二分搜寻的几个常见应用是:在單調函數中逼近特定的值、在有序範圍內查找界 線或元素。
C++ STL 中有 <algorithm> binary_search() lower_bound() upper_bound()。
binary_search first last is repeat
median gets ( first + last )/2 if f( median )
last gets median else
first gets median
until ( first , last ) is short enough
6.1 Ternary Search 三分搜寻法
有一个凸函数f(x),我们可以用三分搜寻法逼近极大值的位置。
不过可以直接二分搜寻布林函数 g(x)≡f′(x)>0,常数更小,程式更好写。
6.2 Problems
Lowest Common Ancestor (LCA) 最低共同祖先 Classic Minimum Ratio Cycle
6.3 Problems on OJs
POJ 1094 Sorting It All Out POJ 2104 K-th Number
經典,使用 lower bound 與upper bound。
HOJ 003 童話故事 HOJ 019 智力測驗 HOJ 085 毛毛蟲問題 HOJ 185 噪音取締
HOJ 131 野心勃勃的乳牛
HOJ 184 血拼
IOICJ 017 程度差距 EX
7 Sorting Algorithms 排序算法
常见的排序算法有:选择排序、插入排序、气泡排序、快速排序、归并排序。
不断从未排序区朴素的以线性时间找出最小值,并将之接于已排序区的结尾,这 样的算法被称为选择排序。
不断输入数字,并朴素的以线性算法将其插入到阵列中的对应位置,这样的算法 被称为插入排序。
不断检查相邻的两元素,如果其逆序,则将其键值对换,直到没有逆序的相邻元 素为止,这样的算法被称为气泡排序。
选择与排序区间中的一的键值 x,并将比 x 小的元素都放到其之前,比 x 大的元 素都放到其之后。然后使用快速排序将 x 的两边都作排序。这样的算法被称为快速排 序。
如果排序区间大小为 1,则算法结束。否则将目标区间从中间分为两块,分别使用 归并排序法排序以后,再以线性算法将两个已排序区间合并,这样的算法被称为归并 排序。
7.1 Algorithms those depend on Sorting Algorithms
Kruskal’s Algorithm
7.2 Problems on OJs
TIOJ 1080 A.逆序數對
8 Depth-first Search 深度优先搜索
深度优先搜索(Depth-first Search(DFS)、深搜)。 深度优先搜索是一种遍历图的算法。
深搜总是先从所有与已发现的顶点相邻的顶点中找到深度最高的继续探索,直到 其中没有比当前节点更深的才回朔。直到所有顶点都被探访过则搜索结束。
IDDFS 是一种特殊的 DFS,可以以更小的空间复杂度取代部分用途的 BFS。
8.1 虚拟码
DFS on directed graph:
dfs v is
mark v as discovered for all (v,w) in E
if undiscovered w
mark (v,w) as tree edge dfs w
else if not finished w
mark (v,w) as back edge else if lca (v,w) = v
mark (v,w) as forward edge else
mark (v,w) as cross edge mark v as finished
DFS on undirected graph:
dfs v is
mark v as visited for all (v,w) in E
if not visited w
mark (v,w) as tree edge dfs w
else
mark (v,w) as back edge
8.2 生成树、生成路径与时间戳记
在一个图上深搜会生成一个有向树————深搜生成树(DFS Spanning Tree、DFS
Tree),以及该生成树的深搜路径————深搜生成路径。
有向圖的 DFS tree有四種邊:
tree edge:tree 上的 edge;
back edge:回到自己祖先的 edge;
forward edge:跳到自己子孫的 edge;
cross edge:其他的 edge。
無向圖的 DFS tree只有 tree edge 與 back edge。
LCA 问题可以被归约成深搜路径上 RMQ问题。
深度优先搜索在进入一个顶点与离开一个顶点的时间戳记(也就是生成路径的反 函数),这被应用在 Tarjan’s Algorithm 与 The DFS version of Topological Sorting Finding Algorithm 上。
一个珍贵的想法:DFS 一張無向圖時,產生有向 tree edge 與 back edge。不探訪父親 就不會把 tree edge當成 back edge。不標記 back edge,就會把 back edge (v, w) 當成 (w, v)。
8.3 Algorithms those depend on DFS
Hierholzer’s Algorithm Kosaraju’s Algorithm Tarjan’s Algorithm
The DFS version of Topological Sorting Finding Algorithm
8.4 Problems
Strongly Connected Component (SCC) 強連通分量 Eulerian Path 歐拉路徑
8.5 Problems on OJs
HOJ 105 連結問題 HOJ 307 幸運碼 TIOJ 1014 打地鼠 TIOJ 1025 數獨問題
ZJ a290 新手訓練系列 圖論
9 Breadth-first Search 广度优先搜索
广度优先搜索(Breadth-first Search(BFS)、广搜)。 广度优先搜索是一种遍历图的算法。
广搜总是先从所有与已发现的顶点相邻的顶点中找到深度最低的继续探索,直到 没有比当前节点更深的才探索下一个深度的顶点。直到所有顶点都被探访过则搜索结 束。
BFS可与用于解决辺权全部为 1的最短路径问题。
bfs v is
initialize Q as an empty stack while Q is not empty
v gets the front element of Q mark v as discovered
for each (v,w) in E if undiscovered w
push w into Q mark v as visited
9.1 Problems on OJs
HOJ 026 靈犬尋寶 POJ 1753 Flip Game POJ 3278 Catch That Cow Step5OJ 0127攻略妹妹
10 C 概论
这部分笔者实在没有时间写,所以许多部分只留下关键字。不过我在想,也许这比真 的讲一遍还要珍贵。
10.1 输入输出与档案
竞赛经常只需要从标准输入输出或档案交流就可以了,这种情况下,我们经常使用
<stdio.h> freopen() 来重置 stdin 与stdout 这两个 FILE* 指标。
keywords: ‘file pointer‘, ‘file descriptor‘, ‘end-of-file‘.
10.2 指标
keywords: ‘pointer to array‘, ‘pointer to function‘, ‘void pointer‘.
10.3 References
Wikipedia
http://www.wikipedia.org/
感谢许多来自Wikipedia 的相关图片。