2017.9.10-9.12

来源:互联网 发布:mac终端获取文件路径 编辑:程序博客网 时间:2024/06/02 01:02

=图论的学习
图论是数组结构中非常重要的一块
学习了图论之后 我渐渐的告别了模拟和简单的算法
开始学习更高深的东西
以下是我觉得这两天学习中的重点:
(1)邻接矩阵的读入:

Int const maxn=100Int a[maxn][maxn]memset(a,0, sizeof(a));Cin>>n;For (int i=0;i<n;i++)   for(int j=0;j<n;j++)     cin>>a[i][j];

还有一些变通的读入就不一一展开论述了
(2)邻接表的读入:

struct edge{    int y,v,next;       //y表示这条边的终点编号,v是权值;};                  //next表示同起点下条边的编号是多少edge e[maxm+10];  //边表。int linkk[maxn+10];  //起点表 link[i]表示由i出去的第一条边的下标是多少void insert(int ss,int ee,int vv)//ss为起点,ee为终点,vv为权值。{      e[++t].y=ee; e[t].v=vv;     //t表示有t条边,是个全局变量。    e[t].next=linkk[ss]; linkk[ss]=t;}void init(){    scanf("%d %d %d",&n,&p,&m);    for (int i=0;i<m;i++)    {        int xx,yy,zz;        scanf("%d%d%d",&xx,&yy,&zz);        insert(xx,yy,zz);        insert(yy,xx,zz);  //这里插入的是无向图,所以两条边都要插入。    }    }    

邻接矩阵只适合于20000个点左右的图 再打就会爆炸了
这时就要用到邻接表了
邻接表是链表中的一种 功能十分强大
在使用邻接表时有个很强大的
for (int i=linkk[k];i;i=e[i].next)
这句话在使用时经常出现

tips:
邻接矩阵:代码书写简单,找邻接点慢
采用二维数组的静态存储结构
一般点数|v|小于 等于5000的时候,用邻接矩阵。

邻接表:代码书写较复杂,找邻接点快
采用动态存储结构(指针或用数组模拟)
一般点数|v|大于等于5000,并且边得个数不是很多的时候,用邻接表,并且现在一般都是用数组来模拟。

数组模拟链表的速度会快一点,并且能避免一些错误。

(2)深度优先搜索 DFS
邻接矩阵:

Void dfs(int k);{    printf(“%d”,k);    f[k]=true;    for (int j=1;j<=n;j++)       if ((!f[j])&& a[k][j])  dfs(j);}

邻接表的dfs :

void dfs(int k){    for (int i=linkk[k];i;i=e[i].next)        if(!vis[e[i].y])         {            vis[e[i].y]=1;            dfs(e[i].y);        }}

(3)求无向的连通分量:

sum=0;      for (int i=1;i<-n;i++)            if (! f[i])                 {                    sum++;                    dfs(i);                }     cout<<sum;

(4)广度优先搜索(宽度优先搜索)BFS

Void  bfs(int i);{    memset(q,0,sizeof(q));    int head=0,tail=1;    q[1]=i;  f[i]=true;    while (head<tail)    {         k=q[++head];  cout>>k;         for (int j=1;j<=n,j++)            if (a[k][j] && !f[j])            {                 q[++tail]=j;                 f[j]=true;             }    }   }

广度优先搜索用到了队列!
(5)图的传递闭包

传递闭包的算法:
判断结点 i 到 j是否有路径

1、结点 i 到 j 有边则有路径。
2、 i 到 j 之间没有边:
如果存在另外的一个结点k,满足:i到k有路径,k到j有路径,则i到j有路径。否则i到j没有路径。
can[ i ][ j ]=true:i到j有边; can[ i ][j ]= false:无边。
则:can[ i ][ j ]=can[ i][ j] || ( can[ i ][ k ] && can[ k ][ j ])

for (int i=1;i<=n;i++)   can[i,i]:=true; for (int k=1;k<=n;k++)      for (int i=1;i<=n;i++)          for (int j=1;j<=n;j++)             can[i][j]  ||=  (can[i][k] && can[k][j]);

学了一些简单(嘿嘿嘿)的图论概念后
就可以来求一些最短距离问题了
这边就有几个大佬的算法
1、图中每对顶点(任意两点)之间的最短路径
(弗洛伊德算法:floyed)。
2、图中一个顶点到其他顶点的最短路径
(迪杰斯特拉算法:dijkstra)。
还有什么bellman-Ford SPFA
(1)floyed算法:

for (int k=1;k<=n;k++)      for (int i=1;i<=n;i++)          for (int j=1;j<=n;j++)            if  (d[i][k]+d[k][j]<d[i][j] )        d[i][j]=d[i][k]+d[k][j]

初始化条件:
D[ i][ i ]=0 //自己到自己为0;对角线为0;
D[i][j]=边权,i与j有直接相连的边
D[i][j]= +∞ ,i与j无直接相连的边。
// 一般设为: memset 10 即可;
(2)Dijkstra算法
上照片:
帅气的弗洛伊德

void dijkstra(int st){    for (int i=1;i<=n;i++) dis[i]=a[st,i];    memset(f,false,sizeof(f));    f[st]=true;  dis[st]=0;    for (int i=1;i<n;i++)    {        int min=1000000000, k=0;        for (int j=1;j<=n;j++)            if (!f[j] && (dis[j]<min))  min=dis[j],k=j;        if (k==0)  return;  //已经找不到了。        f[k]=true;  //把k加入集合1;        for (int j=1;j<=n;j++)  //三角形迭代,更新最短距离            if (!f[j] && (dis[k]+a[k][j]<dis[j]))    dis[j]=dis[k]+a[k][j];          }}Cout<<dis[en]    

时间:O(n2)

注意!!!!
Floyed: 可以有负权,无负权回路。O(n^3)
Dijkstra: 不能有负权。O(n^2)

最短路径—–Bellman-ford算法
如果边有负权的话,Dijkstra算法是错误的。这里需要不断迭代地做“松驰”,直到无“松驰”为止。
算法描述3:
SP_Bellman(G, s)
(1)初始化每点到s点的最短距离为∞
(2)取所有边(x,y),看x能否对y松驰。
(3)如果没有任何松驰,则结束break。
(4)如果松驰次数

SP_Bellman(G, s)     //求单源s到其它点的最短距离 for i=1 to n do      dis[i]  ∞                          // 初始化每点到s距离dis[s]  0                                       //将dis[s]设为0for i=1 to n do                           //最多迭代n     rel=false;                                  //是否有松驰标志     for 每条边(x,y)                          //取图的每一条边        if( dis[x]+len[x][y]<dis[y])     //不满足三角形性质            dis[y]=dis[x]+len[x][y];      //松驰dis[y]            rel=true;       if rel = false  return 0;             //没有一次松驰,则结束return -1;                                        //迭代了n次,有负圈 

SPFA在形式上和宽度优先搜索非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身会再次被改进,于是再次用来改进其它的点,这样反复迭代下去。

图的最小生成树

普里姆算法(prim)
克鲁斯卡尔(kruskal)

生成树:一个|V|个点的图,取其中|V|-1条边,并连接所有的顶点,则组成原图的一个生成树。
属性:|v|-1条边、连通、无环。
最小生成树:加权图的最小生成树是一棵生成树,其所有边的权值之和不会大于其它任何生成树。
简单讲:找出连接所有点的最低成本路线

最小生成树(MST)—–算法原理
(1)剪切属性:在图中,剪切将顶点划分成两个不相交集合。交叉边为这些顶点在两个不同集合的边。对于任何一个剪切,各条最小的交叉边都属于某个MST,且每个MST中都包含一条最小交叉边。
(2)最小边原则:图中权值最小的边(如果唯一的话)一定在最小生成树上。
(3)唯一性:一棵生成树上,如果各边的权都不相同,则最小生成树是唯一的。反之不然。

参考资料:老师的ppt