• 树的公共祖先问题LCA


    树的公共祖先问题可以分为在线法和离线法,在线法就是来一个请求就处理一次,
    离线法就是收集所有请求然后统一给出回复。

    1. 在线法。LCA可以转换为RMQ
    1) 对树进行dfs,访问每个节点时要记录节点的层次,同时要记录每个节点第一次出现的位置,对于节点个数为n的树,最后会形成一个2n-1长度的序列
    2) 如果要查询节点a和节点b的公共祖先,只需找出a和b节点在在序列中第一次
    出现的位置,形成了一个从a到b的子序列, 对应序列中层数最小的节点即为公
    共祖先。
    3) 因此把LCA转化为RMQ问题。对于给定序列求出指定区间中的最小值

      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <math.h>
      4 #include <malloc.h>
      5 
      6 #define max(a,b) ((a)>(b)?(a):(b))
      7 #define min(a,b) ((a)>(b)?(b):(a))
      8 
      9 struct tree{
     10     int n;
     11     int left, right;
     12 }p[100];
     13 
     14 int first[9];
     15 int sequence[20];
     16 int dep[20];
     17 int point=0;
     18 int deep=0;
     19 
     20 /* 建树 */
     21 int createTree()
     22 {
     23     int temp[8]={1,2,7,3,4,8,5,6};
     24     for(int i=0;i<8;i++)
     25         p[i].n=temp[i];
     26     p[0].left=1;p[0].right=2;
     27     p[1].left=3;p[1].right=4;
     28     p[2].left=-1;p[2].right=5;
     29     p[3].left=-1;p[3].right=-1;
     30     p[4].left=6;p[4].right=7;
     31     p[5].left=-1;p[5].right=-1;
     32     p[6].left=-1;p[6].right=-1;
     33     p[7].left=-1;p[7].right=-1;
     34     return 0;
     35 }
     36 
     37 /* 结点访问顺序是: 1 2 3 2 4 5 4 6 4 2 1 7 8 7 1 共2n-1个值 */
     38 /* 结点对应深度是: 0 1 2 1 2 3 2 3 2 1 0 1 2 1 0 */
     39 void dfs(int root)
     40 {
     41     if(p[root].n < 0)
     42         return;
     43     sequence[point]=p[root].n;
     44     dep[point]=deep;
     45     if(first[p[root].n] < 0)
     46         first[p[root].n]=point;
     47     point++;
     48     if(p[root].left > 0){
     49         deep++;
     50         dfs(p[root].left);
     51         deep--;
     52         sequence[point]=p[root].n;
     53         dep[point]=deep;
     54         point++;
     55     }
     56     if(p[root].right > 0){
     57         deep++;
     58         dfs(p[root].right);
     59         deep--;
     60         sequence[point]=p[root].n;
     61         dep[point]=deep;
     62         point++;
     63     }
     64 }
     65 
     66 int map[100][100];
     67 /**
     68  * 花费O(nlogn)的时间进行预处理 递推公式
     69  * f[i,j]=max(f[i, j-1], f[i + 2^(j-1), j-1])
     70  * @param m 存储数值的数组
     71  * @param n 数组长度
     72  */
     73 void pre_handle(int * m , int n)
     74 {
     75     memset(map, 0, sizeof(map));
     76     for(int i=0;i<n;i++)
     77         map[i][0]=dep[i];
     78     int k = (int)(log(n) / log(2));
     79     for(int i=1;i<=k;i++)       /* i表示列,j表示行, 每一列的结果只依赖于前一列 */
     80         for(int j = 0; j + pow(2,i-1) < n; j++)
     81             /* 注意因为每列都限制j + pow(2,i-1) < n,而除了第一列以外
     82              * 其它都不到n-1,所有每一行的最后一个数有可能是不对的,但
     83              * 是不影响最后结果 */
     84             map[j][i]=min(map[j][i-1], map[j+(int)pow(2, i-1)][i-1]);
     85 }
     86 
     87 int RMQ(int a, int b)
     88 {
     89     int k = (int)(log(b-a+1)/log(2)); /* 区间长度为b-a+1 */
     90     /* 两个区间之间有重合 */
     91     return min(map[a][k], map[b+1-(int)pow(2, k)][k]);
     92 }
     93 
     94 
     95 int main()
     96 {
     97     int root = createTree();
     98     memset(first, -1, sizeof(first));
     99     dfs(root);
    100     pre_handle(sequence, point);
    101     printf("%d\n", RMQ(4, 11));
    102     return 0;
    103 }

    2. 离线法。Tarjan
    算法主要是采用dfs+并查集

      1 /**
      2  * @file   code.c
      3  * @author  <kong@KONG-PC>
      4  * @date   Mon Dec 03 20:52:03 2012
      5  *
      6  * @brief  Tarjan算法,基本思想是深度优先遍历+并查集,利用并查集可以将
      7  * 查询两个数字是否在一个集合中的操作变为O(1)
      8  */
      9 
     10 #include <stdio.h>
     11 #include <vector>
     12 using namespace std;
     13 #define N 100
     14 
     15 vector<int> tree[N],request[N]; /* 用来记录子节点和对应节点的询问信息 */
     16 int ancestor[N];                /* 记录节点的祖先节点 */
     17 
     18 int visit[N];                   /* 记录节点的访问情况 */
     19 int p[N];                  /* 临时记录并查集中的父亲节点,在遍历的过程
     20                             * 中它的值会不断变化 */
     21 int rank[N];
     22 
     23 void init(int n)
     24 {
     25     for(int i=0;i<n;i++)
     26     {
     27         rank[i]=1;              /* 初始化所在集合的节点个数 */
     28         p[i]=i;
     29         visit[i]=0;
     30         ancestor[i]=-1;          /* 把所有节点的祖先节点先初始化为-1 */
     31         tree[i].clear();
     32         request[i].clear();
     33     }
     34 }
     35 
     36 /* 返回并查集的根节点 */
     37 int find(int t)
     38 {
     39     if(p[t]==t)
     40         return t;
     41     return p[t]=find(p[t]);     /* 在查询的过程中对并查集进行优化,使
     42                                  * 用路径压缩 */
     43 }
     44 /* 合并两个集合,使用启发算法,将深度较小的树指到深度较大的树的根上,
     45  * 可以防止树的退化 */
     46 int unionSet(int a, int b)
     47 {
     48     int m=find(a);              /* 首先获取m,n所在集合的根元素 */
     49     int n=find(b);
     50     if(m==n)
     51         return 0;
     52     if(rank[m]>=rank[n])        /* 判断两个集合的rank值,并把值小的并
     53                                  * 到值大的集合中 */
     54     {
     55         p[n]=m;
     56         rank[m]+=rank[n];
     57     }
     58     else
     59     {
     60         p[m]=n;
     61         rank[n]+=rank[m];
     62     }
     63     return 1;
     64 }
     65 /* 深度优先遍历+并查集 */
     66 void LCS(int t)
     67 {
     68     ancestor[t]=t;
     69     for(int i=0;i<(int)tree[t].size();i++)
     70     {
     71         LCS(tree[t][i]);
     72         unionSet(tree[t][i],t);
     73         ancestor[p[t]]=t;
     74     }
     75     visit[t]=1;
     76     for(int i=0;i<(int)request[t].size();i++)
     77         if(visit[request[t][i]]==1)
     78             printf("%d %d  %d\n", t, request[t][i],ancestor[p[request[t][i]]]); /* 这里需要知道request[t][i]所在集合的祖先节点 */
     79 }
     80 
     81 int main()
     82 {
     83     freopen("in","r",stdin);
     84     int n,t,r;                  /* 分别表示节点个数、边的个数和询问个
     85                                  * 数 */
     86     int a,b;
     87     scanf("%d%d%d",&n,&t,&r);
     88     init(n);                    /* 初始化结构 */
     89     while(t--)                  /* 读入树结构 */
     90     {
     91         scanf("%d%d",&a,&b);
     92         tree[a].push_back(b);
     93     }
     94     while(r--)                  /* 读入询问并存入对应的列表中 */
     95     {
     96         scanf("%d%d",&a,&b);
     97         request[a].push_back(b);
     98         request[b].push_back(a);
     99     }
    100     LCS(0);
    101     return 0;
    102 }
  • 相关阅读:
    Java学习第一课:搭建Eclipse+MyEclipse+Tomcat
    XFire 与 JAXWS的区别 GlassFish是Java EE 5的开源实现其中包括JAXWS
    在MyEclipse6.5上开发JAXWS web服务
    adobe acrobat professional8 .0 激活方法实施过程 记录
    MySQL MYSQL_ROW 返回的字段若是 float 如何在 C++程序中 把 字段值赋给 一个 float变量
    Arrays.sort 不区分大小写 排序
    Arrays.sort 不区分字母大小写 排序
    Applet与Servlet通讯的四种方法及其比较
    Arrays.sort 不区分大小写字母 Comparable
    利用 Arrays.sort 字符串 进行排序 完全按字符 排序 忽略字符大小写
  • 原文地址:https://www.cnblogs.com/qianye/p/3055601.html
Copyright © 2020-2023  润新知