javascript 数据结构与算法 笔记2.2

笔记2.1

第七章 字典

image.png

封装基本Api

            function Dictionary() {
                var items = {};
                
                this.has = function (key) {
                    return items.hasOwnProperty(key)
                }
                
                this.set = function (key,value) {
                    items[key] = value;
                }
                
                this.delete = function (key) {
                    if(this.has(key)){
                        delete items[key]
                        return true
                    }
                    return false
                }
                this.get = function (key) {
                    return items[key]
                    // 如果items[key] 不存在, 好像不会报错, 而是直接返回 undefined
                }
                this.values = function () {
                    var arr = [];
                    for (let key in items) {
                        if(this.has(key)) {
                            arr.push(items[key])
                        }
                    }
                    return arr
                    / 或者
                    return Object.values(items)
                }
                this.keys = function () {
                    var arr = [];
                    for (let key in items) {
                        if(this.has(key)) {
                            arr.push(key)
                        }
                    }
                    return arr
                    / 或者
                    return Object.keys(items)
                }
                this.size = function () {
                    let len = 0;
                    for(let key in items) {
                        if (this.has(key)) {
                            len++
                        }
                    }
                    return len
                    /或者
                    return Object.keys(items).length
                }
                
                this.getItems = function () {
                    return items
                }
                
            }

其实跟上面集合的封装非常相似. 只不过 集合用的是 值 值
字典用的是 键 值
不过这么封装 和上面的 集合封装一样,
有个缺陷就是, key 都会 隐式转换成字符串,
有些 key 会互相覆盖 比如 1 和 '1',
并且引用值无法作为 key,
可以考虑用 Map

7.2 散列表 到HashTable类

散列算法的作用是尽可能快地在数据结构中找到一个值。在之前的章节中,你已经知道如果
要在数据结构中获得一个值(使用get方法),需要遍历整个数据结构来找到它。如果使用散列
函数,就知道值的具体位置,因此能够快速检索到该值。散列函数的作用是给定一个键值,然后
返回值在表中的地址。

lose lose 散列函数


image.png

其实主要看这个函数,

                var loseloseHashCode = function(key) {
                    var hash = 0; //{1}
                    for(var i = 0; i < key.length; i++) { //{2}
                        hash += key.charCodeAt(i); //{3}
                    }
                    return hash % 37; //{4}
                };

这个函数的作用是, 把传进来的key值 经过计算 变成一个数字,
让这个数字成为 新的key

这么做的第一个好处是, 能够把1 和 '1' 区分开来, 不会互相覆盖.
因为'1' 的值会转换成 charCodeAt(i) (忘了是ask码 还是 unicode码了)

不过很明显是有缺陷的, 比如 '1' 的 charCodeAt(i) 值是 47
换言之, '1' 和 数字 47 是互相覆盖的.
这称之为冲突

这个函数第{4} 行 是对 37 做了个取模,
换言之一旦不同的数据超过37个, 一定会发生互相冲突的情形.

这个函数有很多缺陷, 比如这个函数似乎只处理了 key 为 字符串的情形.
我们先把其他api 写一下

                this.put = function(key, value) {
                    var position = loseloseHashCode(key); //{5}
                    console.log(position + ' - ' + key); //{6}
                    table[position] = value; //{7}
                };
                this.remove = function(key) {
                    table[loseloseHashCode(key)] = undefined;
                };
                this.get = function(key) {
                    return table[loseloseHashCode(key)];
                };

如何解决冲突?

1. 分离链接

image.png

