go 有向图遍历及回路判断

本文探讨了如何使用Go语言进行有向图的深度优先和广度优先遍历,并介绍了利用这两种方法来判断图中是否存在回路。深度优先遍历和拓扑排序是判断回路的主要技术,通过实际的测试数据进行了验证。

有向图遍历

深度优先遍历

func depthFirst(m map[string][]string) []string {
    var order []string
    seen := make(map[string]bool)
    var visitAll func(items []string)
    visitAll = func(items []string) {
        for _, item := range items {
            if !seen[item] {
                seen[item] = true
                visitAll(m[item])
                order = append(order, item)
            }
        }
    }
    var keys []string
    for key := range m {
        keys = append(keys, key)
    }
    sort.Strings(keys)
    visitAll(keys)
    return order
}

广度优先遍历(目前遍历方向不对)

func breadthFirst(m map[string][]string) []string {
    var order []string
    seen := make(map[string]bool)
    var visitAll func(worklist []string)
    visitAll = func(worklist []string) {
        for len(worklist) > 0 {
        	items := worklist
        	worklist = nil
	        for _, item := range items {
	            if !seen[item] {
	                seen[item] = true
	                order = append(order, item)
	                worklist = append(worklist, m[item]...)
	            }
	        }
   		}
    }
    var keys []string
    for key := range m {
        keys = append(keys, key)
    }
    
    visitAll(keys)
    return order
}

测试数据

func main() {
	results := breadthFirst(prereqs)
	for i:=0; i<len(results); i++ {
		fmt.Printf("%d:\t%s\n", i+1, results[i])
	}
}

var prereqs = map[string][]string{
	"algorithms": {"data structures"},
	"calculus":   {"linear algebra"},
	"compilers": {
		"data structures",
		"formal languages",
		"computer organization",
	},

	"data structures":       {"discrete math"},
	"databases":             {"data structures"},
	"discrete math":         {"intro to programming"},
	"formal languages":      {"discrete math"},
	"networks":              {"operating systems"},
	"operating systems":     {"data structures", "computer organization"},
	"programming languages": {"data structures", "computer organization"},
}

判断回路

判断回路主要有两种方法:一种是深度优先遍历的方法,一种是拓扑排序的方法

深度优先遍历

func topoSort(m map[string][]string) []string {
	var order []string
	seen := make(map[string]int) // 初始值默认为0, 与下面notvisit状态相对应
	const (
	    notvisit = iota
		visiting  //正在访问
		visited   // 访问结束
	)
	hasRing := false
	var visitAll func(items []string)

	visitAll = func(items []string) {
		if hasRing {
			return 
		}
		for _, item := range items {
			if seen[item] == notvisit{
				seen[item] = visiting 
				visitAll(m[item])
				order = append(order, item)
				seen[item] = visited
			}else if seen[item] == visiting {
				hasRing = true
				return 
			}
		}
	}

	var keys []string
	for key := range m {
		keys = append(keys, key)
	}

	visitAll(keys) 
	if hasRing {
		fmt.Printf("found ring\n");
		order := []string
	}
	return order
}

拓扑排序

func IsContain(items []string, item string) bool {
	for _, eachItem := range items {
		if eachItem == item {
			return true
		}
	}
	return false
}


func topoSort(m map[string][]string) []string {
	var order []string
	seen := make(map[string]bool)
	hasRing := false
	count := 0 // record the visted node
	var count_last int // compare to find the ring
	
	// collect all the items
	keysMap := make(map[string]bool)
	for k, v := range m {
	    keysMap[k] = true
		for _, x := range v {
		    keysMap[x] = true
		}
	}
	var keys []string
	for key := range keysMap {
        keys = append(keys, key)
    }
	
	if len(keys) == 0 {
	    return order
	}
	
	// calculus the node's indegree
	indegree := make(map[string]int) 
	for _,k := range keys {
	    list, ok := m[k];
	    if ok {
	        indegree[k] = len(list)
	    } else {
	        indegree[k] = 0
	    }
	    
	}
	
	for {
	    if count < len(keys) {
	        count_last = count
	        for _, item := range keys {
	            if !seen[item] && indegree[item] == 0 {
					order = append(order, item)
					seen[item] = true
					count++
					// update the indegree
					for k, v := range m {
					    if IsContain(v, item){
	                        indegree[k]--
					    }
	                }
				}
	        }
	    } else {
			break
		}
		
		if count_last == count && count != len(keys)-1 {
			hasRing = true
			break
		}
	}
	
	if hasRing {
		fmt.Printf("found ring\n")
		order = []string{}
	}

	

	return order
}

测试数据

func main() {
	results := topoSort(prereqs)
	for i:=0; i<len(results); i++ {
		fmt.Printf("%d:\t%s\n", i+1, results[i])
	}
}

var prereqs = map[string][]string{
	"algorithms": {"data structures"},
	"calculus":   {"linear algebra"},
	"linear algebra": {"calculus"},
	"compilers": {
		"data structures",
		"formal languages",
		"computer organization",
	},

	"data structures":       {"discrete math"},
	"databases":             {"data structures"},
	"discrete math":         {"intro to programming"},
	"formal languages":      {"discrete math"},
	"networks":              {"operating systems"},
	"operating systems":     {"data structures", "computer organization"},
	"programming languages": {"data structures", "computer organization"},
}

参考文献:<<go语言圣经>>

使用深度优先遍历(DFS)判断有向图是否存在回路主要有以下两种常见实现思路: ### 思路一:节点状态标记法 在DFS的visited[]数组0,1状态上进行扩充,0表示未访问过,1表示已访问但还没监测完这个顶点可能出现的回路(即还没退出系统栈,处于有子结点还在访问的状态),2表示该结点已经退出系统栈(即不在递归层中)。进入DFS函数对顶点访问时,标记为1,然后访问子结点,如果子结点被访问过了,且子结点访问状态为1则表示有回路,如果子结点状态为2,则过滤掉这个结点。在即将退出递归层时,将结点标记为2,然后退出系统栈,返回上一层 [^3]。 ### 思路二:通用判断法 任取一个[未搜索]的节点x开始深度优先搜索,并将该节点标记为[搜索中]。遍历该节点x的每一个相邻节点y;如果y是[未搜索]节点,那么从y结点开始深度优先搜索;如果y是[搜索中]节点,那么代表图中有一个环;如果y是[已完成]节点,那么代表该节点的分支及子分支已经确认是无环了,不用进行操作;当x的所有相邻节点都是[已完成]时,则代表着从x出发的分支无环 [^1]。 以下是思路二的Python代码示例: ```python from collections import defaultdict class Graph: def __init__(self, vertices): self.V = vertices self.graph = defaultdict(list) def add_edge(self, u, v): self.graph[u].append(v) def is_cyclic_util(self, v, visited, rec_stack): visited[v] = True rec_stack[v] = True for neighbor in self.graph[v]: if not visited[neighbor]: if self.is_cyclic_util(neighbor, visited, rec_stack): return True elif rec_stack[neighbor]: return True rec_stack[v] = False return False def is_cyclic(self): visited = [False] * self.V rec_stack = [False] * self.V for node in range(self.V): if not visited[node]: if self.is_cyclic_util(node, visited, rec_stack): return True return False ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值