• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
qianye0905
博客园    首页    新随笔    联系   管理    订阅  订阅
树的公共祖先问题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 }

 

posted on 2012-12-03 21:18  qianye0905  阅读(1536)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3