数据结构TypeScript之邻接表实现示例详解

目录
  • 图的结构特点
    • 图的分类
    • 图的表示
  • 面向对象方法封装邻接表
    • 构造函数
    • 增加顶点和边
    • 删除顶点和边
  • 图的遍历
    • 颜色标记
    • 广度优先搜索(队列)
    • 深度优先搜索(栈)

图的结构特点

图由顶点和顶点之间的边构成,记为G(V, E)。其中V是顶点集合,E是边集合。作为一种非线性的数据结构,顶点之间存在多对多关系。

图的分类

无向图:两个顶点之间有两条互相关联的边。A和B之间为双向互通。

有向图:两个顶点之间有一条或两条关联的边。从A到B或者从B到A,只能单向通过。

带权无向图:在无向图的基础上增加一个权值,代表距离等衡量单位。

带权有向图:在有向图的基础上增加一个权值,代表距离等衡量单位。

图的表示

图的表示分为两种方法:邻接矩阵和邻接表。

基于邻接表的概念手动构建四种类型的图,演示如下:

// 有向图
let graph = {
    A: { B: null, C: null },
    B: { C: null },
    C: {},
}
// 无向图
let graph = {
    A: { B: null, C: null },
    B: { A: null, C: null },
    C: { A: null, B: null },
}
// 带权有向图
let graph = {
    A: { B: 20, C: 20 },
    B: { C: 40 },
    C: {},
}
// 带权无向图
let graph = {
    A: { B: 20, C: 30 },
    B: { A: 20, C: 40 },
    C: { A: 30, B: 40 },
}

面向对象方法封装邻接表

构造函数

class Graph {
    length: number
    vertices: { [key: string]: { [key: string]: null | number } }
    isDirected: boolean
    constructor(isDirected: boolean = false) {
        this.length = 0
        this.vertices = {}
        this.isDirected = isDirected
    }
}

增加顶点和边

顶点增加:利用传入key参数在顶点集合新建一个属性,值为一个空对象用于存储边。

addVertex(...vertices: string[]): Graph {
    vertices.forEach((key: string) => {
        this.vertices[key] = {}
        this.length++
    })
    return this
}

边增加需要处理的三种情况:

  • 顶点不存在:执行addVertex增加顶点。
  • 有向图:两个顶点间建立一条关联的边。
  • 无向图:两个顶点间建立互相关联的两条边。
addEdge(v: string, edges: { [key: string]: null | number}): Graph {
    if (!this.vertices[v]) { this.addVertex(v) }
    for (const key in edges) {
        if (!this.vertices[key]) { this.addVertex(key) }
        this.vertices[v][key] = edges[key]
        if (!this.isDirected) {
            this.vertices[key][v] = edges[key]
        }
    }
    return this
}

删除顶点和边

顶点删除:需要删除与这个顶点与其他顶点关联的边。

removeVertex(v: string): Graph {
    delete this.vertices[v]
    for (const key in this.vertices) {
        if (!!this.vertices[key][v]) {
            delete this.vertices[key][v]
        }
    }
    this.length--
    return this
}

边删除:有向图,删除一个顶点关联的边即可;无向图,删除两条顶点互相关联的边。

removeEdge(v: string, w: string): Graph {
    delete this.vertices[v][w]
    if (!this.isDirected) {
        delete this.vertices[w][v]
    }
    return this
}

图的遍历

颜色标记

为图中的每一个顶点标记颜色,作为状态记录。三种颜色状态分别如下:

  • 白色:未发现的顶点
  • 灰色:被发现的顶点
  • 黑色:已遍历过的顶点
// 初始化所有顶点颜色,作为初始的状态
const initColor = (vertices: { [key: string]: { [key: string]: null | number } })
    : { [key: string]: 'white' | 'gray' | 'black' } => {
    let color = {}
    for (const key in vertices) {
        color[key] = 'white'
    }
    return color
}

广度优先搜索(队列)