这样做,实际上就变成了, 根据lose 函数返回的不同值,创建不同的链表.
之前跟丁老师学习模拟 map 封装时, 用的就是这个方法.

            function HashTable() {
                var ValuePair = function(key, value) {
                    this.key = key;
                    this.value = value;
                    this.toString = function() {
                        return '[' + this.key + ' - ' + this.value + ']';

                    }
                }
                this.put = function(key, value) {
                    var position = loseloseHashCode(key);
                    if(table[position] == undefined) { //{1}
                        table[position] = new LinkedList();
                    }
                    table[position].append(new ValuePair(key, value)); //{2}
                };
                this.get = function(key) {
                    var position = loseloseHashCode(key);
                    if(table[position] !== undefined) { //{3}
                        //遍历链表来寻找键/值
                        var current = table[position].getHead(); //{4}
                        while(current.next) { //{5}
                            if(current.element.key === key) { //{6}
                                return current.element.value; //{7}
                            }
                            current = current.next; //{8}
                        }
                        //检查元素在链表第一个或最后一个节点的情况
/                                这里我们可以看到另一种循环迭代的技巧,
/                                即, 循环的第一个和最后一个可以进行特殊处理
/                                有时候我们之所以很难就是因为我们想要在循环的时候
/                                 对第一个和最后一个, 也就是边界情况进行兼容 所以才会很困难.
/                                 我看作者这种方式很有用
                        if(current.element.key === key) { //{9}
                            return current.element.value;
                        }
                    }
                    return undefined; //{10}
                };
                this.remove = function(key) {
                    var position = loseloseHashCode(key);
                    if(table[position] !== undefined) {
                        var current = table[position].getHead();
                        while(current.next) {
                            if(current.element.key === key) { //{11}
                                table[position].remove(current.element); //{12}
                                if(table[position].isEmpty()) { //{13}
                                    table[position] = undefined; //{14}
                                }
                                return true; //{15}
                            }
                            current = current.next;
                        }
                        // 检查是否为第一个或最后一个元素
                        if(current.element.key === key) { //{16}
                            table[position].remove(current.element);
                            if(table[position].isEmpty()) {
                                table[position] = undefined;
                            }
                            return true;
                        }
                    }
                    return false; //{17}
                };
            }

2. 线性探查

另一种解决冲突的方法是线性探查。当想向表中某个位置加入一个新元素的时候,如果索引
为index的位置已经被占据了,就尝试index+1的位置。如果index+1的位置也被占据了,就尝试
index+2的位置,以此类推。

            function HashTable() {
                var ValuePair = function(key, value) {
                    this.key = key;
                    this.value = value;
                    this.toString = function() {
                        return '[' + this.key + ' - ' + this.value + ']';

                    }
                }
                this.put = function(key, value) {
                    var position = loseloseHashCode(key); // {1}
                    if(table[position] == undefined) { // {2}
                        table[position] = new ValuePair(key, value); // {3}
                    } else {
                        var index = ++position; // {4}
                        while(table[index] != undefined) { // {5}
                            index++; // {6}
                        }
                        table[index] = new ValuePair(key, value); // {7}
                    }
                };
                this.get = function(key) {
                    var position = loseloseHashCode(key);
                    if(table[position] !== undefined) { //{8}
                        if(table[position].key === key) { //{9}
                            return table[position].value; //{10}
                        } else {
                            var index = ++position;
                            while(table[index] === undefined ||
                                table[index].key !== key) { //{11}
                                index++;
                            }
                            if(table[index].key === key) { //{12}
                                return table[index].value; //{13}
                            }
                        }
                    }
                    return undefined; //{14}
                };
                this.remove = function(key) {
                    var position = loseloseHashCode(key);
                    if(table[position] !== undefined) { //{8}
                        if(table[position].key === key) { //{9}
                            table[index] = undefined;  //{10}
                        } else {
                            var index = ++position;
                            while(table[index] === undefined ||
                                table[index].key !== key) { //{11}
                                index++;
                            }
                            if(table[index].key === key) { //{12}
                                table[index] = undefined;  //{13}
                            }
                        }
                    }
                    return undefined; //{14}
                };
            };

