图的两种遍历算法(深度优先vs广度优先)
1.深度优先算法
主要思路是从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底...,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走
2.广度优先算法
某个顶点出发,首先访问这个顶点,然后找出这个结点的所有未被访问的邻接点,访问完后再访问这些结点中第一个邻接点的所有结点,重复此方法,直到所有结点都被访问完为止
C#实现:
基础定义:
public class NodeInfo { /// <summary> /// 节点ID /// </summary> public int Id { get; set; } = 0; /// <summary> /// 是否被遍历过 /// </summary> public bool Visited { get; set; } = false; /// <summary> /// 临接元素的id /// </summary> public int[] Neighbors { get; set; } = null; /// <summary> /// 构造函数 /// </summary> public NodeInfo() { } /// <summary> /// /// </summary> /// <param name="id"></param> /// <param name="neighbors"></param> public NodeInfo(int id, int[] neighbors) { Id = id; Neighbors = neighbors; } } public class TreeInfo { /// <summary> /// 节点列表 /// </summary> public List<NodeInfo> Nodes = new List<NodeInfo>(); /// <summary> /// 填入tree中 /// </summary> /// <param name="node"></param> public void Append(NodeInfo node) { if (node == null) { return; } Nodes.Add(node); } /// <summary> /// 首个节点 /// </summary> /// <returns></returns> public NodeInfo First() { return Nodes?.FirstOrDefault(); } /// <summary> /// 搜索指定节点相邻节点 /// </summary> /// <param name="node"></param> /// <returns></returns> public List<NodeInfo> FindNeibors(NodeInfo node) { List<NodeInfo> list = new List<NodeInfo>(); foreach (var nx in node.Neighbors) { list.Add(Nodes.FirstOrDefault(k => k.Id == nx)); } return list; } }
算法实现:
/// <summary> /// 遍历树算法(深度优先 广度优先) /// </summary> public class TreeVisit { /// <summary> /// 测试遍历 /// </summary> public static void Test() { TreeInfo tree = new TreeInfo(); tree.Append(new NodeInfo(1, new int[] { 3, 2, 4 })); tree.Append(new NodeInfo(2, new int[] { 1, 5, 8, 300 })); tree.Append(new NodeInfo(3, new int[] { 1, 7, 9, 100 })); tree.Append(new NodeInfo(4, new int[] { 1, 6, 10, 200 })); tree.Append(new NodeInfo(5, new int[] { 2 })); tree.Append(new NodeInfo(6, new int[] { 4 })); tree.Append(new NodeInfo(7, new int[] { 3 })); tree.Append(new NodeInfo(8, new int[] { 2 })); tree.Append(new NodeInfo(9, new int[] { 3, 400 })); tree.Append(new NodeInfo(10, new int[] { 4 })); tree.Append(new NodeInfo(100, new int[] { 3 })); tree.Append(new NodeInfo(200, new int[] { 4 })); tree.Append(new NodeInfo(300, new int[] { 2 })); tree.Append(new NodeInfo(400, new int[] { 9,700 })); tree.Append(new NodeInfo(700, new int[] { 400 })); DFS(tree, tree.First()); //GFS(tree, tree.First()); } /// <summary> /// 深度优先遍历 /// </summary> /// <param name="tree"></param> /// <param name="startNode"></param> public static void DFS(TreeInfo tree, NodeInfo startNode) { List<NodeInfo> path = new List<NodeInfo>(); path.Add(startNode); List<NodeInfo> b = new List<NodeInfo>(); b.Add(startNode); startNode.Visited = true; Console.WriteLine(startNode.Id); NodeInfo a = new NodeInfo(); while (b.Count != 0) { //找到节点的相邻节点取第一个没有访问过的节点 a = tree.FindNeibors(b[b.Count - 1]).FirstOrDefault(k => !k.Visited); while (a != null) { b.Add(a); Console.WriteLine(a.Id); path.Add(a); a.Visited = true; //一直遍历元素的第一个节点直到没有子节点 a = tree.FindNeibors(b[b.Count - 1]).FirstOrDefault(k => !k.Visited); } if (a == null) //没有子节点就删除这个节点 取上一个节点的未被访问过的节点 { b.Remove(b[b.Count - 1]);//删除最后一个 } } } /// <summary> /// 广度优先(利用队列先进先出的规则) /// </summary> /// <param name="tree"></param> /// <param name="startNode"></param> public static void GFS(TreeInfo tree, NodeInfo startNode) { Queue<NodeInfo> b = new Queue<NodeInfo>(); b.Enqueue(startNode); List<NodeInfo> nodeList = new List<NodeInfo>() { startNode }; while (b.Count > 0) { var mq = b.Dequeue(); mq.Visited = true; Console.WriteLine(mq.Id); var neibors = tree.FindNeibors(mq)?.Where(m => !m.Visited).ToList() ?? new List<NodeInfo>(); if (neibors.Count > 0) { neibors.ForEach(m => { b.Enqueue(m); nodeList.Add(m); }); } } } }