算法笔记(有根树遍历)
来源:互联网 发布:iphone视频滤镜软件 编辑:程序博客网 时间:2024/05/08 16:41
public class TreeNode { public string Key { get; set; } public object Data { get; set; } public TreeNode Parent; public List<TreeNode> Children { get; set; } public TreeNode(string Key,object Data) { Children = new List<TreeNode>(); this.Key = Key; this.Data = Data; } public void AddChild(TreeNode node) { this.Children.Add(node); node.Parent = this; } public void DelChild(TreeNode node) { this.Children.Remove(node); } } public class Trees { public TreeNode Root { get; set; } /// <summary> /// 非递归遍历树方法,这里采用的先父后子的顺序遍历.如果要先子后父的话,则需要用到两个栈,两个循环. /// </summary> /// <param name="ListCurrNode"></param> public void ListChildren(Action<TreeNode> ListCurrNode) { if (Root == null) { return; } //先父后子 Stack<TreeNode> _nodesStack1 = new Stack<TreeNode>(); while (_nodesStack1.Count > 0) { TreeNode node = _nodesStack1.Pop(); ListCurrNode(node); foreach (var item in node.Children) { _nodesStack1.Push(item); } } //先子后父.其实对于二叉树的左序和右序访问,也需要两个栈来实现. _nodesStack1.Clear(); _nodesStack1.Push(Root); Stack<TreeNode> _nodesStack2 = new Stack<TreeNode>(); while (_nodesStack1.Count > 0) { TreeNode node = _nodesStack1.Pop(); _nodesStack2.Push(node); foreach (var item in node.Children) { _nodesStack1.Push(item); } } while (_nodesStack2.Count > 0) { TreeNode node = _nodesStack1.Pop(); ListCurrNode(node); } } /// <summary> /// 非递归中序,线性时间,常数额外辅助空间方式,不能修改树(临时的也不行),可以遍历任意的叉树.对于K叉树,每次退回的比较时间为<=K, /// 因为每个节点至多需退回一次,则退回比较为<=Kn次,树遍历本身的O(n).遍历还是线性时间。 /// 其实如果可以修改树,比如设置访问标志,则直接可以退回,然后继续访问第1个未访问的子节点。 /// 下面的算法其实就是利用这种思想,只是不是直接设置标志,而是利用了数组指针的特性。 /// 对于左子右兄弟的表达是任意叉树也可以实现,只是退回遍历方法不同而已(更简单). /// 如果采用的是K个孩子指针变量的表示孩子节点的方式,也是适用的,只是需要默认一个访问逻辑顺序即可. /// 注意:这里其实采用的是先父后子的访问顺序,如果需要先子后父,也是可以的,需要增加一个子访问完成退回标记,稍稍修改 /// 一下代码即可. /// </summary> /// <param name="ListCurrNode">遍历访问者</param> public void ListAll1(Action<TreeNode> ListCurrNode) { int theQuitFrom = -1;//-1表示从父节点来,0-n表示从第k个孩子回来. TreeNode theCurrNode = Root; while (theCurrNode != null) { TreeNode theTmp = null; if (theQuitFrom < 0)//刚从父节点过来(theQuitFrom=-1) { ListCurrNode(theCurrNode); //从父节点过来时,访问第1个孩子(索引为0). if (theCurrNode.Children.Count > 0) { theTmp = theCurrNode.Children[0]; } else { theTmp = null; } } else //从孩子节点遍历退回来,则去下一个孩子节点。 { if (theQuitFrom + 1 < theCurrNode.Children.Count) { theTmp = theCurrNode.Children[theQuitFrom + 1]; theQuitFrom = -1; } else//孩子节点访问完成,theTmp置空,继续执行后退策略. { theTmp = null;//如果要先子后父,需要在这里做标记 } } if (theTmp == null) //如果theTmp为空,则表示无法继续向下遍历,需后退访问兄弟节点 { //如果当前节点是root,表示已经需从root退回,则表示整个树遍历完成, //这里也保证了GetChildrenIndex不会返回-1的情况. if (theCurrNode == Root) break; theQuitFrom = GetChildrenIndex(theCurrNode.Parent, theCurrNode); theCurrNode = theCurrNode.Parent; } else //不为空,则将theTmp节点作为当前节点进行访问。 { //这种情况下,theQuitFrom一定是-1.因为初始为-1,只有退回的时候才会改变这个值, //如果退回有下一个兄弟可访问,则theQuitFrom一定会被设置为-1,如果没有,则只能继续 //退.如果能继续访问,theQuitFrom一定保持着-1. theCurrNode = theTmp; } } } /// <summary> /// 获取孩子节点在父节点中的位置. /// </summary> /// <param name="Parent"></param> /// <param name="Child"></param> /// <returns></returns> private int GetChildrenIndex(TreeNode Parent, TreeNode Child) { for (int i = 0; i < Parent.Children.Count; i++) { if (Parent.Children[i] == Child) { return i; } } //实际上这里放回-1是没法执行的. return -1; } }
本文参考了CSDN网友的文章:http://blog.csdn.net/acrazer/article/details/4454508