一.基本算法:
枚举. (poj1753,poj2965)
贪心(poj1328,poj2109,poj2586)
递归和分治法.
递推.
构造法.(poj3295)
模拟法.(poj1068,poj2632,poj1573,poj2993,poj2996)
二.图算法:
图的深度优先遍历和广度优先遍历.
最短路径算法(dijkstra,bellman-ford,floyd,heap+dijkstra)
(poj1860,poj3259,poj1062,poj2253,poj1125,poj2240)
最小生成树算法(prim,kruskal)
(poj1789,poj2485,poj1258,poj3026)
拓扑排序 (poj1094)
二分图的最大匹配 (匈牙利算法) (poj3041,poj3020)
最大流的增广路算法(KM算法). (poj1459,poj3436)
三.数据结构.
串 (poj1035,poj3080,poj1936)
排序(快排、归并排(与逆序数有关)、堆排) (poj2388,poj2299)
简单并查集的应用.
哈希表和二分查找等高效查找法(数的Hash,串的Hash)
(poj3349,poj3274,POJ2151,poj1840,poj2002,poj2503)
哈夫曼树(poj3253)
堆
trie树(静态建树、动态建树) (poj2513)
四.简单搜索
深度优先搜索 (poj2488,poj3083,poj3009,poj1321,poj2251)
广度优先搜索(poj3278,poj1426,poj3126,poj3087.poj3414)
简单搜索技巧和剪枝(poj2531,poj1416,poj2676,1129)
五.动态规划
背包问题. (poj1837,poj1276)
型如下表的简单DP(可参考lrj的书 page149):
E[j]=opt{D+w(i,j)} (poj3267,poj1836,poj1260,poj2533)
E[i,j]=opt{D[i-1,j]+xi,D[i,j-1]+yj,D[i-1][j-1]+zij} (最长公共子序列) (poj3176,poj1080,poj1159)
C[i,j]=w[i,j]+opt{C[i,k-1]+C[k,j]}.(最优二分检索树问题)
六.数学
组合数学:
1.加法原理和乘法原理.
2.排列组合.
3.递推关系.
(POJ3252,poj1850,poj1019,poj1942)
数论.
1.素数与整除问题
2.进制位.
3.同余模运算.
(poj2635, poj3292,poj1845,poj2115)
计算方法.
1.二分法求解单调函数相关知识.(poj3273,poj3258,poj1905,poj3122)
七.计算几何学.
几何公式.
叉积和点积的运用(如线段相交的判定,点到线段的距离等). (poj2031,poj1039)
多边型的简单算法(求面积)和相关判定(点在多边型内,多边型是否相交)
(poj1408,poj1584)
凸包. (poj2187,poj1113)
中级(校赛压轴及省赛中等难度):
一.基本算法:
C++的标准模版库的应用. (poj3096,poj3007)
较为复杂的模拟题的训练(poj3393,poj1472,poj3371,poj1027,poj2706)
二.图算法:
差分约束系统的建立和求解. (poj1201,poj2983)
最小费用最大流(poj2516,poj2516,poj2195)
双连通分量(poj2942)
强连通分支及其缩点.(poj2186)
图的割边和割点(poj3352)
最小割模型、网络流规约(poj3308)
三.数据结构.
线段树. (poj2528,poj2828,poj2777,poj2886,poj2750)
静态二叉检索树. (poj2482,poj2352)
树状树组(poj1195,poj3321)
RMQ. (poj3264,poj3368)
并查集的高级应用. (poj1703,2492)
KMP算法. (poj1961,poj2406)
四.搜索
最优化剪枝和可行性剪枝
搜索的技巧和优化 (poj3411,poj1724)
记忆化搜索(poj3373,poj1691)
五.动态规划
较为复杂的动态规划(如动态规划解特别的旅行商TSP问题等)
(poj1191,poj1054,poj3280,poj2029,poj2948,poj1925,poj3034)
记录状态的动态规划. (POJ3254,poj2411,poj1185)
树型动态规划(poj2057,poj1947,poj2486,poj3140)
六.数学
组合数学:
1.容斥原理.
2.抽屉原理.
3.置换群与Polya定理(poj1286,poj2409,poj3270,poj1026).
4.递推关系和母函数.
数学.
1.高斯消元法(poj2947,poj1487, poj2065,poj1166,poj1222)
2.概率问题. (poj3071,poj3440)
3.GCD、扩展的欧几里德(中国剩余定理) (poj3101)
计算方法.
1.0/1分数规划. (poj2976)
2.三分法求解单峰(单谷)的极值.
3.矩阵法(poj3150,poj3422,poj3070)
4.迭代逼近(poj3301)
随机化算法(poj3318,poj2454)
杂题(poj1870,poj3296,poj3286,poj1095)
七.计算几何学.
坐标离散化.
扫描线算法(例如求矩形的面积和周长并,常和线段树或堆一起使用)
(poj1765,poj1177,poj1151,poj3277,poj2280,poj3004)
多边形的内核(半平面交)(poj3130,poj3335)
几何工具的综合应用.(poj1819,poj1066,poj2043,poj3227,poj2165,poj3429)
高级(regional中等难度):
一.基本算法要求:
代码快速写成,精简但不失风格
(poj2525,poj1684,poj1421,poj1048,poj2050,poj3306)
保证正确性和高效性. poj3434
二.图算法:
度限制最小生成树和第K最短路. (poj1639)
最短路,最小生成树,二分图,最大流问题的相关理论(主要是模型建立和求解)
(poj3155, poj2112,poj1966,poj3281,poj1087,poj2289,poj3216,poj2446
最优比率生成树. (poj2728)
最小树形图(poj3164)
次小生成树.
无向图、有向图的最小环
三.数据结构.
trie图的建立和应用. (poj2778)
LCA和RMQ问题(LCA(最近公共祖先问题) 有离线算法(并查集+dfs) 和 在线算法(RMQ+dfs)).(poj1330)
双端队列和它的应用(维护一个单调的队列,常常在动态规划中起到优化状态转移的目的). (poj2823)
左偏树(可合并堆).
后缀树(非常有用的数据结构,也是赛区考题的热点).(poj3415,poj3294)
四.搜索
较麻烦的搜索题目训练(poj1069,poj3322,poj1475,poj1924,poj2049,poj3426)
广搜的状态优化:利用M进制数存储状态、转化为串用hash表判重、按位压缩存储状态、双向广搜、A*算法. (poj1768,poj1184,poj1872,poj1324,poj2046,poj1482)
深搜的优化:尽量用位运算、一定要加剪枝、函数参数尽可能少、层数不易过大、可以考虑双向搜索或者是轮换搜索、IDA*算法. (poj3131,poj2870,poj2286)
五.动态规划
需要用数据结构优化的动态规划.(poj2754,poj3378,poj3017)
四边形不等式理论.
较难的状态DP(poj3133)
六.数学
组合数学.
1.MoBius反演(poj2888,poj2154)
2.偏序关系理论.
博奕论.
1.极大极小过程(poj3317,poj1085)
2.Nim问题.
七.计算几何学.
半平面求交(poj3384,poj2540)
可视图的建立(poj2966)
点集最小圆覆盖.
对踵点(poj2079)
⑵ 那些经典算法:AC自动机
第一次看到这个名字的时候觉得非常高级,深入学习就发现,AC就是一种多模式字符串匹配算法。前面介绍的BF算法,RK算法,BM算法,KMP算法都属于单模式匹配算法,而Trie树是多模式匹配算法,多模式匹配算法就是在一个主串中查找多个模式串,举个最常用的例子,比如我们在论坛发表评论或发帖的时候,一般论坛后台会检测我们发的内容是否有敏感词,如果有敏感词要么是用***替换,要么是不让你发送,我们评论是通常是一段话,这些敏感词可能成千上万,如果用每个敏感词都在评论的内容中查找,效率会非常低,AC自动机中,主串会与所有的模式串同时匹配,这时候就可以利用AC自动机这种多模式匹配算法来完成高效的匹配,
AC自动机算法是构造一个Trie树,然后再添加额外的失配指针。这些额外的适配指针准许在查找字符串失败的时候进行回退(例如在Trie树种查找单词bef失败后,但是在Trie树种存中bea这个单词,失配指针会指向前缀be),转向某些前缀分支,免于重复匹配前缀,提高算法效率。
常见于IDS软件或病毒检测软件中病毒特征字符串,可以构建AC自动机,在这种情况下,算法的时间复杂度为输入字符串的长度和匹配数量之和。
假设现有模式字符串集合:{abd,abdk, abchijn, chnit, ijabdf, ijaij} 构建AC自动机如下:
说明:
1)当前指针curr指向AC自动机的根节点:curr=root。
2)从文本串中读取(下)一个字符。
3)从当前节点的所有孩子节点中寻找与该字符匹配的节点:
4)若fail == null,则说明没有任何子串为输入字符串的前缀,这时设置curr = root,执行步骤2.
若fail != null,则将curr指向 fail节点,指向步骤3。
理解起来比较复杂,找网上的一个例子,假设文本串text = “abchnijabdfk”。
查找过程如下:
说明如下:
1)按照字符串顺序依次遍历到:a-->b-->c-->h ,这时候发现文本串中下一个节点n和Trie树中下一个节点i不匹配,且h的fail指针非空,跳转到Trie树中ch位置。
注意c-->h的时候判断h不为结束节点,且c的fail指针也不是结束节点。
2)再接着遍历n-->i,发现i节点在Trie树中的下一个节点找不到j,且有fail指针,则继续遍历,
遍历到d的时候要注意,d的下一个匹配节点f是结束字符,所以得到匹配字符串:ijabdf,且d的fail节点也是d,且也是结束字符,所以得到匹配字符串abd,不过不是失败的匹配,所以curr不跳转。
先将目标字符串插入到Trie树种,然后通过广度有限遍历为每个节点的所有孩子节点找到正确的fail指针。
具体步骤如下:
1)将根节点的所有孩子节点的fail指针指向根节点,然后将根节点的所有孩子节点依次入队列。
2)若队列不为空:
2.1)出列一个字符,将出列的节点记为curr,failTo表示curr的
fail指针,即failTo = curr.fail 。
2.2) 判断curr.child[i] == failTo.child[i]是不是成立:
成立:curr.child[i].fail = failTo.child[i]
因为当前字符串的后缀和Tire树的前缀最长部分是到fail,
且子字符和failTo的下一个字符相同,则fail指针就是
failTo.child[i]。
不成立: 判断failTo是不是为null是否成立:
成立: curr.child[i].fail = root = null。
不成立: failTo = failTo.fail 继续2.2
curr.child[i]入列,再次执行步骤2)。
3)队列为空结束。
每个结点的fail指向的解决顺序是按照广度有限遍历的顺序完成的,或者说层序遍历的顺序进行,我们根据父结点的fail指针来求当前节点的fail指针。
上图为例,我们要解决y节点的fail指针问题,已经知道y节点的父节点x1的fail是指向x2的,根据fail指针的定义,我们知道红色椭圆中的字符串序列肯定相等,而且是最长的公共部分。依据y.fail的含义,如果x2的某个孩子节点和节点y表示的表示的字符相等,y的fail就指向它。
如果x2的孩子节点中不存在节点y表示的字符。由于x2.fail指向x3,根据x2.fail的含义,我们知道绿色框中的字符序列是相同的。显然如果x3的某个孩子和节点y表示字符相等,则y.fail就指向它。
如果x3的孩子节点不存在节点y表示的字符,我们重复这个步骤,直到xi的fail节点指向null,说明我们达到顶层,只要y.fail= root就可以了。
构造过程就是知道当前节点的最长公共前缀的情况下,去确定孩子节点的最长公共前缀。
下图中,每个节点都有fail虚线,指向根节点的虚线没画出,求图中c的孩子节点h的fail指向:
原图中,深蓝色的框出来的是已经确定fail指针的,求红色框中h节点的fail指针。
这时候,我们看下h的父亲节点c的fail指针指向,为ch中的c(这表示abc字符串的所有后缀bc和c和Trie树的所有前缀中最长公共部分为c),且这个c节点的孩子节点中有字符为h的字符,所以图中红色框中框出的h节点的fail指针指向 ch字符串中的h。
求红色框中i的fail指针指向,上图中,我们可以看到i的父亲节点h的指向为ch中的h,(也就是说我们的目标字符串结合中所有前缀和字符序列abch的所有后缀在Trie树中最长前缀为ch。)我们比较i节点和ch中的h的所有子节点,发现h只有一个n的子节点,所以没办法匹配,那就继续找ch中h的fail指针,图中没画出,那么就是它的fail指针就是root,然后去看root所有子节点中有没有和i相等的,发现最右边的i是和我们要找的i相等的,所以我们就把i的fail指针指向i,如后面的图。
⑶ DPDK ACL算法介绍
DPDK提供了三种classify算法:最长匹配LPM、精确匹配(Exact Match)和通配符匹配(ACL)。
其中的ACL算法,本质是步长为8的Multi-Bit Trie,即每次可匹配一个字节。一般来说步长为n时,Trie中每个节点的出边为2^n,但DPDK在生成run-time structures时,采用DFA/QRANGE/SINGLE这几种不同的方式进行数据结构的压缩,有效去除了冗余的出边。本文将为大家介绍ACL算法的基本原理,主要内容包括:trie树的构造、运行时的node array生成和匹配原理。对于ACL接口的使用,参考DPDK的官方文档即可。
ACL规则主要面向的是IP流量中的五元组信息,即IP/PORT/PROTO,算法在这个基础上进行了抽象,提供了三种类型的匹配区域:
熟悉这三种类型的使用后,完全可以用它们去匹配网络报文的其它区域,甚至将其应用到其它场景中。
具体来说,rte_acl_field_def有5个成员:type、size、field_index、input_index、offset。
如果要深入理解算法,可以思考这几个字段的意义,或者换个角度来看:
对于规则的定义,要注意如下两点:
比如定义了5个field,那么请给出每一个的具体定义:
像field[1]中IP和mask都为0,表示匹配所有的IP地址;field[3]中range由0到65535,表示匹配所有。类似这样的全匹配一定要显示的定义出来,因为如果不明确定义,这些字段的值取决于编译器的,最后编译的ACL规则很可能与原有设想存在偏差。
如果在规则中,对于某个field不进行限制,对于不同type的field,规则书写时有一定差异:
对于BITMASK和MASK类型,全0代表匹配所有,如上例中的field[0]、field[1];
对于RANGE,则按照上述field[3]中的形式定义。
规则定义好后,会转换为trie树并最终合并到一起。
实际处理过程中,build_trie函数会自底向上的将rule中的每个field转换为node,然后将这些node合并生成这条rule的trie,最后将这个trie与已有的trie进行merge,最终生成整个rule set的trie。
tire由node组成,其主要数据成员如下:
node中values成员用于记录匹配信息,ptrs则用于描述node的出边,用于指向转换后的node。
values采用bitmap进行压缩,其数据结构为struct rte_acl_bitset values; 一个byte取值范围是[0,255],可通过256个bit位来进行对应,并实现byte值的快速查找:即通过第x位的bit值是否为1来判断是否包含数值x(0 <= x < 256)。
num_ptrs用于描述出边数目,ptrs即为实际的出边,它记录了其匹配值values和匹配后的节点指针。
match_flag和mrt则用于记录匹配结果,trie树中叶子节点一定是记录匹配结果的节点。
trie树其详细结构比较复杂,这里将其结构进行简化,如下所示:
上图的trie树有4个node,通过ptrs进行指向,values字段为匹配值的bitmap表示,为了表述的简洁,后续会采用simple的方式进行描述。
在trie simple中,实心节点表示匹配节点,边上的数字代表匹配值(为便于阅读,采用实际值而不再是bitmap形式),…代表其它匹配值。
不同type的field,转换为node的方式会有所不同。
目前提供的3种类型:BITMASK描述一个byte的匹配,支持mask模式;MASK用于描述4个byte的匹配,支持mask模式;RANGE描述2个byte的匹配,此时mask表示上限。
field到node的转换,见build_trie中的for循环,具体转换函数则参考:
对于BITMASK,如{.value.u8 = 6, .mask_range.u8 = 0xff,},它最后的转换形式如下:
构造field的node时,总会在结尾添加一个空的end节点,最后一个field除外(它是match node)。在for循环中每完成了一个field的解析后,会将其合并到root中,从而生成这个rule的trie。
合并前,也会先构造一个空的end node(见build_trie函数中,while循环下的root创建),让它与field构成的node头合并,因为不相交,所以merge时会将匹配信息合并到end node并释放原有的头,并将field链的end节点返回(保存到end_prev中),下次合并时,就用此end节点与新的node头合并。
循环遍历完所有的field后,这些node就串联起来了,构成这个rule的trie。
对于多个rule,每次构造完成后会merge到整体的trie中。
这里详细介绍下merge算法原理,其实仔细阅读acl_merge_trie函数的注释即可。
对于node A和node B的merge, acl_merge_trie函数返回一个节点,这个节点指向它们路径的交集。
这里给出三个例子用于展示merge前后的变化。为了减少状态点,构造rte_acl_field_def如下:
示例1:
acl_rules[1]为trie A,acl_rules[0]对应trie B,最终trie B合并到trie A上,具体如下:
1和1’合并时,因为level为0,所以1’直接合并到1中;
4和4’合并时,因为节点无交集,所以创建新节点c1(node 4的拷贝),并将4'上的边拷贝到c1中。
示例2,rule类别相同,但优先级不同:
acl_rules[1]为trie A,acl_rules[0]对应trie B,最终trie B合并到trie A上,具体如下:
6和6’是match node,类别相同,且6的优先级为2大于6’的优先级。
6和6’合并时,直接返回6。而前面创建的新节点,如d1,已包含5’的所有边(非ACL_INTERSECT_B),所以最终返回5,free d1。
同理依次往上回溯,a4,b3,c2,也依次被释放,最终merge的trie即为原来的trie A。
示例3,rule类别不同,优先级相同:
acl_rules[1]为trie A,acl_rules[0]对应trie B,最终trie B合并到trie A上,具体如下:
6和6’是match node,因为类别不同,所以最终创建了新node e1,这也导致示例3和示例2最终merge结果的不同。
合并是一个递归的过程,逆向思考构造过程会有助于理解算法。另外,在build_trie之前会sort_rule,匹配范围更大的rule会放到前面优先构造trie,个人为这样node A包含node B的概率更大,这可能也是merge时创建的node C是A的拷贝而不是B的拷贝的原因,因为这样出现ACL_INTERSECT_B的概率相对较低。
一些说明:
trie树构造完成后,会将其由指针跳转的形式转换为等效的数组索引形式,即node array,既可让匹配数据更紧凑,也可提高匹配算法的效率。
采用node array的方式进行状态点的压缩是很常见的优化方式,比如snort里面的ac算法(acsmx.c):
笔者也曾经做过类似的优化,通过将出边由指针方式修改为索引方式,整个匹配tree的内存占用只需要原来的1/5。
将指针方式转换为node array形式是优化的第一步,对于Next[256]出边又可以采用多种压缩方式,比如snort中新的ac算法(acsmx2.c),就采用了Sparse rows和Banded rows等多种压缩方式,但其原理是将出边进行映射转换,本质上还是做DFA状态跳转。
DPDK对边的压缩方式与上述类似,不过它优化的粒度更细,不同type的node有不同的压缩方式:
比如在示例三中,node 1为DFA节点(根节点强制使用DFA方式),2、3、a5、b4、c3、d2为QRANGE,4、5为SINGLE,6、e1为MATCH。
2、3、a5、b4虽然在图上仅有一条有效边,但它不为SINGLE,因为对于无效的匹配其实也会有出边,所以它的真实出边数目并不唯一,只有像4、5这类全匹配节点才是真正的SINGLE节点。
在构造node array前,会调用acl_calc_counts_indices函数更新node的node type,fanout等信息。
node type依据其fanout值决定,fanout计算见acl_count_fanout函数,其原理是:
比如对于示例3中的d2节点:
fanout计算完成后,若其值为1则为SINGLE节点,(1, 5]为QRANGE节点,(5, 256]为DFA节点。
注意:对于trie树的root节点,不论fanout值为多少,会强制将其构造为DFA节点,且其fanout值会重新计算。
type和fanout计算完成后,会统计各类节点数目,信息保存在acl_calc_counts_indices传入的counts参数中,随后rte_acl_gen依据这些信息将整块的node array内存分配出来,其布局大致如下:
Data indexes中用于保存在rte_acl_field_def中定义的offset;
Results对应match node,用于保存匹配结果。
Trans table包含整个匹配过程中的跳转点:
静态将整块node array分配完成后,就需要依据trie 树的node信息填充Trans table和Results了,具体过程见acl_gen_node函数;Data indexes的填充则在acl_set_data_indexes中完成。
2.2中的内存布局大致描绘了各种类型节点的分布情况,DFAs内部由一个一个的DFA节点组成,QUADs和SINGLEs也一样,都是由相同类型的节点构成。
对于每一个节点,其结构则类似如下形式:
DFA节点的fanout一般为4,出边数为fanout*RTE_ACL_DFA_GR64_SIZE;(图中画的为fanout为4的情况,256条出边)
QUAD节点的fanout不超过5,即为节点的出边数不超过5;(图中画的为fanout为4的情况)
SINGLE节点只有一个出边;
图中的trans即为这个节点的出边,它本质是一个uint64的数据结构,通过trans和input信息即可计算得到下一个节点的index,从而实现匹配跳转。trans中不同bit位包含着丰富的信息,具体见acl.h中的说明即可。
高32位对于不同类型的节点有不同的解释:
低32位:
在实际处理过程中,通过高32位与input_byte计算得到index,与低32位中的addr,即可快速定位到下一个trans:trans_table + (addr+index)。
这里的处理其实与传统的DFA跳转差别很大,传统处理时,next = node[‘input’],跳转到下一个节点,然后采用next[‘input’]进行跳转和匹配,即使有数据结构的压缩,跳转目标仍是状态点。但DPDK中,跳转时直接采用trans_table + (addr+index),直接找到了状态点的边(trans),而不是到状态点。
跳转表具体构建时,采用acl_gen_node函数完成:
匹配的过程与跳转表的构建其实是互为一体的,如何构建跳转表就决定了如何进行匹配。
在2.3节,对于跳转的形式已进行了说明,具体可阅读rte_acl_classify_scalar函数:跳转时直接采用trans_table + (addr+index),直接找到了状态点的边(trans),而不是到状态点。
对于具体的匹配过程,还有一点需要注意,即GET_NEXT_4BYTES的使用,每次匹配时候都会去4BTYTES进行匹配,这也是为什么定义input fields时要求4字节连续。比如我在dpdk-dev邮件组中问的这个 问题 。
解决4字节连续,可以通过定义相同的input_index来解决,比如像邮件中提到的设置sport/dport的input_index相同,这是因为data indexes的构造取决于input_index,见acl_build_index函数;同时field_index不同、input_index相同时可避免对field区间的优化(如果优化,将某个field去掉了,这时4字节匹配会失效)。邮件中的问题,正是因为field[3]被优化掉后,4字节连续匹配出现问题。
在特定的场合还必须通过指定.size为32来解决,即使type类型为BITMASK,见DPDK的ACL文档中关于 tos示例的说明 。
另外再说下field_index,前面提出一个问题:field_index是否多余?
答案是不多余,因为算法中会对field进行优化,如果不指定field_index字段,这个优化就无法进行了,具体的优化处理见acl_rule_stats函数。
优化过程中要进行input_index的判断,这是因为相同的input_index可以有多个field,但其中只有某个field是completely wild时应避免进行优化。只有相同input_index的所有field都是completely wild时,才应该将这个field优化掉。
上面的一系列说明,都是针对GET_NEXT_4BYTES每次匹配四个字节的匹配进行的补充说明。
匹配的具体过程,这里用图形的方式进行简要说明,为了能有多种类型的node,这里构造规则如下:
trie树如下所述:
对应的node array如下图所示:
假设输入数据为:proto 16, ip 192.12.8.8,则transition跳转方式如上图红线所示:
注意:node array中indexes、DFA0和idle省略了。
关于trie树相关的理论知识参考 这里 。
本文主要介绍了DPDK的ACL算法,详细描述了如何由规则生成trie,并将trie转换为node array的过程,在文末通过示例介绍了具体的匹配过程。文章旨在介绍ACL算法的基本思路,希望对大家能有所帮助。
⑷ Android socket源码解析(三)socket的connect源码解析
上一篇文章着重的聊了socket服务端的bind,listen,accpet的逻辑。本文来着重聊聊connect都做了什么?
如果遇到什么问题,可以来本文 https://www.jianshu.com/p/da6089fdcfe1 下讨论
当服务端一切都准备好了。客户端就会尝试的通过 connect 系统调用,尝试的和服务端建立远程连接。
首先校验当前socket中是否有正确的目标地址。然后获取IP地址和端口调用 connectToAddress 。
在这个方法中,能看到有一个 NetHooks 跟踪socket的调用,也能看到 BlockGuard 跟踪了socket的connect调用。因此可以hook这两个地方跟踪socket,不过很少用就是了。
核心方法是 socketConnect 方法,这个方法就是调用 IoBridge.connect 方法。同理也会调用到jni中。
能看到也是调用了 connect 系统调用。
文件:/ net / ipv4 / af_inet.c
在这个方法中做的事情如下:
注意 sk_prot 所指向的方法是, tcp_prot 中 connect 所指向的方法,也就是指 tcp_v4_connect .
文件:/ net / ipv4 / tcp_ipv4.c
本质上核心任务有三件:
想要能够理解下文内容,先要明白什么是路由表。
路由表分为两大类:
每个路由器都有一个路由表(RIB)和转发表 (fib表),路由表用于决策路由,转发表决策转发分组。下文会接触到这两种表。
这两个表有什么区别呢?
网上虽然给了如下的定义:
但实际上在Linux 3.8.1中并没有明确的区分。整个路由相关的逻辑都是使用了fib转发表承担的。
先来看看几个和FIB转发表相关的核心结构体:
熟悉Linux命令朋友一定就能认出这里面大部分的字段都可以通过route命令查找到。
命令执行结果如下:
在这route命令结果的字段实际上都对应上了结构体中的字段含义:
知道路由表的的内容后。再来FIB转发表的内容。实际上从下面的源码其实可以得知,路由表的获取,实际上是先从fib转发表的路由字典树获取到后在同感加工获得路由表对象。
转发表的内容就更加简单
还记得在之前总结的ip地址的结构吗?
需要进行一次tcp的通信,意味着需要把ip报文准备好。因此需要决定源ip地址和目标IP地址。目标ip地址在之前通过netd查询到了,此时需要得到本地发送的源ip地址。
然而在实际情况下,往往是面对如下这么情况:公网一个对外的ip地址,而内网会被映射成多个不同内网的ip地址。而这个过程就是通过DDNS动态的在内存中进行更新。
因此 ip_route_connect 实际上就是选择一个缓存好的,通过DDNS设置好的内网ip地址并找到作为结果返回,将会在之后发送包的时候填入这些存在结果信息。而查询内网ip地址的过程,可以成为RTNetLink。
在Linux中有一个常用的命令 ifconfig 也可以实现类似增加一个内网ip地址的功能:
比如说为网卡eth0增加一个IPV6的地址。而这个过程实际上就是调用了devinet内核模块设定好的添加新ip地址方式,并在回调中把该ip地址刷新到内存中。
注意 devinet 和 RTNetLink 严格来说不是一个存在同一个模块。虽然都是使用 rtnl_register 注册方法到rtnl模块中:
文件:/ net / ipv4 / devinet.c
文件:/ net / ipv4 / route.c
实际上整个route模块,是跟着ipv4 内核模块一起初始化好的。能看到其中就根据不同的rtnl操作符号注册了对应不同的方法。
整个DDNS的工作流程大体如下:
当然,在tcp三次握手执行之前,需要得到当前的源地址,那么就需要通过rtnl进行查询内存中分配的ip。
文件:/ include / net / route.h
这个方法核心就是 __ip_route_output_key .当目的地址或者源地址有其一为空,则会调用 __ip_route_output_key 填充ip地址。目的地址为空说明可能是在回环链路中通信,如果源地址为空,那个说明可能往目的地址通信需要填充本地被DDNS分配好的内网地址。
在这个方法中核心还是调用了 flowi4_init_output 进行flowi4结构体的初始化。
文件:/ include / net / flow.h
能看到这个过程把数据中的源地址,目的地址,源地址端口和目的地址端口,协议类型等数据给记录下来,之后内网ip地址的查询与更新就会频繁的和这个结构体进行交互。
能看到实际上 flowi4 是一个用于承载数据的临时结构体,包含了本次路由操作需要的数据。
执行的事务如下:
想要弄清楚ip路由表的核心逻辑,必须明白路由表的几个核心的数据结构。当然网上搜索到的和本文很可能大为不同。本文是基于LInux 内核3.1.8.之后的设计几乎都沿用这一套。
而内核将路由表进行大规模的重新设计,很大一部分的原因是网络环境日益庞大且复杂。需要全新的方式进行优化管理系统中的路由表。
下面是fib_table 路由表所涉及的数据结构:
依次从最外层的结构体介绍:
能看到路由表的存储实际上通过字典树的数据结构压缩实现的。但是和常见的字典树有点区别,这种特殊的字典树称为LC-trie 快速路由查找算法。
这一篇文章对于快速路由查找算法的理解写的很不错: https://blog.csdn.net/dog250/article/details/6596046
首先理解字典树:字典树简单的来说,就是把一串数据化为二进制格式,根据左0,右1的方式构成的。
如图下所示:
这个过程用图来展示,就是沿着字典树路径不断向下读,比如依次读取abd节点就能得到00这个数字。依次读取abeh就能得到010这个数字。
说到底这种方式只是存储数据的一种方式。而使用数的好处就能很轻易的找到公共前缀,在字典树中找到公共最大子树,也就找到了公共前缀。
而LC-trie 则是在这之上做了压缩优化处理,想要理解这个算法,必须要明白在 tnode 中存在两个十分核心的数据:
这负责什么事情呢?下面就简单说说整个lc-trie的算法就能明白了。
当然先来看看方法 __ip_dev_find 是如何查找
文件:/ net / ipv4 / fib_trie.c
整个方法就是通过 tkey_extract_bits 生成tnode中对应的叶子节点所在index,从而通过 tnode_get_child_rcu 拿到tnode节点中index所对应的数组中获取叶下一级别的tnode或者叶子结点。
其中查找index最为核心方法如上,这个过程,先通过key左移动pos个位,再向右边移动(32 - bits)算法找到对应index。
在这里能对路由压缩算法有一定的理解即可,本文重点不在这里。当从路由树中找到了结果就返回 fib_result 结构体。
查询的结果最为核心的就是 fib_table 路由表,存储了真正的路由转发信息
文件:/ net / ipv4 / route.c
这个方法做的事情很简单,本质上就是想要找到这个路由的下一跳是哪里?
在这里面有一个核心的结构体名为 fib_nh_exception 。这个是指fib表中去往目的地址情况下最理想的下一跳的地址。
而这个结构体在上一个方法通过 find_exception 获得.遍历从 fib_result 获取到 fib_nh 结构体中的 nh_exceptions 链表。从这链表中找到一模一样的目的地址并返回得到的。
文件:/ net / ipv4 / tcp_output.c
⑸ 请教一个关于二维数组的算法
如果仅仅对于6×9这么小的矩阵来说,可以直接循环寻找各个方向的匹配,源代码如下:
#include <cstdio>
#include <cstring>
using namespace std;
int Array[6][9] =
{
{1,1,0,1,0,0,1,0,0},
{4,1,1,0,0,0,1,4,0},
{4,0,0,1,1,4,4,1,0},
{0,0,0,0,0,1,0,1,0},
{0,0,1,1,4,1,0,1,0},
{0,0,0,0,4,0,0,0,0},
};
int mo[5] = {1,1,4,1,1};
bool check_right(int a,int b) //右方向
{
int i;
for (i=0;i<5;i++) if (b + i >= 9) return false;
else if (Array[a][b + i] != mo[i]) return false;
return true;
}
bool check_down(int a,int b) //下方向
{
int i;
for (i=0;i<5;i++) if (a + i >= 6) return false;
else if (Array[a + i][b] != mo[i]) return false;
return true;
}
bool check_left_down(int a,int b) //左下方向
{
int i;
for (i=0;i<5;i++) if (a + i >= 6 || b - i < 0) return false;
else if (Array[a + i][b - i] != mo[i]) return false;
return true;
}
bool check_right_down(int a,int b) //右下方向
{
int i;
for (i=0;i<5;i++) if (a + i >= 6 || b + i >= 9) return false;
else if (Array[a + i][b + i] != mo[i]) return false;
return true;
}
int main()
{
int i,j;
for (i=0;i<6;i++)
for (j=0;j<9;j++)
{
if (check_right(i,j)) printf("%d %d right\n",i,j);
if (check_down(i,j)) printf("%d %d down\n",i,j);
if (check_left_down(i,j)) printf("%d %d left_down\n",i,j);
if (check_right_down(i,j)) printf("%d %d right_down\n",i,j);
// 由于模板文本具有回文性质,故只需匹配4个方向,若对于任意模板文本,追加检查剩余的四个方向即可.
}
return 0;
}
若对于较为巨大的矩阵以及较多的模板文本,(例如500×500的矩阵,500个文本),如果需要在较短的时间内完成算法(如1秒)需要使用后缀自动机,具体到这个问题就是trie图,一种基于后缀有限状态自动机(Suffix DFA)的专门用于处理多串匹配的数据结构,其构造方法大致是先构造模板文本的trie树,再通过后缀链接生成包含所有转移的trie图。参考源代码如下:
(此代码从控制台读入一个最大支持到1000×1000的字符矩阵,以及最多1000个文本,程序向控制台输出每一个文本在矩阵中的起始位置和方向,以A-H表示八个方向,在Intel Pentium D 2.1G的处理器上对于任何数据均可在1s内完成运算)
#include <stdio.h>
#include <string.h>
int n,m,l,totalnode;
#define N 1100
#define M 1100000
char data[N][N];
char word[N];
typedef struct
{
int suffix,kind;
int c[26];
}trieT;
trieT trie[M];
typedef struct
{
int a,b;
char oper;
} ans_type;
ans_type ans[N];
int Q[M];
char nowoper;
const int thitah[8]={1,1,0,-1,-1,-1,0,1};
const int thital[8]={0,-1,-1,-1,0,1,1,1};
void read_data()
{
scanf("%d %d %d%*c",&n,&m,&l);
int i,j;
for (i=1;i<=n;i++)
{
for (j=1;j<=m;j++) {scanf("%c",&data[i][j]);data[i][j]-='A';}
scanf("%*c");
}
}
void insert(int s,int len)
{
int i,pnt=1;
for (i=len-1;i>=0;i--)
{
if (trie[pnt].c[word[i]]!=0) pnt=trie[pnt].c[word[i]];
else
{
totalnode++;
trie[pnt].c[word[i]]=totalnode;
pnt=totalnode;
trie[pnt].kind=-1;trie[pnt].suffix=0;
memset(trie[pnt].c,0,sizeof(trie[pnt].c));
}
}
trie[pnt].kind=s;
}
void init()
{
totalnode=1;
trie[1].kind=-1;trie[1].suffix=1;
memset(trie[1].c,0,sizeof(trie[1].c));
int i,j,len;
for (i=1;i<=l;i++)
{
scanf("%s",&word);
len=strlen(word);
for (j=0;j<len;j++) word[j]-='A';
insert(i,len);
}
}
void make_trie_graph()
{
int i,temp,tempnode,cl,op;
cl=0;op=0;
for (i=0;i<26;i++) if (trie[1].c[i]!=0)
{
temp=trie[1].c[i];
trie[temp].suffix=1;
if (trie[temp].kind==-1) trie[temp].kind=trie[1].kind;
Q[++op]=temp;
}else trie[1].c[i]=1;
while (cl<op)
{
tempnode=Q[++cl];
for (i=0;i<26;i++) if (trie[tempnode].c[i]!=0)
{
temp=trie[tempnode].c[i];
trie[temp].suffix=trie[trie[tempnode].suffix].c[i];
if (trie[temp].kind==-1) trie[temp].kind=trie[trie[temp].suffix].kind;
Q[++op]=temp;
}else trie[tempnode].c[i]=trie[trie[tempnode].suffix].c[i];
}
}
void get_ans(int s,int h,int l)
{
while (trie[s].kind!=-1)
{
ans[trie[s].kind].a=h;ans[trie[s].kind].b=l;
ans[trie[s].kind].oper=nowoper;
s=trie[s].suffix;
}
}
void check(int h,int l)
{
int pnt=1;
while ((h>0) && (h<=n) && (l>0) && (l<=m))
{
pnt=trie[pnt].c[data[h][l]];
if (trie[pnt].kind!=-1) get_ans(pnt,h,l);
h+=thitah[nowoper];l+=thital[nowoper];
}
}
void work_ans()
{
int i;
nowoper=0;for (i=1;i<=m;i++) check(1,i);
nowoper=1;for (i=1;i<=m;i++) check(1,i);for (i=2;i<=n;i++) check(i,m);
nowoper=2;for (i=1;i<=n;i++) check(i,m);
nowoper=3;for (i=1;i<=n;i++) check(i,m);for (i=1;i<m;i++) check(n,i);
nowoper=4;for (i=1;i<=m;i++) check(n,i);
nowoper=5;for (i=1;i<=m;i++) check(n,i);for (i=1;i<n;i++) check(i,1);
nowoper=6;for (i=1;i<=n;i++) check(i,1);
nowoper=7;for (i=1;i<=n;i++) check(i,1);for (i=2;i<=m;i++) check(1,i);
}
void show_ans()
{
int i;
for (i=1;i<=l;i++) printf("%d %d %c\n",ans[i].a-1,ans[i].b-1,ans[i].oper+'A');
}
int main()
{
read_data();
init();
make_trie_graph();
work_ans();
show_ans();
return 0;
}
若楼主希望学习trie图的具体理论和构造方法,可回帖注明。
⑹ 算法设计比赛做什么算法好
应该是ACM吧
就是给你8-10道算法题目,5个小时,做出来多的题目数越多,排名越靠前,如果题目数一样多的看用的时间。
时间的计算方法如下:
例如你A题用了20分钟AC,然后B题有用了30分钟AC(此时是比赛开始50分钟),又用了30分钟AC了C题,那么你的时间(penalty )是
20 + 50 + 80 = 150分钟
比赛中常用的算法有
1。动态规划
2。搜索
3。贪心
4。图论
5。组合数学
6。计算几何
7。数论
等
推荐到
http://acm.pku.e.cn
http://acm.zju.e.cn
http://acm.h.e.cn
http://acm.timus.ru
这几个OJ上练习
比较好的题目分类(POJ上的)
1。这个是我最喜欢的
初期:
一.基本算法:
(1)枚举. (poj1753,poj2965)(2008-10-27Done 位运算+宽搜)
(2)贪心(poj1328,poj2109,poj2586)
(3)递归和分治法.
(4)递推.
(5)构造法.(poj3295)
(6)模拟法.(poj1068,poj2632,poj1573,poj2993,poj2996)
二.图算法:
(1)图的深度优先遍历和广度优先遍历.
(2)最短路径算法(dijkstra,bellman-ford,floyd,heap+dijkstra)(2008-08-29Done)
(poj1860,poj3259,poj1062,poj2253,poj1125,poj2240)
(3)最小生成树算法(prim,kruskal)
(poj1789,poj2485,poj1258,poj3026)
(4)拓扑排序 (poj1094)(2008-09-01Done)
(5)二分图的最大匹配 (匈牙利算法) (poj3041,poj3020)
(6)最大流的增广路算法(KM算法). (poj1459,poj3436)
三.数据结构.
(1)串 (poj1035,poj3080,poj1936)
(2)排序(快排、归并排(与逆序数有关)、堆排) (poj2388,poj2299)
(3)简单并查集的应用.
(4)哈希表和二分查找等高效查找法(数的Hash,串的Hash)
(poj3349,poj3274,POJ2151,poj1840,poj2002,poj2503)
(5)哈夫曼树(poj3253)(2008-09-02Done)
(6)堆
(7)trie树(静态建树、动态建树) (poj2513)(2008-10-23Done 并查集、欧拉)
四.简单搜索
(1)深度优先搜索 (poj2488,poj3083,poj3009,poj1321,poj2251)
(2)广度优先搜索(poj3278,poj1426,poj3126,poj3087.poj3414)
(3)简单搜索技巧和剪枝(poj2531,poj1416,poj2676,1129)
五.动态规划
(1)背包问题. (poj1837,poj1276)
(2)型如下表的简单DP(可参考lrj的书 page149):
1.E[j]=opt{D+w(i,j)} (poj3267,poj1836,poj1260,poj2533)
2.E[i,j]=opt{D[i-1,j]+xi,D[i,j-1]+yj,D[i-1][j-1]+zij} (最长公共子序列)
(poj3176,poj1080,poj1159)
3.C[i,j]=w[i,j]+opt{C[i,k-1]+C[k,j]}.(最优二分检索树问题)
六.数学
(1)组合数学:
1.加法原理和乘法原理.
2.排列组合.
3.递推关系.
(POJ3252,poj1850,poj1019,poj1942)
(2)数论.
1.素数与整除问题
2.进制位.
3.同余模运算.
(poj2635, poj3292,poj1845,poj2115)
(3)计算方法.
1.二分法求解单调函数相关知识.(poj3273,poj3258,poj1905,poj3122)
七.计算几何学.
(1)几何公式.
(2)叉积和点积的运用(如线段相交的判定,点到线段的距离等). (poj2031,poj1039)
(3)多边型的简单算法(求面积)和相关判定(点在多边型内,多边型是否相交)
(poj1408,poj1584)
(4)凸包. (poj2187,poj1113)(2008-08-29Done)
中级:
一.基本算法:
(1)C++的标准模版库的应用. (poj3096,poj3007)
(2)较为复杂的模拟题的训练(poj3393,poj1472,poj3371,poj1027,poj2706)
二.图算法:
(1)差分约束系统的建立和求解. (poj1201,poj2983)(2008-09-05Done)
(2)最小费用最大流(poj2516,poj2516,poj2195)
(3)双连通分量(poj2942)
(4)强连通分支及其缩点.(poj2186)
(5)图的割边和割点(poj3352)
(6)最小割模型、网络流规约(poj3308, )
三.数据结构.
(1)线段树. (poj2528,poj2828,poj2777,poj2886,poj2750)
(2)静态二叉检索树. (poj2482,poj2352)
(3)树状树组(poj1195,poj3321)
(4)RMQ. (poj3264,poj3368)
(5)并查集的高级应用. (poj1703,2492)
(6)KMP算法. (poj1961,poj2406)(2008-09-16Done)
四.搜索
(1)最优化剪枝和可行性剪枝
(2)搜索的技巧和优化 (poj3411,poj1724)
(3)记忆化搜索(poj3373,poj1691)
五.动态规划
(1)较为复杂的动态规划(如动态规划解特别的施行商问题等)
(poj1191,poj1054,poj3280,poj2029,poj2948,poj1925,poj3034)
(2)记录状态的动态规划. (POJ3254,poj2411,poj1185)
(3)树型动态规划(poj2057,poj1947,poj2486,poj3140)
六.数学
(1)组合数学:
1.容斥原理.
2.抽屉原理.
3.置换群与Polya定理(poj1286,poj2409,poj3270,poj1026).
4.递推关系和母函数.
(2)数学.
1.高斯消元法(poj2947,poj1487, poj2065,poj1166,poj1222)
2.概率问题. (poj3071,poj3440)
3.GCD、扩展的欧几里德(中国剩余定理) (poj3101)
(3)计算方法.
1.0/1分数规划. (poj2976)
2.三分法求解单峰(单谷)的极值.
3.矩阵法(poj3150,poj3422,poj3070)
4.迭代逼近(poj3301)
(4)随机化算法(poj3318,poj2454)
(5)杂题.
(poj1870,poj3296,poj3286,poj1095)
七.计算几何学.
(1)坐标离散化.
(2)扫描线算法(例如求矩形的面积和周长并,常和线段树或堆一起使用).
(poj1765,poj1177,poj1151,poj3277,poj2280,poj3004)
(3)多边形的内核(半平面交)(poj3130,poj3335)
(4)几何工具的综合应用.(poj1819,poj1066,poj2043,poj3227,poj2165,poj3429)
高级:
一.基本算法要求:
(1)代码快速写成,精简但不失风格
(poj2525,poj1684,poj1421,poj1048,poj2050,poj3306)
(2)保证正确性和高效性. poj3434
二.图算法:
(1)度限制最小生成树和第K最短路. (poj1639)
(2)最短路,最小生成树,二分图,最大流问题的相关理论(主要是模型建立和求解)
(poj3155, poj2112,poj1966,poj3281,poj1087,poj2289,poj3216,poj2446
(3)最优比率生成树. (poj2728)
(4)最小树形图(poj3164)
(5)次小生成树.
(6)无向图、有向图的最小环
三.数据结构.
(1)trie图的建立和应用. (poj2778)(2008-10-26Done 矩阵A^n)
(2)LCA和RMQ问题(LCA(最近公共祖先问题) 有离线算法(并查集+dfs) 和 在线算法
(RMQ+dfs)).(poj1330)
(3)双端队列和它的应用(维护一个单调的队列,常常在动态规划中起到优化状态转移的
目的). (poj2823)
(4)左偏树(可合并堆).
(5)后缀树(非常有用的数据结构,也是赛区考题的热点).
(poj3415,poj3294)
四.搜索
(1)较麻烦的搜索题目训练(poj1069,poj3322,poj1475,poj1924,poj2049,poj3426)
(2)广搜的状态优化:利用M进制数存储状态、转化为串用hash表判重、按位压缩存储状态、双向广搜、A*算法. (poj1768,poj1184,poj1872,poj1324,poj2046,poj1482)
(3)深搜的优化:尽量用位运算、一定要加剪枝、函数参数尽可能少、层数不易过大、可以考虑双向搜索或者是轮换搜索、IDA*算法. (poj3131,poj2870,poj2286)
五.动态规划
(1)需要用数据结构优化的动态规划.
(poj2754,poj3378,poj3017)
(2)四边形不等式理论.
(3)较难的状态DP(poj3133)
六.数学
(1)组合数学.
1.MoBius反演(poj2888,poj2154)
2.偏序关系理论.
(2)博奕论.
1.极大极小过程(poj3317,poj1085)
2.Nim问题.
七.计算几何学.
(1)半平面求交(poj3384,poj2540)
(2)可视图的建立(poj2966)
(3)点集最小圆覆盖.
(4)对踵点(poj2079)
八.综合题.
(poj3109,poj1478,poj1462,poj2729,poj2048,poj3336,poj3315,poj2148,poj1263)
2。这个每个分类的题目比较多,适合作为第一个分类的扩展
说明:递推算动归, 离散化算数据结构, 并查集算数据结构, 博弈算动归, 麻烦题一般都是不错的综合题, 最短路算图论,数据的有序化算排序
麻烦题:1697, 1712, 1713, 1720, 1729, 1765, 1772, 1858, 1872, 1960, 1963, 2050, 2122, 2162, 2219, 2237,
简单题目:1000, 1003, 1004, 1005, 1007, 1046, 1207, 1226, 1401, 1504, 1552, 1607, 1657, 1658, 1674, 1799, 1862, 1906, 1922, 1929, 1931, 1969, 1976, 2000, 2005, 2017, 2027, 2070, 2101, 2105, 2109, 2116, 2136, 2160, 2190, 2232, 2234, 2275, 2301, 2350, 2363, 2389, 2393, 2413, 2419, 推荐:1063, 1064, 1131, 1140, 1715, 2163,
杂题:1014, 1218, 1316, 1455, 1517, 1547, 1580, 1604, 1663, 1678, 1749, 1804, 2013, 2014, 2056, 2059, 2100, 2188, 2189, 2218, 2229, 2249, 2290, 2302, 2304, 2309, 2313, 2316, 2323, 2326, 2368, 2369, 2371, 2402, 2405, 2407, 推荐:1146, 1147, 1148, 1171, 1389, 1433, 1468, 1519, 1631, 1646, 1672, 1681, 1700, 1701, 1705, 1728, 1735, 1736, 1752, 1754, 1755, 1769, 1781, 1787, 1796, 1797, 1833, 1844, 1882, 1933, 1941, 1978, 2128, 2166, 2328, 2383, 2420,
高精度:1001, 1220, 1405, 1503,
排序:1002, 1318, 1877, 1928, 1971, 1974, 1990, 2001, 2002, 2092, 2379, 2388, 2418, 推荐:1423, 1694, 1723, 1727, 1763, 1788, 1828, 1838, 1840, 2201, 2376, 2377, 2380,
搜索容易:1128, 1166, 1176, 1231, 1256, 1270, 1321, 1543, 1606, 1664, 1731, 1742, 1745, 1847, 1915, 1950, 2038, 2157, 2182, 2183, 2381, 2386, 2426, 不易:1024, 1054, 1117, 1167, 1708, 1746, 1775, 1878, 1903, 1966, 2046, 2197, 2349, 推荐:1011, 1190, 1191, 1416, 1579, 1632, 1639, 1659, 1680, 1683, 1691, 1709, 1714, 1753, 1771, 1826, 1855, 1856, 1890, 1924, 1935, 1948, 1979, 1980, 2170, 2288, 2331, 2339, 2340,
数据结构容易:1182, 1656, 2021, 2023, 2051, 2153, 2227, 2236, 2247, 2352, 2395, 不易:1145, 1177, 1195, 1227, 1661, 1834, 推荐:1330, 1338, 1451, 1470, 1634, 1689, 1693, 1703, 1724, 1988, 2004, 2010, 2119, 2274,
动态规划容易:1018, 1050, 1083, 1088, 1125, 1143, 1157, 1163, 1178, 1179, 1189, 1208, 1276, 1322, 1414, 1456, 1458, 1609, 1644, 1664, 1690, 1699, 1740, 1742, 1887, 1926, 1936, 1952, 1953, 1958, 1959, 1962, 1975, 1989, 2018, 2029, 2033, 2063, 2081, 2082, 2181, 2184, 2192, 2231, 2279, 2329, 2336, 2346, 2353, 2355, 2356, 2385, 2392, 2424, 不易:1019, 1037, 1080, 1112, 1141, 1170, 1192, 1239, 1655, 1695, 1707, 1733, 1737, 1837, 1850, 1920, 1934, 1937, 1964, 2039, 2138, 2151, 2161, 2178, 推荐:1015, 1635, 1636, 1671, 1682, 1692, 1704, 1717, 1722, 1726, 1732, 1770, 1821, 1853, 1949, 2019, 2127, 2176, 2228, 2287, 2342, 2374, 2378, 2384, 2411,
字符串:1488, 1598, 1686, 1706, 1747, 1748, 1750, 1760, 1782, 1790, 1866, 1888, 1896, 1951, 2003, 2121, 2141, 2145, 2159, 2337, 2359, 2372, 2406, 2408,
贪心:1042, 1065, 1230, 1323, 1477, 1716, 1784,
图论容易:1161, 1164, 1258, 1175, 1308, 1364, 1776, 1789, 1861, 1939, 1940, 1943, 2075, 2139, 2387, 2394, 2421, 不易:1041, 1062, 1158, 1172, 1201, 1275, 1718, 1734, 1751, 1904, 1932, 2173, 2175, 2296, 网络流:1087, 1273, 1698, 1815, 2195, 匹配:1274, 1422, 1469, 1719, 2060, 2239, Euler:1237, 1637, 1394, 2230, 推荐:2049, 2186,
计算几何容易:1319, 1654, 1673, 1675, 1836, 2074, 2137, 2318, 不易:1685, 1687, 1696, 1873, 1901, 2172, 2333, 凸包:1113, 1228, 1794, 2007, 2187,
模拟容易:1006, 1008, 1013, 1016, 1017, 1169, 1298, 1326, 1350, 1363, 1676, 1786, 1791, 1835, 1970, 2317, 2325, 2390, 不易:1012, 1082, 1099, 1114, 1642, 1677, 1684, 1886,
数学容易:1061, 1091, 1142, 1289, 1305, 1306, 1320, 1565, 1665, 1666, 1730, 1894, 1914, 2006, 2042, 2142, 2158, 2174, 2262, 2305, 2321, 2348, 不易:1067, 1183, 1430, 1759, 1868, 1942, 2167, 2171, 2327, 推荐:1423, 1450, 1640, 1702, 1710, 1721, 1761, 1830, 1930, 2140,
⑺ 字符串匹配算法的使用(未完待整理)
字符串的匹配在Java中都知道使用indexOf函数来实现,那么其匹配算法是怎么样的呢?
单模式和多模式的区别就是一次遍历主串能否将多个模式的字符串都查找出来。
英文全称为Brute Force,暴力匹配算法,匹配字符串的方法比较暴力,也比较简单易懂。其大概的思路就是:
我们可以看到,在极端情况下,在主串 aaaa...aab 中寻找模式串 aab ,那么总共需要寻找(n-m+1)次,且每次都需要比对m次,那么时间复杂度将是 (n-m+1)*m ,即 O(n*m) ;但实际上并不会这么低效,因为我们的使用场景中主串和模式串都不会太长,而且在每个子串和模式串进行比对时,只要中途有一个不匹配,那么当前比对就会提前结束,因此大部分情况下,时间复杂度都会比 O(n*m) 要好。
我们在BF算法的基础上引入哈希算法,我们不需要将每个子串与模式串逐个字符地进行比较,而是计算得出每个子串的hash值,然后和模式串的hash值进行比较,如果有相等的,那就说明有子串和模式串匹配上了。
虽然我们只需要比对模式串和子串的hash值就能得到匹配结果,次数为(n-m+1),但是对每个子串进行hash计算的时候,是要遍历每个字符的,因此次数也是m,那么总的时间复杂度还是 O(n*m) ,并没有明显地提升。
那么我们该如何想出一个办法,使得每个子串hash值的计算时间得到提升呢?这就是RK算法的精髓,假设子串包含的字符集中元素个数为k,那么就用k进制数来代表这个子串,然后hash的过程就是将这个k进制的数转换为十进制的数,这个十进制的数就是该子串的hash值。
相邻子串的hash值计算是有规律的,我们只需要遍历一次主串就能得到所有子串的hash值,算法复杂度为O(n),而不是像原先一样,每个子串都需要O(m)的时间复杂度。
然后将模式串的hash值和所有子串的hash值进行比较,每次比较的时间复杂度是 O(1) ,总共比较(n-m+1)次,所以RK算法的总的时间开销为 O(n)+O(1)*O(n-m+1) ,即为 O(n) ,时间复杂度比BF算法更加高效。
当然,有hash的地方就有可能会存在hash冲突,有可能子串和hash值和模式串的hash值是一样的,但内容就是不一样,此时怎么办呢?其实很简单,对于hash值一样的子串,我们增加双保险,再比较一下这m个字符是否都一样即可,总的时间开销为 O(n)+O(1)*O(n-m+1)+O(m) ,即为 O(n) 。
如果极端情况下出现了很多hash冲突呢?我们对于每个和模式串相同hash值的子串都需要逐一再进行比较,那么总的时间开销就会为 O(n)+O(1)*O(n-m+1)+O(m)*O(n-m+1) ,即为 O(n*m) ,不过这种概率太小了,大部分情况下都不会这样。
在真正的文本编辑器中查找和替换某个字符串时,使用的算法既不是上述的BF算法,也不是RK算法;BF算法只适合不是很长的主串,RK算法则要设计一个冲突概率很低的hash算法,这个比较困难,所以实际使用的是BM算法,它是工程中非常常用的一种字符串匹配算法,效率也是最高的。
算法的思想和过程有些复杂,待以后整理。
KMP算法在本质上是和BM算法一样的。算法的思想和过程有些复杂,待以后整理。
浏览器输入框中的智能输入匹配是怎么实现的,它是怎么做动态字符串匹配查找的呢?这就用到了Trie树。
又名字典树,是一种专门用来快速查找字符串前缀匹配结果的树形结构,其本质就是将所有字符串的重复的前缀合并在一起,构造一个多叉树。
其中,根节点不包含任何信息,每个节点表示一个字符,从根节点到红色节点的一条路径表示存储的一个字符串。当我们在如上Trie树中查找"he"时,发现"he"并非是一个字符串,而是"hello"和"her"的公共前缀,那么就会找到这两个字符串返回。
Trie树在内存中是如何存储的呢?因为每一个节点都可能是包含所有字符的,所以每一个节点都是一个数组(或者散列表),用来存储每个字符及其后缀节点的指针。
使用Trie树,最开始构建的时候,时间复杂度为 O(n) ,其中n为所有字符串长度之和,但是一旦构建完成,频繁地查询某个字符串是非常高效的,时间复杂度为 O(k) ,其中k为查找字符串的长度。
Trie树虽然查询效率很高,但是比较浪费内存,每一个节点都必须维护一个数组存放所有可能的字符数据及其指向下一个节点的指针,因此在所有字符串公共前缀并不多的时候,内存空间浪费地就更多了。这种问题其实也有对应的解决办法,我们可以不使用数组,而是使用有序数组、散列表、红黑树来存放,可以相应地降低性能来节省内存空间。
Trie树除了可以实现浏览器动态输入内容查找候选项的功能外,还可以实现多模式地敏感词匹配功能。假设我们需要对用户输入的内容进行敏感词检查,将所有的敏感内容用***代替,那么该如何实现呢?
首先我们可以维护一个敏感词字典,使用上述四种单模式匹配算法也可以实现,但是需要遍历N次用户输入的内容,其中N是所有敏感词的模式串,显得非常低效。但是我们如果将敏感词字典维护为一个Trie树,然后将用户输入的内容从位置0开始在Trie树中进行查询,如果匹配到红色节点,那么说明有敏感词;如果没有匹配到红色节点,就从用户输入内容的下一个位置开始继续在Trie树中查询,直至将用户输入内容遍历完,因此我们只是遍历了一遍主串。
然而更高效的多模式字符串匹配使用地更多的是如下的AC自动机。
如果把Trie树比作BF算法,KMP算法是BF算法的改进,那么AC自动机就是利用同样的思想改进了Trie树。
算法的思想和过程有些复杂,待以后整理。
⑻ 是的 计算机算法
计算机算法是以一步接一步的方式来详细描述计算机如何将输入转化为所要求的输出的过程,或者说,算法是对计算机上执行的计算过程的具体描述。
编辑本段算法性质一个算法必须具备以下性质: (1)算法首先必须是正确的,即对于任意的一组输入,包括合理的输入与不合理的输入,总能得到预期的输出。如果一个算法只是对合理的输入才能得到预期的输出,而在异常情况下却无法预料输出的结果,那么它就不是正确的。 (2)算法必须是由一系列具体步骤组成的,并且每一步都能够被计算机所理解和执行,而不是抽象和模糊的概念。 (3)每个步骤都有确定的执行顺序,即上一步在哪里,下一步是什么,都必须明确,无二义性。 (4)无论算法有多么复杂,都必须在有限步之后结束并终止运行,即算法的步骤必须是有限的。在任何情况下,算法都不能陷入无限循环中。 一个问题的解决方案可以有多种表达方式,但只有满足以上4个条件的解才能称之为算法。编辑本段重要算法A*搜寻算法
俗称A星算法。这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或线上游戏的BOT的移动计算上。该算法像Dijkstra算法一样,可以找到一条最短路径;也像BFS一样,进行启发式的搜索。
Beam Search
束搜索(beam search)方法是解决优化问题的一种启发式方法,它是在分枝定界方法基础上发展起来的,它使用启发式方法估计k个最好的路径,仅从这k个路径出发向下搜索,即每一层只有满意的结点会被保留,其它的结点则被永久抛弃,从而比分枝定界法能大大节省运行时间。束搜索于20 世纪70年代中期首先被应用于人工智能领域,1976 年Lowerre在其称为HARPY的语音识别系统中第一次使用了束搜索方法,他的目标是并行地搜索几个潜在的最优决策路径以减少回溯,并快速地获得一个解。
二分取中查找算法
一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。这种搜索算法每一次比较都使搜索范围缩小一半。
Branch and bound
分支定界(branch and bound)算法是一种在问题的解空间树上搜索问题的解的方法。但与回溯算法不同,分支定界算法采用广度优先或最小耗费优先的方法搜索解空间树,并且,在分支定界算法中,每一个活结点只有一次机会成为扩展结点。
数据压缩
数据压缩是通过减少计算机中所存储数据或者通信传播中数据的冗余度,达到增大数据密度,最终使数据的存储空间减少的技术。数据压缩在文件存储和分布式系统领域有着十分广泛的应用。数据压缩也代表着尺寸媒介容量的增大和网络带宽的扩展。
Diffie–Hellman密钥协商
Diffie–Hellman key exchange,简称“D–H”,是一种安全协议。它可以让双方在完全没有对方任何预先信息的条件下通过不安全信道建立起一个密钥。这个密钥可以在后续的通讯中作为对称密钥来加密通讯内容。
Dijkstra’s 算法
迪科斯彻算法(Dijkstra)是由荷兰计算机科学家艾兹格·迪科斯彻(Edsger Wybe Dijkstra)发明的。算法解决的是有向图中单个源点到其他顶点的最短路径问题。举例来说,如果图中的顶点表示城市,而边上的权重表示着城市间开车行经的距离,迪科斯彻算法可以用来找到两个城市之间的最短路径。
动态规划
动态规划是一种在数学和计算机科学中使用的,用于求解包含重叠子问题的最优化问题的方法。其基本思想是,将原问题分解为相似的子问题,在求解的过程中通过子问题的解求出原问题的解。动态规划的思想是多种算法的基础,被广泛应用于计算机科学和工程领域。比较着名的应用实例有:求解最短路径问题,背包问题,项目管理,网络流优化等。这里也有一篇文章说得比较详细。
欧几里得算法
在数学中,辗转相除法,又称欧几里得算法,是求最大公约数的算法。辗转相除法首次出现于欧几里得的《几何原本》(第VII卷,命题i和ii)中,而在中国则可以追溯至东汉出现的《九章算术》。
最大期望(EM)算法
在统计计算中,最大期望(EM)算法是在概率(probabilistic)模型中寻找参数最大似然估计的算法,其中概率模型依赖于无法观测的隐藏变量(Latent Variable)。最大期望经常用在机器学习和计算机视觉的数据聚类(Data Clustering)领域。最大期望算法经过两个步骤交替进行计算,第一步是计算期望(E),利用对隐藏变量的现有估计值,计算其最大似然估计值;第二步是最大化(M),最大化在 E 步上求得的最大似然值来计算参数的值。M 步上找到的参数估计值被用于下一个 E 步计算中,这个过程不断交替进行。
快速傅里叶变换(FFT)
快速傅里叶变换(Fast Fourier Transform,FFT),是离散傅里叶变换的快速算法,也可用于计算离散傅里叶变换的逆变换。快速傅里叶变换有广泛的应用,如数字信号处理、计算大整数乘法、求解偏微分方程等等。
哈希函数
HashFunction是一种从任何一种数据中创建小的数字“指纹”的方法。该函数将数据打乱混合,重新创建一个叫做散列值的指纹。散列值通常用来代表一个短的随机字母和数字组成的字符串。好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据,会使得数据库记录更难找到。
堆排序
Heapsort是指利用堆积树(堆)这种数据结构所设计的一种排序算法。堆积树是一个近似完全二叉树的结构,并同时满足堆积属性:即子结点的键值或索引总是小于(或者大于)它的父结点。
归并排序
Merge sort是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
RANSAC 算法
RANSAC 是”RANdom SAmpleConsensus”的缩写。该算法是用于从一组观测数据中估计数学模型参数的迭代方法,由Fischler and Bolles在1981提出,它是一种非确定性算法,因为它只能以一定的概率得到合理的结果,随着迭代次数的增加,这种概率是增加的。该算法的基本假设是观测数据集中存在”inliers”(那些对模型参数估计起到支持作用的点)和”outliers”(不符合模型的点),并且这组观测数据受到噪声影响。RANSAC 假设给定一组”inliers”数据就能够得到最优的符合这组点的模型。
RSA加密算法
这是一个公钥加密算法,也是世界上第一个适合用来做签名的算法。今天的RSA已经专利失效,其被广泛地用于电子商务加密,大家都相信,只要密钥足够长,这个算法就会是安全的。
并查集Union-find
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
Viterbi algorithm
寻找最可能的隐藏状态序列(Finding most probable sequence of hidden states)。编辑本段算法特点1.有穷性。一个算法应包含有限的操作步骤,而不能是无限的。事实上“有穷性”往往指“在合理的范围之内”。如果让计算机执行一个历时1000年才结束的算法,这虽然是有穷的,但超过了合理的限度,人们不把他是为有效算法。 2. 确定性。算法中的每一个步骤都应当是确定的,而不应当是含糊的、模棱两可的。算法中的每一个步骤应当不致被解释成不同的含义,而应是十分明确的。也就是说,算法的含义应当是唯一的,而不应当产生“歧义性”。 3. 有零个或多个输入、所谓输入是指在执行算法是需要从外界取得必要的信息。 4. 有一个或多个输出。算法的目的是为了求解,没有输出的算法是没有意义的。 5.有效性。 算法中的每一个 步骤都应当能有效的执行。并得到确定的结果。编辑本段算法与程序虽然算法与计算机程序密切相关,但二者也存在区别:计算机程序是算法的一个实例,是将算法通过某种计算机语言表达出来的具体形式;同一个算法可以用任何一种计算机语言来表达。 算法列表 图论 路径问题 0/1边权最短路径 BFS 非负边权最短路径(Dijkstra) 可以用Dijkstra解决问题的特征 负边权最短路径 Bellman-Ford Bellman-Ford的Yen-氏优化 差分约束系统 Floyd 广义路径问题 传递闭包 极小极大距离 / 极大极小距离 Euler Path / Tour 圈套圈算法 混合图的 Euler Path / Tour Hamilton Path / Tour 特殊图的Hamilton Path / Tour 构造 生成树问题 最小生成树 第k小生成树 最优比率生成树 0/1分数规划 度限制生成树 连通性问题 强大的DFS算法 无向图连通性 割点 割边 二连通分支 有向图连通性 强连通分支 2-SAT 最小点基 有向无环图 拓扑排序 有向无环图与动态规划的关系 二分图匹配问题 一般图问题与二分图问题的转换思路 最大匹配 有向图的最小路径覆盖 0 / 1矩阵的最小覆盖 完备匹配 最优匹配 稳定婚姻 网络流问题 网络流模型的简单特征和与线性规划的关系 最大流最小割定理 最大流问题 有上下界的最大流问题 循环流 最小费用最大流 / 最大费用最大流 弦图的性质和判定 组合数学 解决组合数学问题时常用的思想 逼近 递推 / 动态规划 概率问题 Polya定理 计算几何 / 解析几何 计算几何的核心:叉积 / 面积 解析几何的主力:复数 基本形 点 直线,线段 多边形 凸多边形 / 凸包 凸包算法的引进,卷包裹法 Graham扫描法 水平序的引进,共线凸包的补丁 完美凸包算法 相关判定 两直线相交 两线段相交 点在任意多边形内的判定 点在凸多边形内的判定 经典问题 最小外接圆 近似O(n)的最小外接圆算法 点集直径 旋转卡壳,对踵点 多边形的三角剖分 数学 / 数论 最大公约数 Euclid算法 扩展的Euclid算法 同余方程 / 二元一次不定方程 同余方程组 线性方程组 高斯消元法 解mod 2域上的线性方程组 整系数方程组的精确解法 矩阵 行列式的计算 利用矩阵乘法快速计算递推关系 分数 分数树 连分数逼近 数论计算 求N的约数个数 求phi(N) 求约数和 快速数论变换 …… 素数问题 概率判素算法 概率因子分解 数据结构 组织结构 二叉堆 左偏树 二项树 胜者树 跳跃表 样式图标 斜堆 reap 统计结构 树状数组 虚二叉树 线段树 矩形面积并 圆形面积并 关系结构 Hash表 并查集 路径压缩思想的应用 STL中的数据结构 vector deque set / map 动态规划 / 记忆化搜索 动态规划和记忆化搜索在思考方式上的区别 最长子序列系列问题 最长不下降子序列 最长公共子序列 一类NP问题的动态规划解法 树型动态规划 背包问题 动态规划的优化 四边形不等式 函数的凸凹性 状态设计 规划方向 线性规划 常用思想 二分 最小表示法 串 KMP Trie结构 后缀树/后缀数组 LCA/RMQ 有限状态自动机理论 排序 选择/冒泡 快速排序 堆排序 归并排序 基数排序 拓扑排序 排序网络
扩展阅读:
1
《计算机算法设计与分析导论》朱清新等编着人民邮电出版社
开放分类:
计算机,算法