最近公共祖先LCAhtml
若是每一个结点都有一个指针指向它的父结点,因而咱们能够从任何一个结点出发,获得一个到达树根结点的单向链表。所以这个问题转换为两个单向链表的第一个公共结点(先分别遍历两个链表获得它们的长度,并求出两个长度之差。在长的链表上先遍历若干个点以后,再同步遍历两个链表,知道找到相同的结点,或者一直到链表结束)。node
不用递归recursive,迭代iterative就行git
public int query(Node t, Node u, Node v) { int left = u.value; int right = v.value; Node parent = null; //二叉查找树内,若是左结点大于右结点,不对,交换 if (left > right) { int temp = left; left = right; right = temp; } while (true) { //若是t小于u、v,往t的右子树中查找 if (t.value < left) { parent = t; t = t.right; //若是t大于u、v,往t的左子树中查找 } else if (t.value > right) { parent = t; t = t.left; } else if (t.value == left || t.value == right) { return parent.value; } else { return t.value; } } }
单链递归github
node* getLCA (node* root, node* node1, node* node2) { if(root == null) return null; if(root== node1 || root==node2) return root; node* left = getLCA(root->left, node1, node2); node* right = getLCA(root->right, node1, node2); if(left != null && right != null) return root; else if(left != null) return left; else if (right != null) return right; else return null; }
首先,Tarjan算法是一种离线算法,也就是说,它要首先读入全部的询问(求一次LCA叫作一次询问),而后并不必定按
照原来的顺序处理这些询问。而打乱这个顺序正是这个算法的巧妙之处。看完下文,你便会发现,若是偏要按原来的顺序
处理询问,Tarjan算法将没法进行。算法
处理结点10的时候并查集的状况 (假设结点是按从左到右的顺序处理的) 与10的LCA是10的结点集合:{10} 与10的LCA是8结点集合:{8 9 11} 与10的LCA是3的结点集合:{3 7} 与10的LCA是1的结点集合:{1 2 5 6} 不属于任何集合的结点:4 12
Tarjan算法是利用并查集来实现的。它按DFS的顺序遍历整棵树。对于每一个结点x,它进行如下几步操做: 计算当前结点的层号lv[x],并在并查集中创建仅包含x结点的集合,即root[x]:=x。 依次处理与该结点关联的询问。 递归处理x的全部孩子。 root[x]:=root[father[x]](对于根结点来讲,它的父结点能够任选一个,反正这是最后一步操做了)。 如今咱们来观察正在处理与x结点关联的询问时并查集的状况。因为一个结点处理完毕后,它就被归到其父结点所在的集合,因此在已经处理过的结点中(包括x自己),x结点自己构成了与x的LCA是x的集合,x结点的父结点及以x的全部已处理的兄弟结点为根的子树构成了与x的LCA是father[x]的集合,x结点的父结点的父结点及以x的父结点的全部已处理的兄弟结点为根的子树构成了与x的LCA是father[father[x]]的集合……(上面这几句话若是看着别扭,就分析一下句子成分,也可参照右面的图)假设有一个询问(x,y)(y是已处理的结点),在并查集中查到y所属集合的根是z,那么z就是x和y的LCA,x到y的路径长度就是lv[x]+lv[y]-lv[z]*2。累加全部通过的路径长度就获得答案。 如今还有一个问题:上面提到的询问(x,y)中,y是已处理过的结点。那么,若是y还没有处理怎么办?其实很简单,只要在询问列表中加入两个询问(x,y)、(y,x),那么就能够保证这两个询问有且仅有一个被处理了(暂时没法处理的那个就pass掉)。而形如(x,x)的询问则根本没必要存储。 若是在并查集的实现中使用路径压缩等优化措施,一次查询的复杂度将能够认为是常数级的,整个算法也就是线性的了。 另外,实现上还有一点技巧:树的边表和询问列表的存储能够用数组模拟的链表来实现。
给定一棵树,每条边都有必定的权值,q次询问,每次询问某两点间的距离。指针
这样就能够用LCA来解,首先找到u, v 两点的lca,而后计算一下距离值就能够了。这里的计算方法是,记下根结点到任意一点的距离dis[],这样ans = dis[u] + dis[v] - 2 * dis[lca(v, v)]了,这个表达式仍是比较容易理解的。。code
这道题和上面那道同样,很典型的LCA问题,不过读入有点麻烦,求的是每一个点被做为最近公共祖先的次数htm
某省调查城镇交通情况,获得现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间均可以实现交通(但不必定有直接的道路相连,只要互相间接经过道路可达便可)。问最少还须要建设多少条道路?
若每一个城市之间有且仅有一条道路相连,那这确定是一棵树了。边数 = 节点数 -1 ,只要咱们知道城市被分红的集合数ans,要修的道路就是ans-1 ,下面贴出通过路径压缩的并查集。
给定n我的,和m个关系,m个关系表明谁和谁认识之类的,求这样的关系中,朋友圈人数最多的人数。
这题用并查集来判断这两我的是否属于同一个朋友圈,刚开始每一个人本身造成一个朋友圈,因此每一个朋友圈人数为1,而后每碰到一个关系就判断这两我的p1,p2是否属于同一个朋友圈,若是不是,就把其中一个的f[t](t=find(p1)为这个圈子的根)改成另外一个朋友圈的根r=find(p2),而后把以r为根的这个圈子的人数c[r]加上以t为根的圈子的朋友数c[t]。这样最后只要找f[i] == i(这样的i是根)的这样的圈子的朋友数,找最大的c[i]。