实现思路:

  • 初始化所有的顶点状态为白色,选择一个初始顶点入队开始进行搜索。
  • 当队列不为空,被选中的初始顶点出队。将这个顶点(通过边)关联的其他顶点入队,并为其标记为灰色。
  • 当执行完第二步后,将初始顶点标记为黑色。然后第二步入队的顶点都会重复二、三步骤直到队列为空。
const breadthFirstSearch = (graph: Graph, startVertex: string): string[] => {
    let result: string[] = []
    let v: string = startVertex
    const color = initColor(graph.vertices)
    const queue = new Queue()
    queue.enqueue(v)
    while (!queue.isEmpty()) {
        v = queue.dequeue()
        for (const key in graph.vertices[v]) {
            if (color[key] === 'white') {
                queue.enqueue(key)
                color[key] = 'gray'
            }
        }
        color[v] = 'black'
        result.push(v)
    }
    return result
}

深度优先搜索(栈)

实现思路:与广度优先搜索步骤相同,队列换为栈即可。需要注意的是深度优先搜索结果不唯一。

const depthFirstSearch = (graph: Graph, startVertex: string): string[] => {
    let result: string[] = []
    let v: string = startVertex
    const color = initColor(graph.vertices)
    const stack = new Stack()
    stack.push(v)
    while (!stack.isEmpty()) {
        v = stack.pop()
        for (const key in graph.vertices[v]) {
            if (color[key] === 'white') {
                stack.push(key)
                color[key] = 'gray'
            }
        }
        color[v] = 'black'
        result.push(v)
    }
    return result
}

本文相关代码已放置我的Github仓库

项目地址:

Algorithmlib|Graph

Algorithmlib|Graph Traversal

以上就是数据结构TS之邻接表实现示例详解的详细内容,更多关于TS数据结构邻接表的资料请关注我们其它相关文章!

(0)

