引出

截屏2020-04-11 下午5.24.11.png

研究事物之间的关系,通过点与线具现化的感觉。抽象的关系,实体表现出来!

示例

截屏2020-04-11 下午5.45.15.png

人与人之间的联系性!

截屏2020-04-11 下午5.45.56.png

交通网络图,节点与节点之间的联系!

截屏2020-04-11 下午5.46.45.png

图结构的概念

图结构:顶点,连线!二叉树太讲究顺序,两个点之间有因果关系,主副。图结构更大的范畴了,图的顶点互相之间独立存在的!

截屏2020-04-11 下午5.51.00.png

七桥问题

截屏2020-04-11 下午6.10.38.png

图论的由来。

截屏2020-04-11 下午6.11.18.png

老师对抽象的理解也是可以的,这两天做递归算法的时候,就觉得把现实问题抽象转化是为了想要用数学知识简化问题 的。同样的还有一种逆向,就是把抽象的一些逻辑简单具象化,也比较重要的。尤其是算法题目的意思有木有明白彻底!

术语

截屏2020-04-12 下午11.06.33.png

点与边!

截屏2020-04-12 下午11.17.07.png

边的意义!路径概念!增加属性!

截屏2020-04-12 下午11.18.07.png

加属性:权重!

图的表示

截屏2020-04-13 上午12.26.08.png

上面说过图跟树的关系,树属于图,但是树太片面了,规则局限很明显的,有因果:父子关系。而图的每个顶点默认的都是相互独立的,不讲究插入顺序而导致的节点之间的关系发生变化的。
所以,各个顶点是相互独立的,就适合用数组来存储了!

邻接矩阵

截屏2020-04-13 上午12.29.28.png

看着挺眼熟的,其实就是关系型数据库里的主键表与副键表的模拟了!
二维数组是个知识点的,而且存储的数值,可提前约定好,数值也可以多用,指代权重等。
缺点就是多个点的简单的路径关系,会浪费内存空间,我觉得适合成熟的网型现象!

邻接表

截屏2020-04-13 上午11.13.09.png

ppt有错误! 是入度用的少!所以,邻接表比较常用!
其实很像哈希表的数组形式存储重复数据的结构!这里其实就是个字典。主键是key,对应一个表数据。

