关于图算法的总结

 例,01问题

求长度为n的01串,满足如下条件

1,长为L0的连续子串中0的个数不少A0,不多于B0

2,长为L1的连续子串中的1的个数不少于A1,不多于B1

如果不存在,输出-1

 

这个题可以构造图,然后求图的各点的最短路径。

由于上述1,2条件的存在,对于任意的k,用f(k)表示前k串的和

则f(k+L0)-f(k)>=L0-b0

   f(k+L0)-f(k)<=L0-a0

 

   f(k+L1)-f(k)>=a1

   f(k+L1)-f(k)<=b1

 

   f(k+1)-f(k)>=0

   f(k+1)-f(k)<=1

 

根据<=的关系可以构造一个图,图中的各个节点就是从1到n,关系如上不等式。相当于求n个点到点0的最短距离。而相邻的两个点与0点之间的距离就是我们要求的该位置的0或者1

 

由于该算法中权值存在负值,所以用bellman松弛法求单源最短路径

源代码如下

 

bellman_ford算法

BELLMAN_FORD(G,w,s)

 Init-single-source(G,s)

for i<-1 to |V[G]|-1

do for each edge(u,v) in E[G]

do relax(u,v,w)

for each edge(u,v) in E[G]

do if d[v]>d[u]+w(u,v)

then return FALSE

return TRUE

 

松弛法

Relax(u,v,w)

if d[v]>d[u]+w(u,v)

then d[v]<-d[u]+w(u,v)

trace[v]<-u;

 

 

 

同时复习一下Dijkstra算法

 

Dijkstra(G,w,s)

 Init-single-source(G,s)

S=null;

Q=V[G];

while Q!=null

 do u=min(Q)

 S=S+{u}

 for each vertex v in Adj[u]

do Relax (u,v,w)

附一个Dijkstra算法的实现

 

for(int i=1;i<=pointNum;i++)

{

min=MaxInt;

for(int j=i+1;j<=pointNum;j++)

{

if(!isVisited[j])

{

if(d[1][j]<min)

{

minPoint=j;

min=d[1][j];

}

}

}

isVisited=true;

for(int j=1;j<=pointNum;j++)

{

if(d[pointNum][j]+min<d[1][j])

d[1][j]=d[pointNum][j]+min;

}

}

 

 

2,二分图

求二分图的最大匹配有两种算法:最大流,匈牙利算法

求二分图的最佳匹配:最大流。

 

最大流的算法基于以下的思想:残留网络,增广路径,最小割。

求最大流的算法过程:1,随意找一个源点到终点的增广路径。2,求该网络的残留网络。3,在残留网络上找增广路径,将增广路径上可通过的最大值加到该路径上的图的流上。4重复2,3直到没有新的增广路径。找增广路径的时候用广度优先算法可以增加算法的效率。

 

对二分图求最大最优匹配的时候,只用额外的加一个源点和终点即可。

求最大匹配最好还是用匈牙利算法。

 

以下用匈牙利算法来求解,国王心爱的王子选妻子的题目:国王有n个儿子,宫里有n个美女。每个王子喜欢一些姑娘(艹),国王对每个儿子的喜欢程度不同为Ai,如果王子挑选到了喜欢的姑娘,国王的高兴程度+Ai^2。找一个算法,让国王足够满意。

 

关于二分图的另一个说明:完全二分图的边的数目小于n^2/4,证明很简单,省略。

在下面的程序中,王子为1~n,女孩为n+1~2n,边的数目正好为n^2,所以开了n^2条边。

 

  

 

关于二分图题目2

求二分图中的必须的边

 

算法有两种:1,枚举所有的边,然后用匈牙利算法求最大匹配,如果找不到最大匹配,则是必须的

       这种算法的情况下,如果是邻接矩阵的话,枚举每条边需要的时间是O(n^2),当然也可以存边,存边的话O(e),匈牙利算法的复杂度为O(EN)

 

  2,缩小查找空间,必须的边一定在一个完备匹配中,因为必须的边在所有的完备匹配中,如果少了这个边,一个完备匹配都找不到,所以可以先找一个完备匹配,然后剔除其中的边,用匈牙利算法验证。这样的话,将边的范围减小了。

 

代码如下

 

 

 

3, 线段树的应用