相关推荐

  • TypeScript数据结构链表结构 LinkedList教程及面试

    目录 1. 认识链表 2. 实现链表结构的封装 2.1 基础框架 v1 版 2.2 添加 append 方法 v2 版 2.3 添加 traverse 方法 v3 版 2.4 添加 insert 方法 v4 版 2.5 添加 removeAt 方法 v5 版 2.6 添加 get 方法 v6 版 2.7 添加 getNode 方法 v7 版 2.8 添加 update 方法 v8 版 2.9 添加 indexOf 方法 v9 版 2.10 添加 remove 方法 v10 版 2.11 添加方法

  • 数据结构TypeScript之链表实现详解

    目录 链表结构特点 面向对象方法封装链表 构造函数 基本单元:链表节点 主体:链表 查找节点 增加节点 删除节点 链表结构特点 链表是线性表的其中一种,用于存储有固定顺序的元素.而元素之间会通过”链“连接在一起. 链表存储的元素称为节点.每个节点内部存在两个值.如下: this.element:链表需要存储的单个元素 this.next:指向下一个节点,也就是链表中的”链“,将节点连接在一起. 尝试手动构建链表结构.过程如下: class LinkedListNode { constructor

  • 数据结构TypeScript之二叉查找树实现详解

    目录 树的结构特点 面向对象方法封装二叉查找树(迭代版) 二叉查找树的定义 构造函数 基本单元:二叉查找树节点 主体:二叉查找树 增加节点 查找节点 删除节点 二叉树的遍历 树的结构特点 树是一种有层次的数据结构,通常用于存储具有层次性的数据.比如上下级的关系图,动物的分类图等.树的类型分好几种,无序树.有序树和二叉树等等.但最常应用的还是二叉树,其特点为每个节点最多含有两个子树. 尝试手动构建一颗二叉树. 过程如下: class BinaryTreeNode { constructor(ele

  • TypeScript 基础数据结构哈希表 HashTable教程

    目录 前言 1. 哈希表介绍和特性 2. 哈希表的一些概念 3. 地址冲突解决方案 3.1 方案一:链地址法 3.2 方案二:开放地址法 4. 哈希函数代码实现 5. 哈希表封装 5.1 整体框架 v1 版 5.2 添加 put 方法 v2 版 5.3 添加 get 方法 v3 版 5.4 添加 delete 方法 v4 版 6. 哈希表的自动扩容 前言 哈希表是一种 非常重要的数据结构,几乎所有的编程语言都有 直接或者间接 的应用这种数据结构. 很多学习编程的人一直搞不懂哈希表到底是如何实现的

  • TypeScript数据结构栈结构Stack教程示例

    目录 1. 认识栈结构 2. 实现栈结构的封装 2.1 基于数组 v1 版 2.2 使用泛型重构 v2 版 3. 实战一:有效的括号 3.1 题目描述 3.2 题目分析 3.3 解一:栈 4. 实战二:下一个更大元素 I 4.1 题目描述 4.2 解一:暴力 4.3 解二:单调栈 1. 认识栈结构 栈是一种 后进先出(LIFO) 的数据结构 在 js 中没有栈,但我们可以用 数组或链表 实现栈的所有功能 栈的常用操作: push(入栈) pop(出栈) peek(返回栈顶元素) isEmpty(

  • TypeScript数据结构之队列结构Queue教程示例

    目录 1. 认识队列结构 2. 实现队列结构封装 3. 实战一:最近的请求次数 3.1 题目描述 3.2 解一:队列 4. 实战二:无法吃午餐的学生数量 4.1 题目描述 4.2 解一:队列 5. 实战三:字符串中的第一个唯一字符 5.1 题目描述 5.2 解一:哈希表 5.3 解二:队列 1. 认识队列结构 队列是一个 先进先出(FIFO) 的数据结构 js 中没有队列,但我们可以用 数组或链表 实现队列的所有功能 队列的常用操作: enqueue(element):向队列尾部添加一个(多个)

  • 数据结构TypeScript之邻接表实现示例详解

    目录 图的结构特点 图的分类 图的表示 面向对象方法封装邻接表 构造函数 增加顶点和边 删除顶点和边 图的遍历 颜色标记 广度优先搜索(队列) 深度优先搜索(栈) 图的结构特点 图由顶点和顶点之间的边构成,记为G(V, E).其中V是顶点集合,E是边集合.作为一种非线性的数据结构,顶点之间存在多对多关系. 图的分类 无向图:两个顶点之间有两条互相关联的边.A和B之间为双向互通. 有向图:两个顶点之间有一条或两条关联的边.从A到B或者从B到A,只能单向通过. 带权无向图:在无向图的基础上增加一个权

  • java数据结构与算法数组模拟队列示例详解

    目录 一.什么是队列 二.用数组来模拟队列 一.什么是队列 队列是一个有序列表,可以用数组或者链表来实现. 遵循先入先出的原则,即:先存入队列的数据,要先取出.后存入的的数据,后取出. 看一张队列的模拟图,1,2,3表示同一个队列Queue.在队列中有2个指针,front表示队首,rear表示队尾. 图1中表示队列里还没有数据,所以front跟rear初始化都是-1. 当图2中有数据进行存入的时候,front没变,而rear则随着数据的增多而改变.存入了4个数据,于是rear=3. 再看图3,f

  • TypeScript 的条件类型使用示例详解

    目录 TypeScript 的条件类型使用方式 条件类型和 keyof 组合 在条件返回中使用 T 在类型输出中使用 T 时的联合类型 使用条件类型推断类型 总结 TypeScript 的条件类型使用方式 我们可以使用 TypeScript 中的条件类型来根据逻辑定义某些类型,就像是在编写代码那样. 它采用的语法和我们在 JavaScript 中熟悉的三元运算符很像:condition ? ifConditionTrue : ifConditionFalse. 我们来看看他是怎么工作的. 假设我

  • java数据结构图论霍夫曼树及其编码示例详解

    目录 霍夫曼树 一.基本介绍 二.霍夫曼树几个重要概念和举例说明  构成霍夫曼树的步骤 霍夫曼编码 一.基本介绍 二.原理剖析 注意: 霍夫曼编码压缩文件注意事项 霍夫曼树 一.基本介绍 二.霍夫曼树几个重要概念和举例说明  构成霍夫曼树的步骤 举例:以arr = {1  3  6  7  8   13   29}  public class HuffmanTree { public static void main(String[] args) { int[] arr = { 13, 7, 8

  • C语言数据结构顺序表中的增删改(头插头删)教程示例详解

    目录 头插操作 头删操作 小结 头插操作 继上一章内容(C语言数据结构顺序表中的增删改教程示例详解),继续讲讲顺序表的基础操作. 和尾插不一样,尾插出手阔绰直接的开空间,咱头插能开吗?好像没听说过哪个接口可以在数据前面开一片空间吧,那我们思路就只有一个了——挪数据.那应该从第一位开始挪吗?注意,这和 memcpy 函数机制是一样的,并不意味着后面数据一起挪动,也不会彼此独立,而是相互影响,挪动的数据会对后面数据进行覆盖. 那我们的逻辑就应该是从后往前挪,那我们就直接定一个下标,指向这段空间的最后

  • java数据结构算法稀疏数组示例详解

    目录 一.什么是稀疏数组 二.场景用法 1.二维数组转稀疏数组思路 2.稀疏数组转二维数组思路 3.代码实现 一.什么是稀疏数组 当一个数组a中大部分元素为0,或者为同一个值,那么可以用稀疏数组b来保存数组a. 首先,稀疏数组是一个数组,然后以一种特定的方式来保存上述的数组a,具体处理方法: 记录数组a一共有几行几列 记录a中有多少个不同的值 最后记录不同值的元素所在行列,以及具体的值,放在一个小规模的数组里,以缩小程序的规模. 这个小规模的数组,就是稀疏数组. 举个栗子,左侧是一个二维数组,一

  • TypeScript 泛型推断实现示例详解

    目录 前言 基础类型准备 最终使用的方式 基于Interface的实现 (失败了) 所有内容都基于type 实现 完整Demo 结束语 前言 最近做东西都在用ts,有时候写比较复杂的功能,如果不熟悉,类型写起来还是挺麻烦的.有这样一个功能,在这里,我们就不以我们现有的业务来举例了,我们还是已Animal举例,来说明场景.通过一个工厂来创建不同的动物实例.在这里我们借助泛型来实现类型的约束和动态推到指定类型. 基础类型准备 用一个枚举来定义Animal的类型 enum EAnimalType {

  • TypeScript新语法之infer extends示例详解

    目录 正文 模式匹配 提取枚举的值的类型 总结 正文 我们知道,TypeScript 支持 infer 来提取类型的一部分,通过模式匹配的方式. 模式匹配 比如元组类型提取最后一个元素的类型: type Last<Arr extends unknown[]> = Arr extends [...infer rest,infer Ele] ? Ele : never; 比如函数提取返回值类型: type GetReturnType<Func extends Function> = F

  • Go语言数据结构之插入排序示例详解

    目录 插入排序 动画演示 Go 代码实现 总结 插入排序 插入排序,英文名(insertion sort)是一种简单且有效的比较排序算法. 思想: 在每次迭代过程中算法随机地从输入序列中移除一个元素,并将改元素插入待排序序列的正确位置.重复该过程,直到所有输入元素都被选择一次,排序结束. 插入排序有点像小时候我们抓扑克牌的方式,如果抓起一张牌,我们放在手里:抓起第二张的时候,会跟手里的第一张牌进行比较,比手里的第一张牌小放在左边,否则,放在右边. 因此,对所有的牌重复这样的操作,所以每一次都是插

  • Go语言数据结构之希尔排序示例详解

    目录 希尔排序 算法思想 图解算法 Go 代码实现: 总结 希尔排序 在插入排序中,在待排序序列的记录个数比较少,而且基本有序,则排序的效率较高. 1959 年,Donald Shell 从“减少记录个数” 和 “基本有序” 两个方面对直接插入排序进行了改进,提出了希尔排序算法. 希尔排序又称为“缩小增量排序”.即将待排序记录按下标的一定增量分组(减少记录个数),对每组记录使用直接插入排序算法排序(达到基本有序): 随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1,整个序列基本有序,再对

随机推荐