声明:题目来自: http://blog.csdn.net/v_JULY_v/archive/2010/11/17/6015165.aspx JULY整理了100道微软等公司的面试题目,我想先不看答案:http://blog.csdn.net/v_JULY_v/archive/2011/01/10/6126406.aspx 自己先做一遍。
题目:
/*第11题求二叉树中节点的最大距离...如果我们把二叉树看成一个图,父子节点之间的连线看成是双向的,我们姑且定义"距离"为两节点之间边的个数。写一个程序,求一棵二叉树中相距最远的两个节点之间的距离。 */
思路:
看到这个题目,我第一个想到的就是BFS,Breath-First-Search, 原因是我在看“算法导论”的时候见到过这个题目,算法导论第二版,第22章"Elementary Graph Algorithms" 第二节 "BFS", 的习题22.2-7 --> the diameter is the largest of all shortest path distances in the tree. Given an algorithm to compute the diameter of a tree and analyze the running time of your algorithm.
我的第一反应是:找到树的最深的叶子节点,和次深的叶子节点,这两个节点到根节点的和就是直径, 马上就否决了:
0
/
1
/ /
2 3
/
4
看上面这棵树, 4和3是最深的两个叶子节点,他们的最短距离是3,根本就不经过根节点。那是不是最深的两个叶子节点到他们的最近的共同祖先的距离和就是直径呢?也不是的,就上面的图来说,如果2有右儿子5,那么最深的两个节点 4,5到他们最近的共同祖先2的和是2,而这棵树的直径确是3.
回到BFS的思路上来,我的想法是:如果我能找到最深的叶子节点,那是不是从那个叶子节点(假设是U)出发做一遍BFS,树的直径就是U到距离U最远的节点V之间的距离?
这里的关键是证明:树的最深的叶子节点U一定在直径上!
证明: 有两种可能
a) 如果直径路径经过根节点: 反证法,如果U不在直径上,那存在另外一个X在直径上,因为直径经过根节点R,那么直径路径Distance(X->V) = Distance(X->R) + Distance (R->V); 这里,X->V 表示从X走到V,Distance(X->V)表示X走到V的最短距离。因为 U比X深,所以 Distance(U->R) > Distance(X->R), 那我们用U->R 替换 X->R的话就得到一条比直径更长的路径,于直径的定义矛盾。
b) 如果直径路径不经过根节点 :反证法。如果不经过根节点,那直径是某一个叶子节点X,和V,到他们的最近的共同祖先P的距离和, 并且U不在直径上,但是,因为U是比X更深的节点,那U到P的距离必然比X到P的距离大,于是推翻了X到V是直径的假设。
所以,算法可以分成两部:1)找到一个最深的节点 2)从那个节点开始做一次 BFS(广度优先搜索)
那么广度优先退出时的最后一个节点到在1)中找到的最深节点的距离就是直径大小。
那如何找到最深的那个节点呢?我最初的想法就是做一次 先序遍历: 伪代码如下
void getDeepestNode(Node* root, int currentLevel, Node** deepestNode, int* deepestLevel){
Node* leftDeepestNode, rightDeepestNode;
int* leftDeepestLevel, rightDeepestLevel;
if(root->left) {
getDeepestNode(root->left, currentLevel+1, &leftDeepestNode,&leftDeepestLevel);
}else{
leftDeepestNode = root; leftDeepestLevel = currentLevel;
}
if(root->right) {
getDeepestNode(root->right, currentLevel+1, &rightDeepestNode,&rightDeepestLevel);
}else{
rightDeepestNode = root; rightDeepestLevel = currentLevel;
}
然后比较 leftDeepestLevel和rightDeepestLevel,那个深,返回哪个
}
后来我在网上搜了一下,这种求直径的一个通用的算法是:(不但适用于树,并且适用于图)
1. 从任意一个节点u开始做第一遍BFS,得到距离u最远的那个节点v
2. 从节点v开始做第二遍BFS,得到距离v最远的节点 e, 那 v 到 e 就是直径。
这里要证明 v 必然在直径路径上!!
1. 如果 u 在直径路径上:反证法, 如果v不在直径上,那根据直径的定义,必然存在一点v2在直径上,使得dist(u->v2) 大于 dist(u->v), 而这和BFS算法 v是从u 出发到达的所有节点中离u最远相矛盾。
2. 如果 u 不在直径路经上, 反证法,看图:
u ----- w ------- v
/
x ----------y ----------z
上图中,u-->v 是第一遍BFS算出来的路径,x-->z 是直径路径,反证法假设v 不在直径路径上,如图所示。根据树和联通图的定义,u->v中必然存在一点w, 和x->z中的某点y 相连通,或者说必然存在一个路径 w--y ,链接uv和xz。
根据直径的定义: Dist(xz) = Dist(xy) + Dist(yz) , Dist(yz) >= Dist(yw) + Dist(wv) , 不然直径就变成x->y->w->v了。
根据BFS的定义: Dist(uv) = Dist(uw) + Dist(wv) , Dist(wv) >= Dist(wy) + Dist(yz), 不然BFS最短路径就变成 u->w->y->z了。
要让上面的不等式成立,唯一的可能是 Dist(yw)=0 并且 Dist(wv)=Dist(yz), 也就是说 uv 必须和xz相交,并且相交后的部重合,也就意味着v 一定在最终的直径路径上!
代码:
import java.util.*;
public class TreeDiameter {
static class TreeNode{ int m_data; TreeNode[] m_adjacents = new TreeNode[3]; //0 for parent, 1 for left child, 2 for right child public TreeNode(int data){ this.m_data = data; } } enum Color{ WHITE, GRAY, BLACK; } static class NodeInfo{ int distance; Color color; TreeNode previousNode; public NodeInfo(){ distance = 0; color = Color.WHITE; previousNode = null; } } TreeNode buildTree(){ /* Constructed binary tree is 0 / / 1 2 / / 3 4 */ TreeNode[] arr = new TreeNode[5]; for(int i=0; i< arr.length; i++){ arr[i] = new TreeNode(i); } arr[0].m_adjacents[0] = null; arr[0].m_adjacents[1] = arr[1]; arr[0].m_adjacents[2] = arr[2]; arr[1].m_adjacents[0] = arr[0]; arr[1].m_adjacents[1] = arr[3]; arr[1].m_adjacents[2] = arr[4]; arr[2].m_adjacents[0] = arr[0]; arr[3].m_adjacents[0] = arr[1]; arr[4].m_adjacents[0] = arr[1]; return arr[0]; }
TreeNode getTreeDiameterBFS(TreeNode root, NodeInfo[] nodeinfo){ //initialize for(NodeInfo i: nodeinfo){ i.distance = 0; i.color = Color.WHITE; i.previousNode = null; } List<TreeNode> queue = new LinkedList<TreeNode>(); if(root != null){ queue.add(root); } TreeNode lastNode = null; while(queue.size() != 0){ TreeNode node = queue.remove(0); //get the fist element of the queue //now, let's iterate all the adjacent nodes for(TreeNode v: node.m_adjacents){ if(v != null && nodeinfo[v.m_data].color == Color.WHITE){ nodeinfo[v.m_data].color = Color.GRAY; nodeinfo[v.m_data].distance = nodeinfo[node.m_data].distance + 1; nodeinfo[v.m_data].previousNode = node; //push this new detected node into queue queue.add(v); } } //when finish processing node, we should mark that node as black nodeinfo[node.m_data].color = Color.BLACK; //remember the last node processed in the queue lastNode = node; } return lastNode; } int getDiameter(TreeNode root, int numberOfNodes){ NodeInfo[] nodeinfo = new NodeInfo[numberOfNodes]; for(int i=0; i< nodeinfo.length; i++){ nodeinfo[i] = new NodeInfo(); } TreeNode lastNode1 = getTreeDiameterBFS(root, nodeinfo); System.out.println("One node in diameter is Node[" + lastNode1.m_data + "]"); //let's do another round of BFS from lastNode TreeNode lastNode2 = getTreeDiameterBFS(lastNode1,nodeinfo); System.out.println("The other node in diameter is Node[" + lastNode2.m_data + "]"); System.out.print("The path is: " + lastNode2.m_data + " "); while(nodeinfo[lastNode2.m_data].previousNode != null){ lastNode2 = nodeinfo[lastNode2.m_data].previousNode; System.out.print(lastNode2.m_data + " "); } return 0; } public static void main(String[] args){ TreeDiameter app = new TreeDiameter(); TreeNode root = app.buildTree(); int result = app.getDiameter(root,5); }}
其他解法:
参见: http://geeksforgeeks.org/?p=5687 用递归的方式解得,基本思想是:
The diameter of a tree T is the largest of the following quantities:
* the diameter of T’s left subtree* the diameter of T’s right subtree* the longest path between leaves that goes through the root of T (this can be computed from the heights of the subtrees of T)