封装

  1. //图封装:邻接表
  2. const {Queue1} = require("./queue");
  3. const {Dictionary} = require("./dictionary");
  4. class Graph {
  5. constructor(vertexes = []) {
  6. this.vertexes = vertexes;
  7. let obj = {}, obj1 = {};
  8. if (vertexes.length) {
  9. vertexes.map(
  10. key => (obj[key] = []) && (obj1[key] = 1)
  11. );
  12. }
  13. this.edges = new Dictionary(obj);
  14. this.vertexState = obj1;
  15. this.queue = new Queue1();
  16. }
  17. hasVertex(v) {
  18. return this.vertexes.indexOf(v) >= 0;
  19. }
  20. addVertex(v) {
  21. if (!this.hasVertex(v)) return false;
  22. this.vertexes.push(v);
  23. this.edges[v] = [];
  24. this.vertexState[v] = 1;
  25. return true;
  26. }
  27. addEdge(v, v1, unidirectional) {
  28. if (!this.hasVertex(v) || !this.hasVertex(v1)) return false;
  29. const edges1 = this.edges.get(v), edges2 = this.edges.get(v1);
  30. if (edges1.indexOf(v1) < 0) {
  31. edges1.push(v1);
  32. }
  33. if (!unidirectional && edges2.indexOf(v) < 0) {
  34. edges2.push(v);
  35. }
  36. // console.log(edges2,edges1);
  37. return true;
  38. }
  39. bfs(start, callback) {
  40. callback && callback(start);
  41. this.vertexState[start] = 2;
  42. this.queue.enqueue(start);
  43. this._bfs(start, callback);
  44. this.initStates();
  45. }
  46. initStates() {
  47. Object.keys(this.vertexState).map(
  48. k => this.vertexState[k] = 1
  49. );
  50. }
  51. _bfs(start, callback) {
  52. //1代表未被访问,2代表访问所有的关联未完成,3代表所有的关联都访问完了
  53. //callback && callback(start)
  54. //this.vertexState[start] = 2
  55. // console.log(999,start);
  56. const edges = this.edges.get(start);
  57. edges.map(
  58. (key, ind) => {
  59. if (this.vertexState[key] === 1) {
  60. callback && callback(key);
  61. this.vertexState[key] = 2;
  62. this.queue.enqueue(key);
  63. }
  64. }
  65. );
  66. this.vertexState[start] = 3;
  67. this.queue.dequeue(start);
  68. // console.log(this.queue,this.vertexState,999);
  69. while (this.queue.size()) {
  70. this._bfs(this.queue.front().data, callback);
  71. }
  72. }
  73. dfs(start, callback) {
  74. this.stack = [];
  75. this._dfs(start, callback);
  76. this.stack = null;
  77. this.initStates();
  78. }
  79. _dfs(start, callback) {
  80. const {stack} = this;
  81. if (this.vertexState[start] === 1) {
  82. // console.log("ccc");
  83. callback && callback(start);
  84. this.vertexState[start] = 2;
  85. stack.push(start);
  86. }
  87. const edges = this.edges.get(start);
  88. // console.log(edges,'ee',start);
  89. const next = edges.length && edges
  90. .find(k => this.vertexState[k] === 1);
  91. // console.log(stack,'s',next);
  92. if (next) {
  93. this._dfs(next, callback);
  94. } else {
  95. this.vertexState[start] = 3;
  96. stack.pop();
  97. if (stack.length) {
  98. this._dfs(stack[stack.length - 1], callback);
  99. }
  100. }
  101. }
  102. toString() {
  103. return this.vertexes.reduce(
  104. (a, b) =>
  105. a + (a ? "\n" : "") + `${b} -> ${this.edges.get(b).join(" ")}`
  106. , ""
  107. );
  108. }
  109. }
  110. const graph = new Graph(
  111. ["a", "b", "c", "d", "e", "f", "g", "h", "i"]);
  112. graph.addEdge("a", "b");
  113. graph.addEdge("a", "c");
  114. graph.addEdge("a", "d");
  115. graph.addEdge("c", "d");
  116. graph.addEdge("c", "g");
  117. graph.addEdge("d", "g");
  118. graph.addEdge("d", "h");
  119. graph.addEdge("b", "e");
  120. graph.addEdge("b", "f");
  121. graph.addEdge("e", "i");
  122. graph.bfs("a", v => console.log(v));
  123. console.log(9);
  124. graph.dfs("a", v => console.log(v));
  125. // console.log(graph);

图的遍历:广度优先、深度优先

截屏2020-04-13 下午11.30.20.png

演绎法得出的,其实广度优先比较硬解题的意思了,按层级遍历的,不重不漏很容易的。
而深度优先的算法,比较注意细节了,倾向于走到一条路的头之后再往回走到之前的节点,看看有木有岔路,有岔路就进去走到头,再回来,回去的路上遇到岔路就进去,如此重复,就是递归了。

状态管理

这里会有疑惑,节点需要区分,访问过没访问过,分支访问探索完了?这里就有了状态意识了!

截屏2020-04-14 上午10.46.28.png

广度优先搜索

截屏2020-04-14 下午4.05.25.png