问题:树的统计。一棵含有n个节点的树,所有节点的编号依次为1,2,3,...,n。对于编号为v的节点,定义t(v)为后代中所有编号小于v的节点个数。请计算每个节点i的t(i)。

用到的数据结构,树,线段树。算法dfs

不妨先思考这样一个方法:假如这是一颗二叉树。我们将这棵二叉树保存在数组里面。从数组最末开始,向前扫描(本质是从叶子节点开始)。对于每一个节点,和父节点n/2比较,如果比这个父节点小,父节点的t值+1,然后父节点的父节点。算法复杂度为(nlogn)当然二叉树比较好的情况的时候。不好的时候复杂度就要高,最坏n^2。

对于这棵树,不方便用二叉树来做。

 

本题的算法很有意思。它是这样做的,先对树做一次正向dfs先序遍历,得到遍历序列,然后反向dfs先序,得到一个遍历序列,而,两个遍历序列重叠的部分,正好是树根的子节点。那么

t=正向dfs中小于v的节点数量+逆向中的+祖先中小于v的-整棵树小于v的(整棵树小于v的为v-1个)

 

该算法对线段树进行了灵活的应用,值得学习。一下是代码和测试用例

 

 

 

测试用例

 

15

0

0

2 12 15

0

0

2 5 8

5 1 3 4 9 10

0

2 6 11

1 14

0

0

0

2 2 13

0

 

 

运行结果0 0 0 0 0 1 6 0 3 1 0 0 0 2 0

 

4,多源点最短路径问题

 

考虑对远点进行合并,然后使用dijkstra算法,记住压缩图和原图之间点序号的映射

 

 

 

5, 闭包运算的题目

 

如何计算闭包,实际上就是floyd算法的思想。

两种方法

1,采用宽度优先或者深度优先遍历来解决。从任意一个顶点出发,进行一次遍历,就可以求出次顶点和其他各个顶点的联通状况。所以只需要把每个顶点做为出发点遍历一次,就能够知道任意两个顶点之间是否有路存在。穷举每个顶点,总的时间复杂度是O(n*n)

2,类似floyd算法,如下算法实现的那样。

 

题目:运动员之间可以传递信息,但不是每两个运动员之间都可以相互传递信息。求教练至少要通知几个人才可以使每个运动员都知道自己的命令。

算法,先计算闭包,给每个闭包赋予一个序号。

然后按照序号将图压缩,一个传递闭包一个节点。

统计入度为0的节点的个数,即为教练需要通知的次数

 


floyd算法又称floyd-warshall算法,是一种动态规划算法,用来求一个有向图G中每对顶点间的最短路径问题,其运行时间为O(v^3)

 

定义n*n的矩阵W。过程范围最短路径的权值矩阵为D(n)

 

Floyd-Warshall(W)

n<-rows(W)

D(0) <-W

     for(k=1~n)

         do for i=1~n

             do for k=1~n

 

             do d(k)ij =min(d(k-1)ij , d(k-1)ik +d(k-1)kj )

return D(n)

 

6, 使用floyd算法的例子。

染色

一个N个点的无向完全图,每条边都被染成[1,m]的一种颜色,要求选择一部分颜色,作为一个集合S,且S中的元素个数不超过(m+1)/2,并满足如下性质:

图中任意两个点a和b,一定存在一条长度不大于3的通路,其每条边的颜色都属于S。

简单的分析:

将m种颜色一分为2,然后证明必有一半的颜色满足。

 

使用然后,设A集合边为1,B集合的边的权重为4。使用floyd算法,求两点之间的最短距离。如果超过了3,则结果为B集合,否则为A集合。

代码如下

 

 

 7,二叉树的应用

 

坐船问题:某学校有n个学生去公园划船。一条船最多可以坐两个人。如果某两个学生同姓或者同名就可以坐同一条船。学校希望每个学生都上船,但是小船的租用费用很高。学校想要租最少的船。请问学校要租多少条船。

 

用二叉树来解决。左孩子同姓,右孩子同名。在选择将谁放在一条船上的时候有以下几种情况

1,如果父节点已经被其他节点使用了,则增加一条船。

2,如果父节点没有被使用,并且父节点的另一个节点已经被使用,则当前节点和父节点一条船

3,如果非1,2情况。若父节点是父父节点的左孩子,则父节点和右孩子被访问,左孩子连接到父父节点

4,若父节点是右孩子,则右孩子连接到父父节点

 

代码如下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章