相关概念

顶点

方向

有向图

入度

出度

无向图

权重

带权图

无权图

带权有向图

带权无向图

存储

邻接矩阵

简单,直观,但比较浪费空间。使用比较方便,空间换时间。

邻接表

存储比较节省空间,但是使用比较耗时。因为邻接矩阵是数组存储,邻接表是链表存储,不连续,不支持随机读取。想知道两个顶点间是否存在一条边,得遍历这两个点所有点,对比。

优化:可以将链表换成其他相对高效的动态数据结构如:二叉查找树,跳表,散列表等。

遍历

广度搜索

时间复杂度

广度搜索过程中,queue中所有的顶点要入一次队列,所有顶点要访问一遍记为V,visited保存每条边也要访问一次记为E,所以时间复杂度为:O(V+E)。

广度搜索应用

最短路径

二度好友/可能认识的人

深度优先搜索

走迷宫

时间复杂度

从下面代码实现可以看出,每条边至多访问两次,一次是遍历,一次是回退,所以时间负载度为O(E),E为边数。

空间复杂度

实现中的变量主要有visited,prev与程序运行时候的递归调用栈,这些都跟图的顶点个数V成正比,所以空间复杂度为O(V)。

代码实现

package main

import (
	"fmt"
	"sync"
)

type Nodee struct {
	Value interface{}
}

type Graphh struct {
	Nodees []*Nodee
	Edges  map[Nodee][]*Nodee
	Lock   sync.RWMutex
}

func (this *Nodee) String() string {
	return fmt.Sprintf("%v", this.Value)
}

func NewNode(v interface{}) *Nodee {
	return &Nodee{Value: v}
}

func NewGraph() *Graphh {
	return &Graphh{
		Nodees: []*Nodee{},
		Edges:  map[Nodee][]*Nodee{},
		Lock:   sync.RWMutex{},
	}
}

func (this *Graphh) AddNodee(v *Nodee) {
	this.Lock.Lock()
	defer this.Lock.Unlock()

	if this.Edges == nil {
		this = NewGraph()
	}

	this.Nodees = append(this.Nodees, v)
}

func (this *Graphh) AddEgde(s, t *Nodee) {
	this.Lock.Lock()
	defer this.Lock.Unlock()

	if this.Edges == nil {
		this = NewGraph()
	}

	this.Edges[*s] = append(this.Edges[*s], t)
	this.Edges[*t] = append(this.Edges[*t], s)
}

func (this *Graphh) String() string {
	this.Lock.Lock()
	defer this.Lock.Unlock()

	s := ""
	if this.Edges == nil || len(this.Nodees) == 0 {
		return s
	}

	for _, nodeV := range this.Nodees {
		s += nodeV.String() + " --> "
		for _, item := range this.Edges[*nodeV] {
			s += item.String() + " "
		}
		s += "\n"
	}

	return s
}

func (this *Graphh) BFS(s, t *Nodee) (b bool, prev map[Nodee]*Nodee) {
	this.Lock.Lock()
	defer this.Lock.Unlock()

	if s == t {
		return b, prev
	}

	// 初始化辅助变量
	queue := []Nodee{}
	visited := make(map[Nodee]bool, len(this.Nodees))
	for _, node := range this.Nodees {
		visited[*node] = false
	}
	prev = make(map[Nodee]*Nodee)

	queue = append(queue, *s)
	visited[*s] = true

	for len(queue) != 0 {
		q := queue[0]
		queue = queue[1:]

		for _, node := range this.Edges[q] {
			if visited[*node] {
				continue
			}

			prev[*node] = &q
			if node == t {
				PrintPrev(prev, s, t)
				b = true
				return b, prev
			}

			queue = append(queue, *node)
			visited[*node] = true
		}
	}

	return b, prev
}

func (this *Graphh) DFS(s, t *Nodee) {
	// 初始化辅助变量
	found := false
	visited := make(map[Nodee]bool, len(this.Nodees))
	for _, node := range this.Nodees {
		visited[*node] = false
	}
	visited[*s] = true
	prev := make(map[Nodee]*Nodee)

	this.dfsCore(s, t, visited, prev, found)

	PrintPrev(prev, s, t)
}

func (this *Graphh) dfsCore(w, t *Nodee, visited map[Nodee]bool, prev map[Nodee]*Nodee, found bool) {
	if w == t {
		//b, prevRes = false, nil
		return
	}

	if visited == nil {
		visited = make(map[Nodee]bool, len(this.Nodees))
	}

	for _, nodePoint := range this.Edges[*w] {
		if visited[*nodePoint] {
			continue
		}

		prev[*nodePoint] = w
		visited[*nodePoint] = true

		if t == nodePoint {
			found = true
			//b, prevRes = true, prev
			return
		}

		this.dfsCore(nodePoint, t, visited, prev, found)
	}

	return
}

func PrintPrev(prev map[Nodee]*Nodee, s, t *Nodee) {
	if prev[*t] != nil && s != t {
		PrintPrev(prev, s, prev[*t])
	}

	fmt.Printf("%+v ", t)
}

func main() {
	node1 := NewNode(1)
	node2 := NewNode(2)
	node3 := NewNode(3)
	node4 := NewNode(4)
	node5 := NewNode(5)

	g := NewGraph()
	g.AddNodee(node1)
	g.AddNodee(node2)
	g.AddNodee(node3)
	g.AddNodee(node4)
	g.AddNodee(node5)

	g.AddEgde(node1, node2)
	g.AddEgde(node1, node5)

	g.AddEgde(node2, node3)
	g.AddEgde(node2, node4)
	g.AddEgde(node2, node5)

	g.AddEgde(node3, node4)

	g.AddEgde(node4, node5)

	s := g.String()
	fmt.Println(s)


	fmt.Println("dfs res:")
	g.DFS(node1, node4)

}

总结

广度优先搜索和深度优先搜索是图上的两种最常用、最基本的搜索算法,比起其他高级的搜索算法,比如 A*、IDA* 等,要简单粗暴,没有什么优化,所以,也被叫作暴力搜索算法。所以,这两种搜索算法仅适用于状态空间不大,也就是说图不大的搜索。

广度优先搜索,通俗的理解就是,地毯式层层推进,从起始顶点开始,依次往外遍历。广度优先搜索需要借助队列来实现,遍历得到的路径就是,起始顶点到终止顶点的最短路径。

深度优先搜索用的是回溯思想,非常适合用递归实现。换种说法,深度优先搜索是借助栈来实现的。在执行效率方面,深度优先和广度优先搜索的时间复杂度都是 O(E),空间复杂度是 O(V)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值