我一开始自己做的时候,没用队列。通过筛选出来所有的顶点的state为2的,遍历它们,进行后续。每一层的变量,多一次筛选。虽然做出来了,但是确实是队列比较好的。
我尝试不用队列,每层的遍历添加了一个数组来存储产生的state为2的顶点。但是失败了!我反复思考,发现是这个问题的:

  1. bfs(start,callback){
  2. callback && callback(start)
  3. //1代表未被访问,2代表访问所有的关联未完成,3代表所有的关联都访问完了
  4. this.vertexState[start] = 2
  5. this.queue = [start] //这里存储
  6. this._bfs(start,callback)
  7. Object.keys(this.vertexState).map(
  8. k=>this.vertexState[k]=1
  9. )
  10. }
  11. _bfs(start,callback){
  12. //callback && callback(start)
  13. //this.vertexState[start] = 2
  14. // console.log(999,start);
  15. const edges = this.edges.get(start)
  16. edges.map(
  17. (key,ind)=>{
  18. if (this.vertexState[key] === 1){
  19. callback && callback(key)
  20. this.vertexState[key] = 2 //修改状态
  21. this.queue.push(key) //添加新的2状态的点,这里有问题
  22. }
  23. }
  24. )
  25. this.vertexState[start] = 3 //完结了,把start的顶点标为完成
  26. this.queue.shift() //删除完成的点
  27. // console.log(this.queue,this.vertexState,999);
  28. //这里我用的是遍历:这里也错了,其实可以用while循环,强制一个执行完了再执行,
  29. //而不是map循环,无法控制。因为假如现在正在循环第二层的b,执行到上面的 this.queue.push(i),
  30. //this.queue.map的表现是,动态的,直接把i也放入跟b同一层进行遍历了,而不是下一层。
  31. this.queue.map(
  32. k=>{
  33. this._bfs(k,callback)
  34. }
  35. )
  36. }

不过,我后来,自己重新改了一下原本用数组做出来的队列的封装,我思量到底是用双链表还是单链表做?选了双链表。然后用了实在的队列进行解题,发现效果是真的好的不得了!

        //1代表未被访问,2代表访问所有的关联未完成,3代表所有的关联都访问完了
         bfs(start,callback){
        callback && callback(start)
        this.vertexState[start] = 2
        this.queue.enqueue(start)   //这是双链表实现的队列,暂时没有优先队列这一特点。

        this._bfs(start,callback)
        Object.keys(this.vertexState).map(
            k=>this.vertexState[k]=1
        )
    }

    _bfs(start,callback){
        //callback && callback(start)
        //this.vertexState[start] = 2
        // console.log(999,start);

        const edges = this.edges.get(start)

        edges.map(
            (key,ind)=>{
                if (this.vertexState[key] === 1){
                    callback && callback(key)
                    this.vertexState[key] = 2
                    this.queue.enqueue(key)
                }
            }
        )
        this.vertexState[start] = 3
        this.queue.dequeue(start)
        // console.log(this.queue,this.vertexState,999);
        while (this.queue.size()){
            this._bfs(this.queue.front().data,callback)
        }
    }

深度优先搜索

截屏2020-04-14 下午6.33.21.png

深度优先算法,我演绎了一下,发现很适合用栈的逻辑去应对的。木有看老师的视频,然后完全模拟了栈的思路:
这样完全模拟,在我看到老师的写法之后,发现自己变笨了!
先看:

         dfs(start, callback) {
        this.stack = [];  //栈
        this._dfs(start, callback);
        this.stack = null;
        this.initStates();
    }

    _dfs(start, callback) {
        const {stack} = this;
        if (this.vertexState[start] === 1) {
            // console.log("ccc");
            callback && callback(start);
            this.vertexState[start] = 2;
            stack.push(start);   //入栈
        }
        const edges = this.edges.get(start);
        // console.log(edges,'ee',start);
        const next = edges.length && edges
            .find(k => this.vertexState[k] === 1);
        // console.log(stack,'s',next);

        if (next) {
            this._dfs(next, callback);  //重复
        } else {
            this.vertexState[start] = 3;
            stack.pop();  //到栈顶了,出栈
            if (stack.length) {
                this._dfs(stack[stack.length - 1], callback); //新的重复
            }
        }
    }

而老师的栈的思想,其实就是遍历中递归,很简单的套路!

         dfs1(start, callback) {
        if (this.vertexState[start] === 1) {
            callback && callback(start);
            this.vertexState[start] = 2;
        }
        this.edges.get(start).map(
            k => {
                if (this.vertexState[k] === 1) {
                    this.dfs1(k, callback);
                }
            }
        );
              this.initStates();
    }

又对栈加深了印象了!今天的队列和栈真的是帮大忙了!