算法设计手冊(第2版)读书笔记, Springer - The Algorithm Design Manual, 2ed Steven S.Skiena 2008

  1. 云栖社区>
  2. 博客>
  3. 正文

算法设计手冊(第2版)读书笔记, Springer - The Algorithm Design Manual, 2ed Steven S.Skiena 2008

技术mix呢 2017-10-19 17:14:00 浏览1930
展开阅读全文

The Algorithm Design Manual, 2ed

跳转至: 导航、 搜索

Springer - The Algorithm Design Manual, 2ed Steven S.Skiena 2008

文件夹

介绍

  1. 求一维最长不重叠的子区间的集合:从左往右,总是选择结束时间最早的(启示式=〉贪心策略)
    1. 一般的问题:贪心策略的正确性怎样证明?比方MST(好像忘掉了)
  2. War Story:Psychic Modeling(这个问题究竟是什么?)
    1. 一開始建模为set cover,可是不正确(确保winning pair不须要cover all,正确的怎么做作者没有说明)

算法设计

  1. p48 高速指数幂
  2. War Story:金字塔传奇(算法的并行加速)
    1. Waring's问题:不论什么整数可表示为4个整数的平方和
    2. (猜想)不论什么整数可表示为<=5个金字塔数(形如(m^3-m)/6)的和(事实上就是元素满足特定约束的背包问题)
      1. 首先按大小排序。然后构造2个元素和的全部情形(仍然按大小排序)
        1. 评论:这里构造估计算集相当于空间换时间,而要求数据有序则使得能够二分搜索
      2. (层次递进)对数k。首先确认它是否是1818个最小的金字塔数p之中的一个。再确认它是否是2个金字塔数的和two之中的一个(二分搜索!

        1. 要确认是否是3个金字塔数的和之中的一个。这时须要分治:首先从k中减去p[i];复杂度O(n^(1/3) lgn)
        2. 4个情况。分为‘2+2’。即k-two[i]
      3. 总复杂度O(n^(4/3) lgn),相比原来的穷尽搜索(O(n^2))
  3. Esoteric函数
    1. 反向Ackerman:invA(n)‘缓增’
    2. loglog n
    3. logn / loglog n(n个叶子节点的二叉树的高度)
    4. log^2 n

数据结构

  1. Contiguous vs Linked
  2. 栈与队列
  3. 字典
  4. BST
    1. 平衡搜索树
  5. 优先队列
  6. War Story:Stripping Triangulations(把3D模型的三角剖分曲面数据处理为条带)
    1. 把每一个三角形映射为图的顶点
    2. 注意,所谓的‘strip’就是指2个三角形共享一条边,以‘边共享’作为图的顶点邻接关系
    3. 则本优化问题相当于求图的最短Hamiltonian路径。NP-complete
    4. 启示式:to find the largest strip to peel off next
  7. 哈希与字符串
    1. 冲突消解:chaining / open-addressing?(实际上开地址法遇到桶溢出是非常不划算的,但chaining有一般的链表维护开销)
    2. 字符串匹配的Rabin-Karp算法:假设2个字符串同样,则它们的长度和hash应该相等
      1. 注意。这里源字符串的hash计算是‘滚动式’的——‘滑动窗体hash’(要求hash函数基于线性加权取模)
        1. 取模为某素数M,则hash碰撞的可能性为1/M=〉随机算法?
  8. Specialized DS
  9. War Story:String 'em Up
    1. DNA序列:(A, C, T, G)*
    2. sequencing by hybridization(SBH):确信k-子串集,构造可能的2k-子串集,如AC, CA, CC => ACCA可能。CAAC不可能
      1. BST --> hashtable -> 后缀树 -> 压缩后缀树 ->

排序和搜索

  1. 排序的应用:求点集的凸包(Convex Hull)
    1. 按x从左到右排序。然后依次插入目标凸包集合(维护一个上下高度范围约束?这里似乎没讲清楚)
  2. HeapSort
    1. 能够视为插入排序的变形:pg_insert -> bubble_up(当然,假设反方向的话能够bubble_down)
    2. 用最小堆来排序:extract_min,这时堆顶出现了空洞,把最后一个(right-most)元素补进来,bubble_down
    3. 注意,这里初始构造堆能够bubble_up也能够bubble_down,但中间过程heap_adjust总是bubble_down
      1. 考虑到假设使用最小堆排序的话,每次取出的当前最小元素放哪里?这样看来还是用最大堆排序更合适点,作者的代码:
        heapsort(item_type s[], int n){//这里使用了额外的空间,假设用最大堆就能够in-place排序了
        int i;
        priority_queue q;
        make_heap(&q, s, n);
        for(i=0; i<n; ++i) s[i] = extract_min(&q);
        }
    4. 更快的建堆:for(int i=q->n; i>=1; --i) bubble_down(q,i); //搞不明确作者这里的priority_queue内部元素下标为什么从1開始?
    5. Stop and Think:怎样O(k)推断堆中第k小的元素是否>=指定x?=> 转化为在层次1..k范围内统计<x的元素计数!

      1. 低效的做法:extract_min k次以得到kth smallest;
        1. 第k小的元素的层次不可能大于k:在此范围内线性扫描?
      2. 递归的compare_heap:传入当前已经>=x的计数count。返回改动累计后的新count
        1. 结束条件:if( count<=0 || i> q->n) return count;
        2. 注意,这里count概念的特殊传參是一个技巧
  3. War Story:Give me a Ticket on an Airplane(最廉价的飞机票路径规划?)
    1. X+Y排序(X,Y是有序列表,求全部X+Y的有序列表)
    2. 即时的增量计算结果 => 假设要全局最优,则须要维护一个理论上没上限的缓存(考虑到反复值的情况)
    3. 问题是实际情况并不满足这样的简单的约束。
  4. MergeSort
    1. 这里作者再次使用了额外的queue
  5. QuickSort(注意理解这里的partition算法:‘滚动的>=区间’)
    int partition(item_type s[], int l, int h){
    int i; /* counter */
    int p; /* pivot element index */
    int firsthigh; /* divider position for pivot element */
    p = h;
    firsthigh = l;
    for (i=l; i<h; i++)
    if (s[i] < s[p]) {
    swap(&s[i],&s[firsthigh]);
    firsthigh ++;
            }
    swap(&s[p],&s[firsthigh]);
    return(firsthigh);
    }
  6. 桶排序
  7. War Story:Skiena for the Defense
    1. 外排序:多路归并
  8. 二分查找与相关算法
    1. O(lgn)统计同样元素的出现次数:BS去除if(A[middle]==key)return middle;一句。分别用<和>=作为comparator。得到上下界
      1. 前提是元素已经有序?嗯。不是非常稀奇
    2. 单側二分查找:相同不稀奇
  9. 分治
    1. master定理:T(n)=aT(n/b)+f(n),注意loga/logb与f(n)次数的关系

图遍历

  1. 2种表示:邻接矩阵、邻接表
  2. War Story:I was a Victim of Moore's Law
    1. To make a program run faster, just wait(哈哈)
  3. War Story:Getting the Graph(注意数据集的内在关联性)
  4. 顶点状态:
    1. undiscovered
    2. discovered(已訪问过。但它的incident边没有所有处理)
    3. processed
  5. 3个回调时机:
    1. process_vertex_early(v)
    2. process_edge(v, w)
    3. process_vertex_late(v)
  6. BFS
    1. 样例:推断是否可顶点2-着色(即是否是二部图), process_edge(v, w): //这里color[v]默认着色第一种
      1. if( color[v]==color[w]) bipartite=FALSE;
      2. color[w] = complement( color[w] );
  7. 无向图上的DFS
    1. 边的区分:树边(parent->child)、回边(child->parent)、cross边(到非parent节点的边)
    2. 额外的entry_time[v]和exit_time[v]?
    3. 无向图找圈:if( parent[v]!=w )(回边,if写法有点怪异) //注意。在dfs框架里。parent[w]==v 或 ‘w已訪问过但未处理’
    4. Articulation顶点(割点?去除会导致图不再弱连通)
      1. reachable_ancestor[v] v的最早(?)可达祖先,初始reachable_ancestor[v]=v
      2. 核心推断条件:if( class(v,y)==BACK_EDGE && parent[v]!=w ) //不是直接回到parent的回边?
        1. && if( entry_time[w] < entry_time[ reachable_ancestor[v] ] ) ==> update reachable_ancestor[v]=w
        2. 注意,这里的&& if实际上是为了排除可能的CROSS边情形(此时w还未被訪问到!

      3. 3种割点:Root- Bridge- Parent-
        1. OK。还须要在process_vertex_late(v)里做进一步推断(略。-_-)
        2. 割边(桥)=〉无。‘边-biconnected’
    5. p178 Forward边?不就是Tree边嘛。是不是有误?
  8. 有向图上的DFS
    1. 新的边分类:edge_classification(v,w):
      1. if( v==parent[w] ) return TREE;
      2. if( discovered[w] && !processed[w] ) return BACK;
      3. if( processed[w] && entry_time[w]>entry_time[v] ) return FORWARD; //(在遍历v的其余子女节点时已经作为v的后代节点被訪问过)<== BFS不可能存在FORWARD边
      4. if( processed[w] && entry_time[w]<entry_time[v] ) return CROSS; //要求w已訪问过(但未处理)
    2. 拓扑排序(前提:无BACK边)
      1. Labeling vertices in the reverse order that they are marked processed(?)
    3. 强连通分量scc(关键是这里的分情况讨论)
      1. ‘active’栈
      2. low[v]:v所在连通分量最早的节点代表
      3. scc[v]:v所在连通分量的编号
      4. 分析:low[v]不一定是v的祖先,可能相应CROSS边。从一个scc到还有一个的CROSS边无助于连接2个scc;忽略FORWARD边
      5. 一个新的scc被发现,仅仅要low[v]==v

加权图

  1. MST
    1. Prim
    2. Kruskal
      1. Union-Find
  2. War Story:Nothing but Nets
    1. break big nets into small nets?
    2. L^infinite metric
  3. 最短路径
    1. Dijkstra(与Prim仅仅差3行代码):... is how they rate the desirablility of each outside vertex
    2. 全源:Floyd,3重循环k,i,j
  4. War Story:Dialing for Documents(用电话号码0-9来输入字母a-z)
    1. 实际上。这个问题似乎有个缺陷,容错性不佳(未考虑用户可能输入有错误的情形。正如设计一个容错性高的parser...)
    2. N-Gram,语句级别反歧义:归约到最短路径,DAG上的最短路径DP称为Viterbi算法
  5. 网络流与最大二部图匹配(Bipartite Matching)
    1. 后者归约到前者(创建2个连接到全部顶点的source/sink节点)
    2. ‘residual flow graph’
      1. s-t间的最大流=最小割(注意,割指的是删除后使得s-t不连通的边集)
      2. netflow(增广路径法):bfs path_volume augment_path find_edge
        1. 暂略(TODO)
  6. 设计图而非算法(归约)
    1. Stop and Think:把平面上的矩形划分为子集和,当中每一个集合中的矩阵互不重叠
      1. 把每一个矩形作为图中节点,‘重叠’关系视为边,则可转化为最小顶点着色into独立集问题
    2. Stop and Think:DOS长文件名称重命名问题
      1. 注意:生成每一个长文件名称的全部简写形式,然后寻找原文件名称与简写之间的‘(最大)二部图匹配’
    3. Stop and Think:OCR应用中的字符区域segmentation问题
      1. 转化为最短路径

组合搜索与启示式

  1. 回溯
    1. backtrack:注意for(i : candidates){ make_move(); 递归 unmake_move(); }部分(~permutation)
      1. ncandidates = construct_candidates(A, k, n, candidates) //构造第k步的候选移动集合
        1. candidates_set实际上是backtrack每步递归时产生的局部变量。因此backtrack深层嵌套递归时栈开销非常大
    2. 构造(枚举){1...n}的全部子集
      1. 第k个元素仅仅有“选中/不选中”2个选择,因此candidates可用bool[2]数组表示
  2. 剪枝
    1. 添加约束条件推断
    2. 利用对称性引入‘序’(也可理解为做复杂事情时的特殊的先后步骤)
  3. Sudoku
    1. 候选集优先顺序(假设目标是尽快找到一个可行解的话):选择约束最多的
    2. ‘Look ahead’?这个详细怎么做有点难度(alpha-beta剪枝似乎也可归类为本策略?)
  4. War Story:Covering Chessboards
  5. 启示式搜索
    1. 搜索空间表示 & 代价函数
    2. 随机採样(Monte carlo)
      1. 有可能以均匀的概率发现目标解。从而在平均意义上更快地找到第一个解?
      2. p250 Uniformly生成随机数:i=random(1,n-1); j=random(i+1,n); 注意i<j
    3. 梯度下降(局部搜索,hill_climbing)
      1. 某种理论根据:解可能在局部聚集(great coherence)
    4. 模拟退火
      1. 暂略(TODO)
  6. (TODO)War Story:Only it is Not a Radio
  7. (TODO)War Story:Annealing Arrays
  8. 其它启示式
    1. GA
    2. PA(并行)
  9. 并行算法
  10. War Story:Going Nowhere Fast:均衡负载的重要性!
  11. 练习
    1. 7-1 Multiset的排列算法?
    2. *7-6 turnpike重建:已知n个数的两两和。反向求这n个数(靠,这个问题太有难度了!

DP

  1. 缓存 vs 计算
    1. 二项式系数:C(n,k) = C(n-1, k-1) + C(n-1, k) <== 怎样依据这个递推公式编写DP代码?
  2. 近似字符串匹配(编辑距离:替换/插入/删除)
    1. me:能够从LCS上扩展思考(只是Skiena的思路是先有‘编辑距离’。然后约简到LCS)
    2. 递归的string_compare:
      1. 注意,求编辑距离时2个字符串s,t可觉得是对称的,也能够看作是“从s变到t”
      2. opt[MATCH] = string_compare(s,t,i-1,j-1) + cost_replace(s[i], t[j])
      3. opt[INSERT] = string_compare(s,t,i,j-1) + cost_delete(t[j])
      4. opt[DELETE] = string_compare(s,t,i-1,j) + cost_delete(s[i]) #注意,这里下标INSERT/DELETE的说法不正确称
      5. lowest_cost=min( opt[MATCH / INSERT / DELETE] )
    3. reconstruct_path:由于之前的lowest_cost仅仅选择一个。所以这里的反向重建路径也仅仅有一条。
  3. LIS(最长递增子序列)
    1. 假设是‘最长的连续递增子序列(run)’,则能够简单地O(n)扫描
    2. 须要知道lis(s[1..n])的extent =〉l(0)=0, l(i)=max_{0<j<i} l(j) + 1, where s[j]< s[i](嗯?)
    3. DP:须要存储l(i)及相应的前驱p(i)
    4. 能够用LCS求LIS
  4. War Story:Evolution of the Lobster
    1. 最优Morphing:关键在于定义代价函数
  5. Partition问题
    1. 一维线性分组:定义划分的代价为全部划分的最大元素和(此值越小,说明负载划分越均匀!)
  6. 解析CFG(CYK算法)
    1. CFG的Chomsky规范形式:仅仅有X->YZ、X->a两种形式的规则
    2. 定义M[i,j,X]=true。iff S[i..j]可被X生成 =〉O(n^3)解析
    3. 最小加权的Triangulation(此问题与CFG解析有类似的DP递推公式)
  7. DP的限制:TSP
  8. *War Story:What's Past is Prolog
    1. 必须保证固定的规则顺序这一约束条件成了可以DP的正确性保证
  9. War Story:条形码文本压缩
  10. 练习
    1. 8-3 最长公共子字符串:怎样从编辑距离得到?更简单的不依赖于DP的算法??
    2. *8-10 整数划分(分为2个和相等的子集)
    3. *8-15 扔鸡蛋
    4. *8-21 最大连续子序列和
    5. *8-22 给定指定字母的*运算表,*既不交换也不结合(都不是一个群?),求给定字母序列的加()方案,使得结果运算=a

Intractable问题与近似算法

  1. 问题与归约
  2. 算法归约
  3. 基础困难归约(SAT?)
  4. 可满足性(SAT)
  5. 创造性归约
    1. 3-SAT => IP
    2. 3-SAT => Vertex Cover(比前一个更困难?)
  6. 困难证明的艺术
    1. 源问题:3-SAT 整数划分 顶点覆盖 哈密顿回路
  7. War Story:Hard Against the Clock
    1. 把3-SAT归约到‘带条件赋值程序的不等价性’(问是否存在一个初始赋值方案,使得2个程序对某些变量的终于状态同样)
  8. War Story:And Then I Failed
    1. 顶点覆盖 => ‘单连通子图’(图的有向边(弧)子集,使得随意2顶点之间存在至多1条路径)
  9. P vs. NP
  10. 处理NP-complete问题
    1. 近似顶点覆盖
    2. 欧拉TSP
    3. 最大DAG子图
    4. Set Cover
  11. Notes
    1. 最好的介绍NP全然理论的书:Garey and Johnson: Computers and Intractability
  12. 练习
    1. 9-9 MST带约束‘全部顶点度数<=k’:NP-hard
      1. 假设要求‘存在某顶点度数>k’,则存在有效算法
    2. 9-10 稠密子图 is NP-complete
    3. 9-13 Hitting Set is NP-complete
    4. 9-16 边加权的最长路径(顶点至多訪问1次) is NP-complete
    5. 9-20 Feedback Vertex Set is NP-complete

如何设计算法

数据结构

  1. 字典
    1. 搜索树:‘局部旋转’
      1. p370 AVL和2-3树似乎过时了,红黑树更流行;伸展树同意更快的搜索(数据訪问的局部性原理)
      2. B-树:Cache-oblivious数据结构?
  2. 优先队列
    1. Bouned高度的优先队列(即Sorted-Array-with-ListElement)
    2. 复杂的Fibonacci & pairing堆:用于加速decrease-key操作(?)
    3. 双端优先队列?
  3. 后缀树、后缀数组
    1. O(n)空间优化:压缩‘简单路径’。边上标记一个连续子序列s[i..j]
    2. O(n)的后缀树构造算法是非平庸的(作者没细讲)
    3. 后缀数组:每一个元素仅仅须要存储字符串后缀的起始位置指针,因此空间开销O(n)。但初始构造须要排序
      1. space/time高效的算法直接构造后缀数组???
    1. Hypergraph
  4. 集合
    1. Bloom filters
  5. Kd树

数值问题

  1. 解线性方程
  2. *带宽归约:全部顶点都在一条直线上时最小化最大的顶点距离?(这个问题描写叙述太含糊不清了)
  3. 矩阵乘法:略
  4. Determinants and Permanents:略
  5. 有约束和无约束的优化:略
  6. LP
    1. p414 LP is P-complete under log-space归约,这使得不可能有一个NC平行算法(???)
  7. 随机数生成:略
  8. 因式分解与素性測试:略
  9. 随意精度算术:略
  10. 背包(Knapsack):略
  11. DFT:略

组合问题

  1. 排序
  2. 搜索
  3. 中值与选择
    1. 嗯,得复习一下那个O(n)的Selection算法!

  4. 生成排列
    1. n!
    2. Rank(p) Unrank(m,n)
    3. 随机排列:为什么是swap(A[i], A[Random[i..n]]) #see TAOCP?
    4. C++ STL:next/prev_permutation
  5. 生成子集(组合)
    1. 2^n
      1. 文法序(surprisingly difficult?)
      2. 格雷码(记不得细节了?)
      3. 二元计数(easy)
  6. 生成划分
    1. 整数划分
    2. 集合划分
      1. restricted growth function?,比如0,1,1,2,0,3,1定义了划分{{1,5},{2,3,7},{4},{6}}
    3. Young Tableaux(杨辉三角?好像不是)
  7. 生成图
  8. Calendrical计算
  9. 作业调度
    1. *job-shop调度:可建模为bin-packing
  10. 可满足性

图问题:P

  1. 连通分量
  2. 拓扑排序
    1. ?it is #P-complete to count the num of linear extensions of a partial order
      1. #P includes NP
  3. MST
    1. Boruvka's算法:能够看作Kruskal的‘顶点并行’加速版?
    2. Prim在稠密图上更快(O(n^2)),而Kruskal在稀疏图上更快(O(mlgm))
      1. 使用高级数据结构,Prim可实现到O(m+nlgn);Prim+pairing堆将是最快的实际选择...
  4. 最短路
    1. p493 最快的单源最短路径:Dijkstra+Fibonacci堆?
  5. 传递闭包与归约
  6. 匹配
  7. 欧拉圈/中国邮递员
    1. p504 de Bruijn序列:(略)
  8. 边与顶点连通
  9. 网络流
    1. 增广路径法
    2. Preflow-pushing法
  10. 美观地绘制图
  11. 绘制树
  12. 平面图检測与嵌入
    1. *每一个平面图都是稀疏的
    2. 尝试将K_5 - e绘制为平面图?

图问题:困难的

  1. 独立集
  2. 顶点覆盖
  3. TSP
  4. 哈密顿回路
  5. 图划分
    1. p543 ‘Spectral partitioning’?
  6. 顶点着色
    1. 每一个平面图可用至多6种颜色着色
    2. 求G的chromatic数是NP全然的
  7. 边着色
    1. Viking:设图的最大顶点度=D。则G可(D+1)-边着色
  8. 图同态(Isomorphism)
    1. 没有找到有效的多项式算法,但也无法证明它是NP-全然的。
  9. Steiner树
  10. Feedback Edge/Vertex Set

计算几何

  1. 鲁棒的几何原语
  2. 凸包
  3. 三角剖分(与Voronoi是对偶问题)
  4. Voronoi
  5. 近期邻搜索
  6. 范围搜索
  7. 点定位
  8. 相交检測
  9. Bin Packing
  10. 中轴转换
  11. 多边形剖分
    1. p602 凸形分解的Hertel-Mehlhorn启示式?
  12. Simplifying Polygons(不明确在说什么。计算几何的很多问题实际上是不够精确的)
  13. 形状相似度
  14. 运动规划
  15. 维护Line Arrangements
  16. Minkowski Sum

集合与字符串

  1. 集合覆盖
    1. p622 It is instructive to show how to model vertex cover as an instance of set cover
      1. Hitting set is dual to set cover.
  2. Set Packing
  3. 字符串匹配
    1. Aho-Corasick:把模式表示为状态机
    2. 经典的KMP、BM
  4. 近似字符串匹配
  5. 文本压缩
    1. BWT(Burrows-Wheeler转换)
    2. LZW, Lempel-Ziv
    3. 实际应用:gz --> bz2 --> xz?
  6. 加密
  7. FSM最小化
    1. NFA转换为DFA:PSPACE-hard
    2. p648 对长度为m的模式,相应的DFA状态可能指数增长(因为那个* Kleene闭包),但匹配仅仅须O(n)
  8. LCS(子串1/子序列2)
    1. O(n),后缀树:把叶子节点标记为输入的哪个字符串,然后做一个DFS求得具有全部字符串作为后代的deepest节点
    2. O(nm)。编辑距离的特例(禁止替换操作):edit_distance(P,T)=n+m-2|lcs(P,T)|
      1. 反向回溯:Max(n,m)
      2. LIS:=LCS(s, sort(s))
        1. 求1..n的某排列的lis?
      3. 多个字符串(多序列对齐):O((2n)^k),NP-complete
        1. wait。最长公共子序列事实上不就是diff算法的核心嘛
      4. 2个随机的n长字符串的LCS的期望值?
  9. Shortest Common Superstring, SCS
    1. 可应用于稀疏矩阵压缩?
    2. DNA序列组装?
    3. 可归约到(不正确称的)TSP,“much harder”
    4. 贪心启示式:近似的SCS。可能2倍的最优。但不会坏于3.5倍最优
    5. 增加‘禁则’:NP-complete

算法资源

  1. Great artists steal.
  2. C++程序库:LEDA(这个似乎是商业授权的)CGAL Boost.Graph GOBLIN Netlib Standford-GraphBase Combinatorica 





本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/5236395.html,如需转载请自行联系原作者

网友评论

登录后评论
0/500
评论
技术mix呢
+ 关注