个人觉得这个方法是有很大的缺陷的, 虽然没经过测试,
但很明显, 这个方法无法区分相同的key值, 比如 我put 两次 {'a' : 'b'},
按照这个代码的逻辑, 似乎不会进行覆盖.(也许应该在put 里 设置一下 key值检查)
而且如果这个方案配合 lose 函数, 感觉非常没意思. 跟不用 lose 函数基本没什么区别吧?

介绍了一个更好的 散列函数

            var djb2HashCode = function(key) {
                var hash = 5381; //{1}
                for(var i = 0; i < key.length; i++) { //{2}
                    hash = hash * 33 + key.charCodeAt(i); //{3}
                }
                return hash % 1013; //{4}
            };

其实这个函数只看 第{4} 行就能得知, 最后获得的值 必然小于1013
即, 超过1013个的数据 一定会发生冲突.
(其实, 作为一个小白, 我不太明白, 为什么一定要最后% 一个数, 难道是因为怕数字太大?)

个人觉得如果同时用好几个不同的散列函数,
进行组合标记一个key, 能够大大减少冲突.

第八章 树

image.png

8.3 二叉树和二叉搜索树

二叉树 : 每个节点 最多只有左右两个枝
二叉搜索树(BST): 是二叉树的一种,但是它只允许你在左侧节点存储(比父节点)小的值,
在右侧节点存储(比父节点)大(或者等于)的值。上一节的图中就展现了一棵二叉搜索树。


image.png

二叉搜索树封装

            function BinarySearchTree() {
                var Node = function(key) { //{1}
                    this.key = key;
                    this.left = null;
                    this.right = null;
                };
                var root = null; //{2}
插入
                this.insert = function(key) {
                    var newNode = new Node(key); //{1}
                    if(root === null) { //{2}
                        root = newNode;
                    } else {
                        insertNode(root, newNode); //{3}
                    }
                };

                var insertNode = function(node, newNode) {
                    if(newNode.key < node.key) { //{4}
                        if(node.left === null) { //{5}
                            node.left = newNode; //{6}
                        } else {
                            insertNode(node.left, newNode); //{7}
                        }
                    } else {
                        if(node.right === null) { //{8}
                            node.right = newNode; //{9}
                        } else {
                            insertNode(node.right, newNode); //{10}
                        }
                    }
                };
中序遍历
                this.inOrderTraverse = function(callback) {
                    inOrderTraverseNode(root, callback); //{1}
                };

                var inOrderTraverseNode = function(node, callback) {
                    if(node !== null) { //{2} 
                        inOrderTraverseNode(node.left, callback); //{3}
                        callback(node.key); //{4}
                        inOrderTraverseNode(node.right, callback); //{5}
                    }
                };
先序遍历
                this.preOrderTraverse = function(callback) {
                    preOrderTraverseNode(root, callback);
                };

                var preOrderTraverseNode = function(node, callback) {
                    if(node !== null) {
                        callback(node.key); //{1}
                        preOrderTraverseNode(node.left, callback); //{2}
                        preOrderTraverseNode(node.right, callback); //{3}
                    }
                };
后续遍历
                this.postOrderTraverse = function(callback) {
                    postOrderTraverseNode(root, callback);
                };
                var postOrderTraverseNode = function(node, callback) {
                    if(node !== null) {
                        postOrderTraverseNode(node.left, callback); //{1}
                        postOrderTraverseNode(node.right, callback); //{2}
                        callback(node.key); //{3}
                    }
                };
            }

可以看到,二叉树这里开始, 就会用上大量的递归结构.
稍微观察一下即可以发现

  • 使用递归时, 尽量不要用 对象调用方式执行函数, 而是改成 传参形式.
  • 使用递归时, 必然存在 if else 结构, 因为要保证一个出口

另外,可以发现的是,
上面的封装当中, 之所以这棵树是个二叉搜索树 是由 插入 函数决定的.
实际上即使是普通的二叉树, 依然可以用 上面的 三种遍历方式.

