关于图论

进来也不是一两天了,成为一个OIer的日子也过了许久。那么就谈谈自己对最近所学的心得吧。

图(she)论(pi)

遍历与最短路

Floyd

  • 神奇的三重循环,一层套一层,适用于500个点以内的题目。

    • 关于floyd
      1. k循环一定要套在外面!!!
      2. 看到500别冲动,抑制住你的兴奋和激动,500点的不一定是floyd。]![*(要控几你暨几)*
  • 献上代码

//N为点数
void floyd()
{
    for(int k=1;k<=N;k++)
      for(int i=1;i<=N;i++)
        forint j=1;j<=N;j++)
          if(a[i][k]+a[k][j]<a[i][j])
             a[i][j]=a[i][k]+a[k][j];
//floyd能把整个图弄好,so cool right?
}

Dijkstra

  • 同样是个神奇的东西它能计算某一顶点到其它所有顶点的最短路径。时间复杂度为O(n^2),用斐波那契堆的复杂度O(E+n*lg n)

    • 关于Dijkstra
      1. 用了迪哥的图不能有负权,否则就GG了。
      2. 算法思想:
        (1). 将所有的点放入两个集合内,并将初始点start放入集合1。
        (2).在集合2中找一个到start距离最近的顶点k ,距离=d[k]。
        (3).把顶点k加到集合1中,同时修改集合2 中的剩余顶点j的d[j]是否经过k后变短。如果变短修改d[j]。
        (4).重复(2),直至集合2空为止。
      3. 迪哥是图灵奖的获得者…………嗯。
  • ——————————–———————————–

  • 献上代码
const int maxm=20011//总之定义一个很大的数
int dis[maxm];
bool flag[maxm]={};
void Diskstra(int st)//st为起点作最短路
{
        for(int i=1;i<=N;i++)
          dis[i]=a[st][i];
        flag[st]=1;
        dis[st]=0;
        for(int i=1;i<=n;i++)
        {
            int min=1000000,k=0;
            for(int j=1;j<=N;j++);
              if(!flag[j]&&dis[j]<min)
                min=dis[j],k=j;
            if(k==0) return;
            flag[k]=1;
            for(int j=1;j<=N;j++)
              if(!flag[j]&&dis[k]+a[k][j]<dis[j])
                dis[j]=dis[k]+a[k][j];//三角形迭代,更新最短路
        }
} 
.
.
.
.
.
//主程序省略掉
cout<<dis[end];//end为终点

Bellman_Ford

  • 同样为单源最短路,对于迪哥算法来说出现负权是件很尴尬的事情,于是乎,就应运而生了BF这个神(gui)奇(chu)的东西(其实图论的东西都很神奇)。
  • 这个通过三角形的迭代进行松弛,直到松不了为止。时间复杂度为O(n*E)
    (E为边数)。
    • 关于Bellman_Ford
      1. 它能够确定是否有负权回路。
      2. 算法思想:
        (1).初始化每点到s点的最短距离为∞
        (2).取所有边(x,y),看x能否对y松驰。
        (3).如果没有任何松驰,则结束break。
        (4).如果松驰次数<N,转(2)。
        (5).如果第n次还能松弛,图中有“负圈”。
      3. 注意事项:
        (1).用有序点对(x,y)记录边时,可直接取边。但要请注意对无向图,要注意(y,x)也要松驰。
        (2).对于求s到某点t的最短距离,可能因为其它地方有“负环”而出现问题,要预处理。
  • 献上代码
const int maxm=20011;
struct node{
    int head,tail,v;
};
node bian;
int dis[maxm];
bool flag=0;
int Bell_Ford(int st)//st为起点
{
    memset(dis,10,sizerof(dis));
    dis[st]=0;
    for(int i=1;i<=N;i++)//N为点数
    {
        flag=0;
        for(int j=1;j<=num;j++)//num为边数 
            if(dis[bian[j].head]+bian[j].v<dis[a[j].tail])//head为之前的点,v是边的权值,tail就是后面的
            {
                dis[a[j].tail]=d[a[i].head]+a[i].v;
                flag=1;//标记迭代 
            } 
        if(!flag)
            return dis[end];//end为终点
    } 
    return -1;
}

SPFA

  • Bellman-ford算法中,每次都要检查所有的边。这个看起来比较浪费,对于边(x,y),如果上一次dis[x]没有改变,则本次的检查显然是多余的。这SPFA算是对Bellman_Ford的优化吧。
    我们每次只要从上次刚被“松驰”过的点x,来看看x能不能松驰其它点即可。
    SPFA算法中用BFS中的队列来存放刚被“松驰”过的点。由于顶点个数为|V|,队列如果用数组的话显然要用“循环队列”使用空间。
  • SPFA在形式上和宽度优先搜索非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身会再次被改进,于是再次用来改进其它的点,这样反复迭代下去。
    —————————-(怎么突然间这么理论了…………)—————————–
    • 关于SPFA
      好像没什么要讲的……………………
  • 献上代码
