• 树的公共祖先问题LCA


    在一棵树中,每个节点都有0个或者多个孩子节点,除了根节点以外每个节点都有一个双亲节点,从根节点到当前节点的路径上的节点都是当前节点的祖先节点, 现任意给定两个节点,要求它们的公共祖先,并且这个公共祖先离它们最近

    这里有两种算法:在线法和离线法.

    在线法:指每提出一次请求,便给出一次应答

    离线法:收集所有的请求,然后统一进行处理

    在线法 dfs+RMQ

    在线法主要借助了RMQ的思想,先对树进行深度优先遍历,对于含有n个节点的树,深度优先遍历产生的序列长度为2n-1, 遍历的过程中,记录每个节点第一次出现的位置,并记录序列中每个节点的层次。当询问节点a和节点b之间的最近公共祖先时,首先找到节点a和节点b第一次在遍历序列中出现的位置,它们之间有一段遍历序列,要想找离a和b最近的祖先,只用找出这段序列中层次最小的节点即可,而借助于RMQ的知识可以经过O(nlogn)的预处理,然后在O(1)的时间内找出最值,深度优先遍历的时间复杂度是O(n),因此总的时间复杂度是O(nlogn)

      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 }

    离线法 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 }
  • 相关阅读:
    法瑞意游记+攻略 一(巴黎 凡尔赛、荣军院,十二月二十六)
    [转]Exchange Server 2013部署系列之一:部署环境介绍
    [转]在Windows server 2012上部署DPM 2012 SP1 RTM之安装配置
    DinnerNow案例分析
    [转]DPM2012系列之六:在Win7上安装DPM远程管理控制台
    Windows Phone Dev Center Develop
    [转]DPM2012系列之十:备份exchange2010数据库
    [转]DPM2012系列之五:开启最终用户恢复功能
    Windows Phone Dev Center How do I join?
    [转]DPM2012系列之三:浅谈硬件需求
  • 原文地址:https://www.cnblogs.com/qianye/p/2800384.html
Copyright © 2020-2023  润新知