如果是插入时没有什么顺序, 那么这个树可以是无序的存储结构,
如果插入时 依照了某种顺序, 那么这个树可以是有序的存储结构.
即, 我们创建一个有序二叉树时, 没有必要只是比较一个值的大小?

搜索最大值和最小值

其实对二叉搜索树而言, 就相当于 搜索最左值和最右值

                this.min = function() {
                    return minNode(root); //{1}
                };
                var minNode = function(node) {
                    if(node) {
                        while(node && node.left !== null) { //{2}
                            node = node.left; //{3}
                        }
                        return node.key;
                    }
                    return null; //{4}
                };
                this.max = function() {
                    return maxNode(root);
                };
                var maxNode = function(node) {
                    if(node) {
                        while(node && node.right !== null) { //{5}
                            node = node.right;
                        }
                        return node.key;
                    }
                    return null;
                };
搜索特定值
                this.search = function(key) {
                    return searchNode(root, key); //{1}
                };
                var searchNode = function(node, key) {
                    if(node === null) { //{2}
                        return false;
                    }
                    if(key < node.key) { //{3}
                        return searchNode(node.left, key); //{4}
                    } else if(key > node.key) { //{5}
                        return searchNode(node.right, key); //{6}
                    } else {
                        return true; //{7}
                                              /或者这里可以直接返回节点 return node
                    }
                };
移除一个节点

说实话, 这是二叉树当中遇到的最难的部分,或者说最巧的部分
真的很难想得到.
这第一个想出来的,到底是用什么思路想到的呢?

                var removeNode = function(node, key) {
                    if(node === null) { //{2}
                        return null;
                    }
                    if(key < node.key) { //{3}
                        node.left = removeNode(node.left, key); //{4} 
                        return node; //{5}
                                                
                    } else if(key > node.key) { //{6}
                        node.right = removeNode(node.right, key); //{7}
                        return node; //{8}
                    } else { //键等于node.key
                        //第一种情况——一个叶节点
                        if(node.left === null && node.right === null) { //{9}
                            node = null; //{10}
                            return node; //{11}
                        }
                        //第二种情况——一个只有一个子节点的节点
                        if(node.left === null) { //{12}
                            node = node.right; //{13}
                            return node; //{14}
                        } else if(node.right === null) { //{15}
                            node = node.left; //{16}
                            return node; //{17}
                        }
                        //第三种情况——一个有两个子节点的节点
                        var aux = findMinNode(node.right); //{18}
                        node.key = aux.key; //{19}
                        node.right = removeNode(node.right, aux.key); //{20}
                        return node; //{21}
                    }
                };
/个人认为第三种情况的处理实在是太巧妙了,
/ 这绝对是对概念的透彻理解之上才能做出这种操作.
/ 说了两句废话..
                var findMinNode = function(node) {
                    while(node && node.left !== null) {
                        node = node.left;
                    }
                    return node;
                };

跟我以前见过的递归函数不同
以前见过的递归的返回值一般是这样的

第一种
            function fn (a) {
                if (flag) {
                    return fn(a)
                } else{
                    return 'some'
                }
            }
而这里是这种感觉
第二种
            function fn (a) {
                if (flag) {
                    a.b = fn(a.b)
                    return a
                } else{
                    return 'some'
                }
            }
我觉得这里存在一种比较大的区别, 但我无法很好的归类这是什么问题

从形式上讲, 第二种是, 我传入的值和他返回的值是同一个引用值.
从某种角度上来讲, 这么做的一个用途是, 能够保持这个引用值的整体结构.

我感觉这俩的一个还有一个区别可能是,
第一种给我的感觉是, 他提供的是一个值, 并且返回的也是一个值,
或者极有可能的情形是, 每次递归的返回值, 可能是下一个循环需要的值.
比如
return fn(a) + 1 这种情况下, 第二次返回的值,对第一次返回值是有影响的.
一种reduce 的感觉. 

