笔记_图基础

图主要的特点在于一个根可以有任意个子节点,除了会在特殊情况下形成的环形图需要注意是否会造成死循环之外,图的基本操作和使用逻辑与二叉树基本相似;但是由于它相对于二叉树的节点关系的一般性,和它本身自带的复杂性,致使操作它的时间复杂度和空间复杂度都比较差劲。

因此,我们往往不会把它当作工具去解题,更多的时候,图是题目、问题的一个背景,我们得去到图上去解题、去处理各个节点间的复杂关系。

故图的问题,主要是需要把具体的问题抽象在图中,然后以某种方式遍历完这个图,或是求图的最长链、或是找从一个点起始所能到达的某些特殊点,从而完成解题。

表示、存储

  • 相较于二叉树,图有两个痛点需要解决:

  1. 根节点所连子节点数量的不确定性

    这个特性导致用普通数组无法很便利的存储,因为数组每个维度的长度是固定的,以最多的节点去初始化数组会导致有大量的空间被浪费。

  1. 图中,所有点和点之间都有可能有联系

    i/j12
    1
    2

    若以“存储每两个点之间有无通路”为思路进行存储,则需要max(边长)的平方的空间存储,”相连“和”不相连“都需要在这个正方形的二维大图中被一一记录,大量空间被浪费。

  1. 节点间是有特定方向的

    在二叉树中,所有节点间的关系都是相同的,而图中的节点关系不同,在节点间的通路上,两节点间单、双向通路都可能存在,故在存储的时候需要表示方向。常用的方法是通过数组的维度来表示节点间的方向。例如如果采用2中的方法,要想存储有向图,则还需要另外开一个j/i数组来表示j→i的方向。

  • 解决方法:

    故我们不采用”记录每个点之间的关系”这种记录思想,而是与二叉树类似,采用“记录根节点的子节点”这种思想去记录图。

    而对于空间浪费的问题,我们采用可变长度的数组vector进行维护,一个节点每能到一个方向,表示该节点的数组长度加一并存储。

    于是就得到了pic[N][x]这样的存储方式,N表示节点名,而长度为x的pic[N]里面,存储的便是节点N能够达到的那x个节点名。如果节点有多个性质需要存储(例:节点的权值、节点的入度与出度),则可用多个辅助数组在遍历到的时候就去维护,或者直接创建结构体去存储这些性质。

图中的题目

  1. 常规遍历

  2. 遍历的一些延伸,如从某点出发,找到某种要求的点。

    • 搜索是完成1 、2操作的最直接工具,且只有搜索能够较便捷和快速的完成;目前(我学的)没有什么复杂度明显优势的算法去完成图的遍历。

    • 但是,需要注意的是,图中是可能出现环状的。遇到这种情况,大部分时候,是可以根据题目含义,重新改变图或搜索的含义与方向,仍然靠搜索完成。

      优化方法:
      • 改变搜索方向或含义,让每个节点只需到达一次就能完成操作(变向运用记忆,全局都只用搜索一次,单次自然就只会访问一次嘛)。

      • 通过再开一个数组的方法,去记录单次深搜所到达过的节点,从而避免在单次搜索中的重复访问呢。

  3. 图的长度

    因为不知道最长的链的终点是哪个节点,所以,需要分解为求每个节点最长的链的长度(max(子节点1的长度,子节点2的长度……),满足递归形式,可用递归求解),然后比较更新出链最长的点。

    代码示例:

    int dfs(int x) //节点名 该搜索采用数组a进行记忆化记录,图数组为pic[n][m]
    { //a[x]表示节点x的链长
    if(a[x]!=0) return a[x];//搜索过直接返回
    int ans=0;
    for(int i=0;i<pic[x].size();i++)
    {
    a[x]=max(ans,dfs(pic[x][i]));
    }
    return a[x]+1; //链长需要加上这个节点本身
    }
  4. 求图中链的个数

    例题:

    给出一个食物网,求其中食物链的个数。要求答案对80112002取模。

    不难发现,这其时就是给出一个无环图,要求你去求其中链的个数。

    ”找链的个数“虽然也是记忆化搜索的应用,但它需要注意与记录节点的更多属性,如入度、出度,才能完成对于一条链的划定。

    关键:

    • 什么是链?

    • 如何划分“什么是一条链”?

    • 链上的开始节点与结束节点有什么性质或特点?

       用代码回答完上述三个问题,便是最终的答案。

       其中,在寻找链时,需要分解问题。

  • 不难发现,对于每个节点来说,寻找链的过程是一个可重复的递归过程,我们可以采用采用数组去记录到达该节点的链的个数到入度为0的起始点就停止去完成搜索递归,最后去加上所有出 度为0的节点算得的链的个数。 

代码示例:

#include<bits/stdc++.h>
using namespace std;
#define mod 80112002
int n,m;
vector<int> pic[10005];
int last[10005],nxt[10005];
int bgin[10005],ed[10005];
long ans[10005];

long dfs(int x)
{
  if(ans[x]!=0) return ans[x];
  for(int i=0;i<pic[x].size();i++)  //vector数组是从0开始计数的,人无法干预 
  {
      int a=pic[x][i];
      ans[x]+=dfs(a);
      ans[x]%=mod;
  }
  return ans[x];
}
main()
{
 scanf("%d%d",&n,&m);
 for(int i=0;i<m;i++) //m种捕食关系 
 {

    int a,b;
    scanf("%d%d",&a,&b);
    if(last[a]==0) //这两个判断相当于是默认的初始化,如果已知的确切关系,没有否定这个初始化,它才能保留初始化。故需要判断这个点有没有被记录过
    last[a]=-1; //表示该节点存在,且无入度 即生产者
    if(nxt[b]==0)//同上 
    nxt[b]=-1; //表示该节点存在,且无出度 即顶级消费者
    last[b]=1; //有能量流入b  即有入度 (这是依据本次输入确切判断的,)
    nxt[a]=1; //a的能量有流出 即有出度
    pic[b].push_back(a);

 }
 int nn=0,mm=0;
 for(int i=1;i<=n;i++) //对n种动物枚举,看它们是否有入度或出度
 if(last[i]==-1) //没有入度
 {

     bgin[nn++]=i; //记录该生产者
     ans[i]=1;

 }
 else if(nxt[i]==-1) //没有出度
 ed[mm++]=i; //记录顶级消费者 

 for(int i=0;i<mm;i++)
 dfs(ed[i]); //从所有入度为0的点开始找链

 long sum=0;
 for(int i=0;i<mm;i++) //把所有出度为0的点记录的链数加起来
 {
//    printf("%d ",ed[i]);
    int a=ed[i];
    sum+=ans[a];
    sum%=mod;
 }
 printf("%ld\n",sum);
// for(int i=0;i<=n;i++)
// printf("%ld",ans[i]);
}

原来写的笔记是md文件,弄上来之后格式有点抽抽,我也不知这csdn的编辑器怎么玩,知识浅薄只能随便改一下将就着这样了。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值