深度优先遍历和广度优先遍历_图与深度优先搜索和广度优先搜索

什么是图?

图是一种复杂的非线性表结构.由若干给定的点一级任意两点间的连线所构成.图通常用来描述事物之间的特定关系, 代表的就是事物, 线就是事物之间所具有的关系.例如社交网络就是一种典型的图关系, 每一个用户都是一个顶点,任意两个用户加了好友, 他们之间就有了一条边.

简单总结下, 图就是由顶点(vertex)边(edge)组成.

79eb80b48a605fedf00ae3e8ad06b1b9.png

除此而外, 图中还有度的概念, 和顶点相连的边数就称为度. 如果用微信来类比的话, 我们自己本身就是一个顶点, 我们微信中的好友个数, 就是度.

有向图中, 度又被分为入度(In-degree)出度(Out-degree). 入度指的是有多少个边指向这个顶点, 而出度指的是有多少个边以此顶点为 起点 指向其他的顶点.这种情况可以用微博来类比下, 你被多少人关注了就是入度, 你关注了多少人就是出度.

d8498474016da9d104718b8099c809b2.png

还有一种带权图, 每条边都会被分配一个权重(weight), 权重可以用来表示边之间的比较关系.比如时间更短, 道路状况更加良好等等.

65f9670f30735121aa5ce29666496c43.png

图的存储

  • 邻接矩阵存储
    邻接矩阵的存储, 实际上使用的是一个二维数组. 在无向图的存储中, 如果顶点 i 和 j 之间有一条边, 那么我们就将 Array [ i ] [ j ] 和 Array [ j ] [ i ] 都标记为 1. 如果是有向图, i → j, 则 Array [ i ] [ j ] 标记为 1, 反之亦然. 带权图中, 数组存储对应的权重.
    邻接矩阵存储图虽然很直观, 但是浪费空间. 在无向图中, 如果 Array [ i ] [ j ] 被标记为 1, 那么 Array [ j ] [ i ] 也必然为 1.这样我们就有一半的空间浪费掉了. 而对于顶点多, 边少的稀疏图来说, 浪费则可能更加严重.
    但它也有自己的优点, 因为是基于数组的, 所以查找两条边的关系时非高效, 还有可以将很多图的运算转换成矩阵之间的运算.
  • 邻接表存储
    邻接表其实有点像散列表.

6e4aee16238cdaf932f1beff7ae3ab30.png

图中的每一个顶点都存储一条链表, 链表中存储的是所有与它相连的顶点.

邻接表虽然在存储上节省了空间, 但在查找上却比邻接矩阵耗时.

深度优先搜索

图的搜索算法, 最直接的理解就是从某个顶点出发, 到另一个顶点的最短路径. 深度优先搜索和广度优先搜索是其中最简单最暴力的两种.

理解深度优先搜索算法最简单就是把它想象成是在走迷宫, 我们从入口开始出发, 每遇到一个岔口, 就选择其中一个, 如果有路就一直走, 直到到达目的地. 如果没有路了, 就返回上一个岔路, 重新选择一条路.

深度优先搜索使用的就是回溯思想.

class Graph {
    constructor(v) {
        this.adj = new Map()
        this.v = v
        for (let i = 0; i < v; i++) {
            this.adj.set(i, [])
        }
    }
    addEdge(s, t) {
        this.adj.get(s).push(t)
        this.adj.get(t).push(s)
    }
    getAdj() {
        return this.adj
    }
}

这里我们使用邻接表来存储图. 用深度递归来寻找 s 到 t 的最短路径.

class Graph {
  ...
  ...
   DFS(s, t) {
        const prev = new Array(this.v).fill(-1) // 存储访问过的路径
        const visited = new Array(this.v) // 存储已经访问过的节点, 避免重复访问
        let isFound = false // 是不是已经找到
        visited[s] = true
        this.recurse(s, t, prev, visited, isFound)
        printPrev(prev, s, t)
    }
    recurse(s, t, prev, visited, isFound) {
        if (isFound) {
            return
        }
        visited[s] = true
        if (s === t) {
            isFound = true
            return
        }
        let curr = this.adj.get(s)
        for (let i = 0; i < curr.length; i++) {
            let q = curr[i]
            if (!visited[q]) {
                visited[q] = true
                this.recurse(q, t, prev, visited, isFound)
            }
        }
    }
}

广度优先搜索

广度优先搜索是一种像水波一样的从中心向外扩散的策略. 从起点开始先寻找离它最近的点(可能是多个), 然后是次近的, 直到找到为止.

class Graph{
  ...
  ...
     BFS(s, t) {
        if (s === t) {
            return
        }
        const prev = new Array(this.v).fill(-1) // 存储访问路径
        const queue = [] // 存储已经访问, 但相邻顶点还没有被访问的顶点. 只有访问完 k 层的所有顶点, 才能访问第 k+1 层的.
        const visited = [] // 用来存储已经被访问过的顶点, 避免重复访问
        let isFinised = false
        queue.push(s)
        visited[s] = true
        while (queue.length !== 0 && !isFinised) {
            let w = queue.shift()
            let curr = this.adj.get(w)
            for (let i = 0; i < curr.length; i++) {
                let q = curr[i]
                if (!visited[q]) {
                    prev[q] = w
                    if (q === t) {
                        isFinised = true
                        break
                    }
                    visited[q] = true
                    queue.push(q)
                }
            }
        }
        if (isFinised) {
            printPrev(prev, s, t) //打印路径
        } else {
            console.log('not found')
        }
    }
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值