而第二种给我的感觉是, 他做的是一个搜索工作, 提供的是一个地址?(当然也是值),
他不需要前一次的运算并不需要后一次的运算结果参与.
一种forEach的感觉.

8.6 自平衡树

Adelson-Velskii-Landi 树(AVL 树)

AVL树是一种自平衡二叉搜索树,意思是任何
一个节点左右两侧子树的高度之差最多为1。也就是说这种树会在添加或移除节点时尽量试着成
为一棵完全树。

插入

            var insertNode = function(node, element) {
                if(node === null) {
                    node = new Node(element);
                } else if(element < node.key) {
                    node.left = insertNode(node.left, element);
                    if(node.left !== null) {
                        // 确认是否需要平衡 {1}
                    }
                } else if(element > node.key) {
                    node.right = insertNode(node.right, element);
                    if(node.right !== null) {
                        // 确认是否需要平衡 {2}
                    }
                }
                return node;
            };

可以看到从形式上来讲, 这跟上面的insert的递归方式不相同, 会return 一个 node,
更接近我们刚才讲的 remove, 即, 有保持结构, 拼接的意思.

计算平衡因子

对每个节点计算右子树高度(hr)和左子树高度(hl)的差值,该值
(hr-hl)应为0、1或1。如果结果不是这三个值之一,则需要平衡该AVL树。这就是平衡因子
的概念。

计算高度的函数

            var heightNode = function(node) {
                if(node === null) {
                    return -1;
                } else {
                    return Math.max(heightNode(node.left),
                        heightNode(node.right)) + 1;
                }
            };

我勒个去, 就这个函数, 打死我都想不出来啊!
太巧妙了.
从概念上讲, 还是能理解的,
即, 每次递归,我加一层,
左右树当中, 哪个高,就选哪一个.
但我感觉我写不出来.
这绝对是我见过的递归中, 最巧妙的一个之一了吧.
也许我想得复杂是因为, 我总是想要思考步骤,
所以跟着递归走两回 就头晕脑胀,
而这些人写递归的时候, 想得不是步骤,
而是概念的组合?
一个概念是, 每次递归加一层,
一个概念是, 左右哪个更大选哪个,
这两个进行了组合?
反正我觉得很牛逼啊

            // 替换insertNode方法的行{1}
            if((heightNode(node.left) - heightNode(node.right)) > 1) {
                // 旋转 {3}
            }
            向右子树插入新节点时, 应用同样的逻辑, 代码如下:
            // 替换insertNode方法的行{2}
            if((heightNode(node.right) - heightNode(node.left)) > 1) {
                // 旋转 {4}
            }
image.png
            var rotationRR = function(node) {
                var tmp = node.right; // {1}
                node.right = tmp.left; // {2}
                tmp.left = node; // {3}
                return tmp;
            };
image.png
            var rotationLL = function(node) {
                var tmp = node.left; // {1}
                node.left = tmp.right; // {2}
                tmp.right = node; // {3}
                return tmp;
            };
image.png
            var rotationLR = function(node) {
                node.left = rotationRR(node.left);
                return rotationLL(node);
            };
image.png
            var rotationRL = function(node) {
                node.right = rotationLL(node.right);
                return rotationRR(node);
            };

插入完整版

            var insertNode = function(node, element) {
                if(node === null) {
                    node = new Node(element);
                } else if(element < node.key) {
                    node.left = insertNode(node.left, element);
                                        / 其实这里是, 先插进去, 然后再进行判断,然后再调整
                                        / 也许存在方法是, 直接插进去的时候, 就按照平衡的规则插入?
                    if(node.left !== null) {
                        if((heightNode(node.left) - heightNode(node.right)) > 1) {/ 计算平衡因子
                            // 旋转 {3}
                            if(element < node.left.key) {
                                node = rotationLL(node);
                            } else {
                                node = rotationLR(node);
                            }
                        }
                    }
                } else if(element > node.key) {
                    node.right = insertNode(node.right, element);
                    if(node.right !== null) {
                        if((heightNode(node.right) - heightNode(node.left)) > 1) {
                            // 旋转 {4}
                            if(element > node.right.key) {
                                node = rotationRR(node);
                            } else {
                                node = rotationRL(node);
                            }
                        }
                    }
                }
                return node;
            };

