图的类型和结构

一个图就是由边(edge) 相连的节点(node) 组成的。
通过一条线相连接的两个节点叫做相邻节点(adjacent vertices)。同时,我们可以把一个节点连接的数量叫做度(degree)。一个边也可以加权(weighted)。一条路径(path)就是一组相邻节点的序列。
一条可以回到原点的路径是循环图(cyclic graph)。一个没有可以回到原点的路径的图被称作无环图(acyclic graph)。如果图之间的边是有指向的就会被称为是有向图(directed graph),反之,如果图之间的边没有指向,就被称之为无向图(undirected graph)。如果一个图是有向无环的,就叫做我们上一讲提到过的有向无环图(DAG,directed acyclic graph)。
一种用来存储图的方式是通过邻接矩阵(adjacency matrix),适用于稠密图(dense graph)
另外一种用来存储图的方式就是通过邻接表(adjacency list)。这种数据结构既可以用数组,也可以用链表、 哈希或者字典来表示,所以可以和邻接矩阵形成有效的互补。

对图的遍历有两种经典的方式,一种是广度优先搜索(BFS,breath first search),另外一种是深度优先搜索(DFS,depth first search)。广度优先搜索最经典的使用场景就是寻找最短路径(shortest path)的场景了。而深度优先搜索呢,就是我们前面说到的拓扑排序(topological sorting)可以用到的一种遍历方式。

如何在选择中找到最短路径

如图所示,假设我们要计算从 A 到 B、C、D、E 和 Z 的最短距离。

Dijkstra 的算法先将所有的距离初始化为无限 dist[i] = INF;已访问的数组为否 visited[i] = false,然后把从源头到自己的距离设置为零 dist[src] = 0。接下来,为了找到最短距离,我们寻找还未被处理的节点中最短的一个 minDist(dist, visited),然后将它标记为已访问 visited[u] = true;当找到并设置最短距离 dist[u] + graphu后,所有路径的数组会被返回。下面是简化后的代码:

const dijkstra = (graph, src) => {
  for (let i = 0; i < length; i++) { 
    dist[i] = INF;
    visited[i] = false;
  }
  dist[src] = 0; 
  for (let i = 0; i < length - 1; i++) { 
    var u = minDist(dist, visited); 
    visited[u] = true; 
    for (let v = 0; v < length; v++) {
      if (...dist[u] + graph[u][v] < dist[v]) { 
        dist[v] = dist[u] + graph[u][v]; 
      }
    }
  }
  return dist; 
};

如何在无序中找到资源

依赖比如我们在用 webpack 或类似的工具做资源打包的时候,要知道模块间的依赖关系是很重要的。抽象来看,就是对于有向图来说,要知道如何在混乱中建立秩序,分析出哪个节点先被处理是很重要的。
拓扑排序(topological sorting)
拓扑排序用的就是从一个节点开始,进行深度优先的遍历,直到和它相连的每个顶点都用递归方式穷尽为止。每一次递归中的节点被加到一个访问过的 visited 集合中,以此类推,最后,在递归结尾,把节点用 unshift 以相反的顺序插到数组头部。

dfs = function(v, visited, stack) {
  visited.add(v);
  for (var item in this.edges[v]) {
      if (visited.has(item) == false) {
          this.topologicalSortUtil(item, visited, stack)
      }
  }
  stack.unshift(v);
};

dfsTopoSort = function() {
  var visited = {},
  stack = [];
  for (var item in this.edges) {
      if (visited.has(item) == false) {
          this.dfs(item, visited, stack);
      }
  }
  return stack;
};

树的类型和结构
树(tree)可以说是非常常见的一种数据结构了。
树的分支和叶子在树结构里就是节点(node)。
树的顶端这个节点叫做根(root)。
二叉树(binary tree)和二叉查找树(BST,binary search tree)
二叉树里面分为满二叉树(full binary tree)和完全二叉树(complete binary tree)。

在二叉查找树中有三种遍历的方式。第一种是中序遍历(in-order traversal),这种方法是对节点展开的从小到大的遍历,它的遍历顺序是左、根、右;第二种是前序遍历(pre-order traversal),这种方法是在先访问根节点再访问子节点,它的遍历顺序是根、左、右;第三种是后序遍历(post-order traversal),这种方法是在访问子节点后访问它的父节点,它的遍历顺序是右、左、根。除了这三种以外,还有层次遍历(level order traversal),这种方式就是上面我们讲到图的时候讲到的深度优先搜索(DFS,depth first search)的原理。

二叉查找树有一个问题,就是当这个树的一个分支过深的时候,在增加、减少和查找的时候,可能会出现性能上的问题。
AVL 树(AVL tree,Adelson-Velskii and Landi’s tree)和红黑树(Red-Black tree)
都属于平衡二叉树(balanced binary tree)。

为了在插入后保持平衡,AVL 树会通过左旋或右旋的方式来调整。比如以上的例子就是左旋,下面的例子中,我们也可以看到右旋。在理想的状态下,AVL 的复杂度是 O(log2(n))。但是当有频繁的插入、删除等操作的时候,效率就会下降。在极端情况下它的复杂度会退化到 O(n)。

虽然 AVL 的查询效率很高,但是为了保持节点插入或者删除后的平衡所进行的旋转操作,可能会导致复杂度的增加。
红黑树(Red-Black tree)
如果一个节点是红色的,那么它的两个子节点都是黑色的。不能有两个相邻的红色节点,也就是说红色节点不能有红色父或子节点。从给定节点到其以后的每条路径都包含相同数量的黑色节点。关于插入,它也有 2 个原则,1 是插入的节点需要是红色,2 是插入的位置需要是叶子节点。
我们看到除了换色,红黑树和 AVL 一样,有时也需要用到旋转。
红黑树的好处是它的查询、插入、删除等操作的复杂度都比较稳定,可以控制在 O(log2(n))。

堆(heap)
从一个数据结构的角度来看,那么堆是一个完全二叉树。
满二叉树与完全二叉树区别:
满二叉树是除最后一层没有任何子节点以外,其它每一层的所有结点都有两个子结点二叉树。而完全二叉树指的是叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。
堆的特点在于它所有结点的值必须“大于或等于”或“小于或等于”其子结点的值。它和其它的树型结构通过对象存储节点和指针的方式不太一样的地方在于,它可以用一个数组来存储。它可以被用于优先级队列,另外一个比较常见的应用就是堆排序。和快排和归并排序类似,它的时间复杂度也是 O(nlog2(n))。
堆排序的场景里,因为它对比交换(compare and swap)的次数可能要比快排多,所以虽然它们的时间复杂度一样,但是实际从性能上看,堆排序的性能会相对较低。

字符串的匹配算法有哪些

暴力(BF,Brute Force)、BM(Boyer-Moore)、RK(Rabin–Karp)和KMP(Knuth–Morris–Pratt)
经常说的路由,是用的哪一种的?它用的一种特殊的字典树(trie)。
字典树(trie)

极客时间《Jvascript进阶实战课》学习笔记 Day20

豪猪
4 声望4 粉丝

undefined