const int maxm;
int dis[maxm],duilie[maxm],;
bool flag[maxm]={};
void spfa(int st)
{
    memset(dis,10,sizeof(dis));
    int head=0,tail=1;//队头队尾
    dis[st]=0;
    duilie[tail]=st;//看名字就知道是队列了
    flag[st]=1;
    while(head<=tail)
    {
        int tn=duilie[++head];
        for(int i=linkk[tn];i;i=a[i].next)//邻接表储存……
        {
            int temp=a[i].y;
            if(dis[tn]+a[i].v<dis[temp])
            {
                dis[temp]=dis[tn]+a[i].v;
                if(!flag[temp])
                flag[temp]=1,duilie[++tail]=temp;
            }
        }
        flag[tn]=0;
    }
}
.
.
.
.
cout<<dis[end];//不解释

最小生成树(MST)

一些MST的属性
  1. 剪切属性:
    在图中,剪切将顶点划分成两个不相交集合。交叉边为这些顶点在两个不同集合的边。对于任何一个剪切,各条最小的交叉边都属于某个MST,且每个MST中都包含一条最小交叉边。
  2. 环属性:
    一棵生成树上,增加一条边e,再删除e所在环上的最大边,会得到另一棵“更好”的生成树(如果e不是最大边)
  3. 最小边原则:
    图中权值最小的边(如果唯一的话)一定在最小生成树上。
  4. 唯一性:
    一棵生成树上,如果各边的权都不相同,则最小生成树是唯一的。反之不然。

prim

  • 和迪哥超级像的的一个算法
    • 关于prim
      1. 算法思想 :
        (1).将G(树)剪切成两个集合A、B;A中只有一个点r。
        (2).取最小权的交叉边(x,y),x∈A, y∈B。
        (3).将y加入A
        (4).如果已经加了n-1条边,结束。否则,转 (3)。
      2. 算法要点:
        每次求最小权交叉边时,如果都重新计算,则显然要枚举(x,y)— x∈A ,y∈B。O(n^2)时间复杂度。
        其实每次A中只是增加一个新顶点v,最多有交叉边(v,y),修改量只有与v有边的顶点,为O(n)。
        只需记录下B中的每个元素y与A所有元素中最小权边,则求最小值最多为O(n)—有时可以用“堆”优化。(stl的set)
  • 献上代码
const int maxm=20011;
bool flag[maxm]={};
int dis[maxm],ans;
void prim()
{
    memset(dis,10,sizeof(dis);
    //还要给dis数组赋个距离啊什么的
    dis[1]=0;
    flag[1]=1;
    for(int i=1;i<n;i++)
    {
        int minn=dis[0],k=0;//这个其实很骚的,自己体会
        for(int j=1;j<=n;j++)
            if(!flag[j]&&dis[j]<minn)
                minn=dis[j],k=j;
        flag[k]=1;
        ans+=dis[k];//ans为最小生成树的距离总和 
        for(int j=1;j<=n;j++)
            if(flag[j]!=1&&dis[j]>a[k][j])
                dis[j]=a[k][j];     
    }
}

Kruskal

  • 按边来的MST
    • 关于Kruskal
      1. 算法思想:
        (1).将G所有条边按权从小到大排序;图mst开始为空
        (2).从小到大次序取边(x,y)
        (3).若加入边(x,y),mst就有环,则放弃此边,转(2)
        (4).将边(x,y)加入mst,如果已经加了n-1条边,结束。否则,转 (2)
      2. 算法要点:
        Kruskal算法的最难点在于怎样判断加入边(x,y)后是否形成了环。
        判断边(x,y)的两个顶点x,y在图(实际是森林)MST中最否已经连通。如果已经连通,加入边将形成环;否则,不形成环。
        ps:连通点集之类问题,有高效算法—并查集(翔哥的找爸爸)。
struct Road{        //简易的定义一条边  
    int a, b;   //一条边的两个顶点  
    int w;      //边的权值  
};  
Road road[m];  
int v[maxSize] = {0};       //定义并查集数组  

int getRoot(int a)      //在并查集中找根节点的函数  
{  
    while(a != v[a]) a = v[a];      //自己为自己双亲时,为根节点  
    return a;  
}  

void Kruskal(MGraph g, int &sum,Road road[])   //克鲁斯卡尔方法  
{  
    int i,a,b;  
    sum=0;  
    for(i=0;i<g.n;++i) v[i]=i;  //各自为根节点(一棵树)  
    sort(road,g.e);            //对所有边进行排序  
    for(i=0;i<g.e;++i)        //排过序的边依次判断 
    {     
        a=getRoot(road[i].a);  
        b=getRoot(road[i].b);  
        if(a!=b)           //不同根时,将该边并入(此时不会形成回路) 
        {      
            v[a]=b;      // b成为a的双亲结点  
            sum+=road[i].w;  
        }  
    }  
}  
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值