理解倒是能理解, 但如果让我自己研究出来估计要很长很长时间.
主要是概念整理得好!

红黑树,和 堆积树

书中作为扩展来提及, 看来,这本书可能就是为了我们小白写得,

图是网络结构的抽象模型。图是一组由边连接的节点(或顶点)。学习图是重要的,因为任
何二元关系都可以用图来表示。


image.png
  • 相邻顶点:由一条边连接在一起的顶点
  • 一个顶点的度 : 相邻顶点的数量
  • 路径 : 顶点v1, v2,…,vk的一个连续序列
  • 简单路径 : 要求不包含重复的顶点 (环也是一个简单路径)

有向图和无向图

  • 强连通: 如果图中每两个顶点间在双向上都存在路径,则该图是强连通的
    (我的理解是, 两个顶点之间如果是双向的, 这两个顶点之间是强联通的, 但不能说整个图是强联通图)
    (图中所有的顶点之间都是双向时, 才能说整个图是强联通图.)
    (不对,百度百科里的定义不太一样: 强连通(Strongly Connected)是指一个有向图(Directed Graph)中任意两点v1、v2间存在v1到v2的路径(path)及v2到v1的路径。)
    (这就表示没两点之间起码存在两条路径, 而顶点之间不一定非得是双向的)
  • 加权 与 未加权

9.2 图的表示

这里说的图的表示是, 用数学或者代码的形式来表示.
而非用画图的方式来表示.

9.2.1 邻接矩阵

image.png

矩阵用代码表示, 就是二维数组,
即,用二维数组来表示.

9.2.2 邻接表

image.png

存在好几种方式来表示这种数据结构。我们可以用列表(数组)、链表,甚至是
散列表或是字典来表示相邻顶点列表。

9.2.3 关联矩阵

邻接矩阵,邻接表都是用点来表示的,
而关联矩阵则是用点和边来表示的

在关联矩阵中,矩阵的行表示顶点,列表示边。如下图所
示,我们使用二维数组来表示两者之间的连通性,如果顶点v是边e的入射点,则array[v][e] === 1;
否则,array[v][e] === 0。


image.png

说实话, 这幅图配的有点莫名其妙的, 完全看不懂和 关联矩阵有毛关系?
下面是百度百科的,


image.png

9.3 创建 Graph 类

书中是打算实现 邻接表 来表示图(即上面的邻接矩阵和关联矩阵就略过去了)

            function Graph() {
                var vertices = []; //{1} 放顶点
                var adjList = new Dictionary(); //{2} 放顶点的相邻顶点
            }
            this.addVertex = function(v) { // 添加顶点
                vertices.push(v); //{3}
                adjList.set(v, []); //{4}
            };
            this.addEdge = function(v, w) { // 添加顶点的相邻顶点
                adjList.get(v).push(w); //{5}
                adjList.get(w).push(v); //{6}
            };
            this.toString = function() {// 这个函数的功能是 展示 都有哪些顶点的关系
                var s = '';// 实际上可以看成是如何遍历的功能
                for(var i = 0; i < vertices.length; i++) { //{10}
                    s += vertices[i] + ' -> ';
                    var neighbors = adjList.get(vertices[i]); //{11}
                    for(var j = 0; j < neighbors.length; j++) { //{12}
                        s += neighbors[j] + ' ';
                    }
                    s += '\n'; //{13}
                }
                return s;
            };

9.4 图的遍历

大名鼎鼎的 深度优先遍历和广度优先遍历在这里


image.png

image.png

这里的关键是, 访问和探索是两个行为.
访问的意思是, 访问之后的顶点会被放入 待探索的列表之中.

辅助函数

            var initializeColor = function() {
                var color = [];
                for(var i = 0; i < vertices.length; i++) {
                    /初始化 让所有顶点的状态为 未访问,即,白色
                    color[vertices[i]] = 'white'; //{1}
                }
                return color;
            };

广度优先遍历
版本1

            this.bfs = function(v, callback) {/ v 是要遍历的起点
                var color = initializeColor(), //{2}
                    queue = new Queue(); //{3}
                queue.enqueue(v); //{4}
                while(!queue.isEmpty()) { //{5}
                    var u = queue.dequeue(), //{6}/ 取出顶点
                        neighbors = adjList.get(u); //{7} / 取出该顶点的 相邻顶点数组
                    color[u] = 'grey'; // {8}/表示已经被访问过了
                    for(var i = 0; i < neighbors.length; i++) { // {9}
                        var w = neighbors[i]; // {10}
                        if(color[w] === 'white') { // {11}
                            color[w] = 'grey'; // {12}
                            queue.enqueue(w); // {13}/放进队列中
                        }
                    }
                    color[u] = 'black'; // {14}//表示 , 已经完成访问, 也完成 callback了
                    if(callback) { // {15}
                        callback(u);
                    }
                }
            };

版本2 : 使用BFS寻找最短路径

            this.BFS = function(v) {
                var color = initializeColor(),
                    queue = new Queue(),
                    d = [], //{1} 
                    pred = []; //{2}
                queue.enqueue(v);
                for(var i = 0; i < vertices.length; i++) { //{3}
                    d[vertices[i]] = 0; //{4}
                    pred[vertices[i]] = null; //{5}
                }
                while(!queue.isEmpty()) {
                    var u = queue.dequeue(),
                        neighbors = adjList.get(u);
                    color[u] = 'grey';
                    for(i = 0; i < neighbors.length; i++) {
                        var w = neighbors[i];
                        if(color[w] === 'white') {
                            color[w] = 'grey';
                            d[w] = d[u] + 1; //{6}
                            pred[w] = u; //{7}
                            queue.enqueue(w);
                        }
                    }
                    color[u] = 'black';
                }
                return { //{8}
                    distances: d,
                    predecessors: pred
                };
            };
image.png

可以看到 distances 表示的是 离起始遍历点的距离,
pred 是表示 该顶点是被哪个顶点"举报的"

var myVertices = ['A','B','C','D','E','F']; 
var shortestPathA = graph.BFS(myVertices[0]); 

            var fromVertex = myVertices[0]; //{9}
            for(var i = 1; i < myVertices.length; i++) { //{10}
                var toVertex = myVertices[i], //{11}
                    path = new Stack(); //{12}
                for(var v = toVertex; v !== fromVertex; v = shortestPathA.predecessors[v]) { //{13}
                    path.push(v); //{14}
                }
                path.push(fromVertex); //{15}
                var s = path.pop(); //{16}
                while(!path.isEmpty()) { //{17}
                    s += ' - ' + path.pop(); //{18}
                }
                console.log(s); //{19}
            }
image.png

也能看懂.

深入最短路径算法

举些例子,Dijkstra算法解决了单源最短路径问题。Bellman-Ford算法解决了边权值为负的
单源最短路径问题。A*搜索算法解决了求仅一对顶点间的最短路径问题,它用经验法则来加速搜
索过程。Floyd-Warshall算法解决了求所有顶点对间的最短路径这一问题。
与上面提到的红黑树和堆积树一样, 只是提及, 书中并不介绍

9.4.2 深度优先搜索

            this.dfs = function(callback) {
                var color = initializeColor(); //{1} / 状态初始化
                for(var i = 0; i < vertices.length; i++) { //{2} 注意 这里 和 行 {8} 以及 行{11}
                    if(color[vertices[i]] === 'white') { //{3}// 之所以遍历顺序为深度优先就取决于 {2}{8}{11} 了
                        dfsVisit(vertices[i], color, callback); //{4}
                    }
                }
            };
            var dfsVisit = function(u, color, callback) {
                color[u] = 'grey'; //{5}
                if(callback) { //{6}
                    callback(u);
                }
                var neighbors = adjList.get(u); //{7}
                for(var i = 0; i < neighbors.length; i++) { //{8}
                    var w = neighbors[i]; //{9}
                    if(color[w] === 'white') { //{10}
                        dfsVisit(w, color, callback); //{11}
                    }
                }
                color[u] = 'black'; //{12}
            };

实话实说, 这一部分有点失望,
他在上面实现广度优先时 用的是迭代,
而在这里实现深度优先时, 用的是 递归.
不是说不行, 而是说, 要么两个都用递归,都用迭代, 这样好做比较吧?
或者说, 上面用得是 队列, 这里应该用栈,
但用递归之后, 无法体现栈了吧?
我试着简单改了一下

            this.dfs = function(callback) {
                var color = initializeColor(); //{1}
                for(var i = 0; i < vertices.length; i++) { //{2}
                    var u = vertices[i];
                    if(color[u] === 'white') { //{3}
                        color[u] = 'grey'; //{5}
                        if(callback) { //{6}
                            callback(u);
                        }
                        var neighbors = adjList.get(u); //{7}
                        for(var i = 0; i < neighbors.length; i++) { //{8}
                            var w = neighbors[i]; //{9}
                            if(color[w] === 'white') { //{10}
                                color[w] = 'grey'; //{5}
                                if(callback) { //{6}
                                    callback(w);
                                }
                                color[w] = 'black';
                            }
                        }
                        color[u] = 'black';
                    }
                }
            };
想继续改成 用Stack , 发现没我想象的那么简单. 回头再想想吧

1. 探索深度优先算法

对于给定的图G,我们希望深度优先搜索算法遍历图G的所有节点,构建“森林”(有根树的
一个集合)以及一组源顶点(根),并输出两个数组:发现时间和完成探索时间。我们可以修改
dfs方法来返回给我们一些信息:


image.png
var time = 0; //{1}
            this.DFS = function() {
                var color = initializeColor(), //{2}
                    d = [],
                    f = [],
                    p = [];
                time = 0;
                for(var i = 0; i < vertices.length; i++) { //{3}
                    f[vertices[i]] = 0;
                    d[vertices[i]] = 0;
                    p[vertices[i]] = null;
                }
                for(i = 0; i < vertices.length; i++) {
                    if(color[vertices[i]] === 'white') {
                        DFSVisit(vertices[i], color, d, f, p);
                    }
                }
                return { //{4}
                    discovery: d,
                    finished: f,
                    predecessors: p
                };
            };
            var DFSVisit = function(u, color, d, f, p) {
                console.log('discovered ' + u);
                color[u] = 'grey';
                d[u] = ++time; //{5}
                var neighbors = adjList.get(u);
                for(var i = 0; i < neighbors.length; i++) {
                    var w = neighbors[i];
                    if(color[w] === 'white') {
                        p[w] = u; // {6}
                        DFSVisit(w, color, d, f, p);
                    }
                }
                color[u] = 'black';
                f[u] = ++time; //{7}
                console.log('explored ' + u);
            };
image.png

到这里, 也不是不能理解.还是能看懂的.
如果广度优先遍历, 也配一下 发现时间和完成时间的话我猜应该是这样的


image.png

拓扑排序

有向无环图(DAG)。


image.png

拓扑排序 : 要编排一些任务或步骤的执行顺序时
拓扑排序只能应用于DAG(有向无环图) -- 能理解, 有向代表顺序, 无环代表不需要重复

image.png

也就是说, 深度优先遍历 能用于 按步骤, 按顺序执行的相关内容?

你可能感兴趣的:(javascript 数据结构与算法 笔记2.2)