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

目录
  • 树的结构特点
  • 面向对象方法封装二叉查找树(迭代版)
    • 二叉查找树的定义
    • 构造函数
      • 基本单元:二叉查找树节点
      • 主体:二叉查找树
    • 增加节点
    • 查找节点
    • 删除节点
  • 二叉树的遍历

树的结构特点

是一种有层次的数据结构,通常用于存储具有层次性数据。比如上下级的关系图,动物的分类图等。树的类型分好几种,无序树、有序树和二叉树等等。但最常应用的还是二叉树,其特点每个节点最多含有两个子树

尝试手动构建一颗二叉树。

过程如下:

class BinaryTreeNode {
    constructor(element) {
        this.element = element
        this.left = null
        this.right = null
    }
}
let n1 = new BinaryTreeNode(1)
let n2 = new BinaryTreeNode(2)
let n3 = new BinaryTreeNode(3)
n1.left = n2
n1.right = n3
console.log(n1)
// 输出二叉树结构:
// {
//     element: 1,
//     left: { element: 2, left: null, rgiht: null },
//     right: { element: 3, left: null, rgiht: null },
// }

面向对象方法封装二叉查找树(迭代版)

二叉查找树的定义

它或者是一棵空树,或者是具有下列性质的二叉树:

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
  • 它的左、右子树也分别为二叉查找树。

构造函数

基本单元:二叉查找树节点

class BinarySearchTreeNode {
    index: number
    element: any
    left: (null | BinarySearchTreeNode)
    right: (null | BinarySearchTreeNode)
    constructor(index: number, element: any) {
        this.index = index
        this.element = element
        this.left = null
        this.right = null
    }
}

主体:二叉查找树

class BinarySearchTree {
    length: number
    root: (null | BinarySearchTreeNode)
    constructor() {
        this.length = 0
        this.root = null
    }
}

增加节点

实现思路:根据二叉查找树的有序性能够让节点不断接近它合适的插入位置。

在此之前收集的两个条件如下:

  • 已知index的值大小。
  • 二叉查找树的左子树节点值都比根节点值小右子树节点值都比根节点值大

接下来就需要利用上面这两个条件,将传入的index参数不断与树中已存在的节点的index进行大小比较。直到它在树中找到合适的位置,执行新节点的插入操作。

insert(index: number, element: any = null): BinarySearchTree {
    if (this.root === null) {
        this.root = new BinarySearchTreeNode(index, element)
    } else {
        let node: (null | BinarySearchTreeNode) = this.root
        while (1) {
            if (index === node!.index) {
                throw new Error(`${index} already exists`)
            } else if (index < node!.index) {
                if (node!.left === null) {
                    node!.left = new BinarySearchTreeNode(index, element)
                    break
                }
                node = node!.left
            } else if (index > node!.index) {
                if (node!.right === null) {
                    node!.right = new BinarySearchTreeNode(index, element)
                    break
                }
                node = node!.right
            }
        }
    }
    this.length++
    return this
}

查找节点

实现思路:让待查找节点值在遍历过程中不断接近结果。

如果当前节点不为空,在未到达叶子节点之前仍需对这颗树进行遍历,直到找到值。

如果遍历已到达叶子节点,都没有找到值。说明值根本就不存在,我们直接终止遍历。

search(index: number): (void | boolean) {
    if (this.isEmpty()) {
        throw new Error('BinarySearchTree is empty')
    } else {
        let node: (null | BinarySearchTreeNode) = this.root
        while (node !== null) {
            if (index === node!.index) {
                return true
            } else if (index &lt; node!.index) {
                node = node!.left
            } else if (index &gt; node!.index) {
                node = node!.right
            }
        }
        if (!node) { return false }
    }
}

删除节点

删除的方法依然是在迭代的基础上,需要考虑四种不同节点情况,分别如下:

  • 叶子节点:没有左右子树的节点,节点直接置空。
  • 只带左子树的节点:让它的左节点覆盖待删除节点。
  • 只带右子树的节点:让它的右节点覆盖待删除节点。
  • 带左右子树的节点:为保证二叉树的有序性,一般将待删除节点值替换为它右子树的最小值。
removeNode(node: (null | BinarySearchTreeNode)): (null | BinarySearchTreeNode) {
    if (node!.right === null && node!.left === null) {
        node = null
    } else if (node!.right === null) {
        node = node!.left
    } else if (node!.left === null) {
        node = node!.right
    } else if (node!.right !== null && node!.left !== null) {
        let temp: (null | BinarySearchTreeNode) = this.minNode(node!.right)
        this.remove(temp!.index)
        node!.index = temp!.index
        node!.element = temp!.element
        this.length++
    }
    return node
}

minNode方法:查找当前节点的右子树最小值

minNode(node: (null | BinarySearchTreeNode)): (null | BinarySearchTreeNode) {
    while (node!.left !== null) {
        node = node!.left
    }
    return node
}

remove方法:若index有效,遍历将会到达待删除节点的前一个节点,执行removeNode方法删除节点

remove(index: number): BinarySearchTree {
    if (this.isEmpty()) {
        throw new Error('BinarySearchTree is empty')
    } else {
        let node: (null | BinarySearchTreeNode) = this.root
        while (node !== null) {
            if (index === node!.index) {
                this.root = this.removeNode(node)
                break
            } else if (node!.left !== null && index === node!.left.index) {
                node!.left = this.removeNode(node!.left)
                break
            } else if (node!.right !== null && index === node!.right.index) {
                node!.right = this.removeNode(node!.right)
                break
            } else if (index < node!.index) {
                node = node!.left
            } else if (index > node!.index) {
                node = node!.right
            }
        }
        if (!node) { throw new Error(`${index} does not exist`) }
        this.length--
        return this
    }
}

注意:我们的需求是让二叉树查找树中待删除节点的前一个节点属性发生改变,让实例对象产生引用值的特点从而实现删除的效果。如果我们直接遍历到被删除节点,无论对这个节点(变量)作任何修改,都不会反映到实例对象上。来看下面的例子:

let a = { name: "小明", age: 20 }
let b = a       // a和b指向同一地址
b.age = null    // 此时产生效果,a.age也会变为null
b = null        // b被重新赋值,a和b不会指向同一地址。所以a不会变为null

二叉树的遍历

递归实现如下:

前序遍历(根左右)

export const preOrderTraversal = (tree: BinarySearchTree) => {
    let result: Array<{ index: number, element: any }> = []
    preOrderTraversalNode(tree!.root, result)
    return result
}
const preOrderTraversalNode = (
    node: (null | BinarySearchTreeNode),
    result: Array<{ index: number, element: any }>) => {
    if (node !== null) {
        result.push({ index: node!.index, element: node!.element })
        preOrderTraversalNode(node!.left, result)
        preOrderTraversalNode(node!.right, result)
    }
}

中序遍历(左根右)

export const inOrderTraversal = (tree: BinarySearchTree) => {
    let result: Array<{ index: number, element: any }> = []
    inOrderTraversalNode(tree!.root, result)
    return result
}
const inOrderTraversalNode = (
    node: (null | BinarySearchTreeNode),
    result: Array<{ index: number, element: any }>) => {
    if (node !== null) {
        inOrderTraversalNode(node!.left, result)
        result.push({ index: node!.index, element: node!.element })
        inOrderTraversalNode(node!.right, result)
    }
}

后序遍历(左右根)

export const postOrderTraversal = (tree: BinarySearchTree) => {
    let result: Array<{ index: number, element: any }> = []
    postOrderTraversalNode(tree!.root, result)
    return result
}
const postOrderTraversalNode = (
    node: (null | BinarySearchTreeNode),
    result: Array<{ index: number, element: any }>) => {
    if (node !== null) {
        postOrderTraversalNode(node!.left, result)
        postOrderTraversalNode(node!.right, result)
        result.push({ index: node!.index, element: node!.element })
    }
}

层序遍历(层级记录)

export const levelOrderTraversal = (tree: BinarySearchTree) => {
    let result: Array<Array<{ index: number, element: any }>> = []
    levelOrderTraversalNode(tree!.root, result, 0)
    return result
}
const levelOrderTraversalNode = (
    node: (null | BinarySearchTreeNode),
    result: Array<Array<{ index: number, element: any }>>, level: number) => {
    if (!result[level]) { result[level] = [] }
    result[level].push({ index: node!.index, element: node!.element })
    if (node!.left) { levelOrderTraversalNode(node!.left, result, level + 1) }
    if (node!.right) { levelOrderTraversalNode(node!.right, result, level + 1) }
}

迭代实现如下:

前序遍历(根左右)

const preOrderTraversal = (tree: BinarySearchTree) => {
    let stack: Stack = new Stack()
    let node: (null | BinarySearchTreeNode) = tree!.root
    let result: Array<{ index: number, element: any }> = []
    while (node !== null || !stack.isEmpty()) {
        while (node !== null) {
            stack.push(node)
            result.push({ index: node!.index, element: node!.element })
            node = node!.left
        }
        node = stack.pop()
        node = node!.right
    }
    return result
}

中序遍历(左根右)

const inOrderTraversal = (tree: BinarySearchTree) => {
    let stack: Stack = new Stack()
    let node: (null | BinarySearchTreeNode) = tree!.root
    let result: Array<{ index: number, element: any }> = []
    while (node !== null || !stack.isEmpty()) {
        while (node !== null) {
            stack.push(node)
            node = node!.left
        }
        node = stack.pop()
        result.push({ index: node!.index, element: node!.element })
        node = node!.right
    }
    return result
}

后序遍历(左右根)

export const postOrderTraversal = (tree: BinarySearchTree) => {
    let stack: Stack = new Stack()
    let node: (null | BinarySearchTreeNode) = tree!.root
    let result: Array<{ index: number, element: any }> = []
    let prev: (null | BinarySearchTreeNode) = null
    while (node !== null || !stack.isEmpty()) {
        while (node !== null) {
            stack.push(node)
            node = node!.left
        }
        node = stack.pop()
        if (node!.right === null || node!.right === prev) {
            result.push({ index: node!.index, element: node!.element })
            prev = node
            node = null
        } else {
            stack.push(node)
            node = node!.right
        }
    }
    return result
}

层序遍历(广度优先搜索)

const levelOrderTraversal = (tree: BinarySearchTree) => {
    let result: Array<Array<{ index: number, element: any }>> = []
    if (!(tree!.root)) { return result }
    let queue: Queue = new Queue()
    let node: (null | BinarySearchTreeNode) = tree!.root
    queue.enqueue(node)
    while (!queue.isEmpty()) {
        result.push([])
        const { length } = queue
        for (let i = 0; i < length; i++) {
            node = queue.dequeue()
            result[result.length - 1].push({ index: node!.index, element: node!.element })
            if (node!.left) { queue.enqueue(node!.left) }
            if (node!.right) { queue.enqueue(node!.right) }
        }
    }
    return result
}

至此已完成二叉查找树的增删查和遍历方法,迭代实现的优势在于JavaScript每调用一个函数就会产生一个执行上下文将其推入函数调用栈中。如果我们构建的二叉查找树十分的高,递归就有可能出现爆栈问题。

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

项目地址:

Algorithmlib|BinarySearchTree

Algorithmlib|BinaryTree Traversal

以上就是数据结构TypeScript之二叉查找树实现详解的详细内容,更多关于TypeScript数据结构二叉查找树的资料请关注我们其它相关文章!

(0)

相关推荐

  • 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,只能单向通过. 带权无向图:在无向图的基础上增加一个权

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

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

  • 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 基础数据结构哈希表 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之二叉查找树实现详解

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

  • 数据结构之树的概念详解

    数据结构树简介 一.树简介 树(Tree)是一种抽象的数据结构,是一个数据的集合,集合中的数据组成了一个树状结构.例如上图,看起来像一棵倒挂的树,根朝上叶朝下. 树是由n(n>=0)个节点组成的具有层次关系的数据集合.当 n=0 时,树中没有节点,称为空树.当 n>0 时,有且仅有一个节点被称为根节点(Root),如果 n=1 ,树只有根节点一个节点.如果 n>1 ,除根节点外,将其余的节点分成m(m>0)个互不相交的数据集合,这 m 个集合每一个都要满足树的结构(有且仅有一个根节

  • Java数据结构之平衡二叉树的实现详解

    目录 定义 结点结构 查找算法 插入算法 LL 型 RR 型 LR 型 RL 型 插入方法 删除算法 概述 实例分析 代码 完整代码 定义 动机:二叉查找树的操作实践复杂度由树高度决定,所以希望控制树高,左右子树尽可能平衡. 平衡二叉树(AVL树):称一棵二叉查找树为高度平衡树,当且仅当或由单一外结点组成,或由两个子树形 Ta 和 Tb 组成,并且满足: |h(Ta) - h(Tb)| <= 1,其中 h(T) 表示树 T 的高度 Ta 和 Tb 都是高度平衡树 即:每个结点的左子树和右子树的高

  • Python 数据结构之树的概念详解

    数据结构树简介 一.树简介 树(Tree)是一种抽象的数据结构,是一个数据的集合,集合中的数据组成了一个树状结构.例如上图,看起来像一棵倒挂的树,根朝上叶朝下. 树是由n(n>=0)个节点组成的具有层次关系的数据集合.当 n=0 时,树中没有节点,称为空树.当 n>0 时,有且仅有一个节点被称为根节点(Root),如果 n=1 ,树只有根节点一个节点.如果 n>1 ,除根节点外,将其余的节点分成m(m>0)个互不相交的数据集合,这 m 个集合每一个都要满足树的结构(有且仅有一个根节

  • redis数据结构之intset的实例详解

    redis数据结构之intset的实例详解 在redis中,intset主要用于保存整数值,由于其底层是使用数组来保存数据的,因而当对集合进行数据添加时需要对集合进行扩容和迁移操作,因而也只有在数据量不大时redis才使用该数据结构来保存整数集合.其具体的底层数据结构如下: typedef struct intset { // 编码方式 uint32_t encoding; // 集合包含的元素数量 uint32_t length; // 保存元素的数组 int8_t contents[]; }

  • 数据结构 栈的操作实例详解

    数据结构 栈的操作实例详解 说明: 往前学习数据结构,想运行一个完整的顺序栈的程序都运行不了,因为书上给的都是一部分一部分的算法,并没有提供一个完整可运行的程序,听了实验课,自己折腾了一下,总算可以写一个比较完整的顺序栈操作的小程序,对于栈也慢慢开始有了感觉.下面我会把整个程序拆开来做说明,只要把这些代码放在一个文件中,用编译器就可以直接编译运行了. 一.实现 1.程序功能 关于栈操作的经典程序,首当要提及进制数转换的问题,利用栈的操作,就可以十分快速地完成数的进制转换. 2.预定义.头文件导入

  • 数据结构之数组Array实例详解

    数据结构之数组Array实例详解 数组Array 基本操作 Status InitArray(int dimm,...)//若维数dim和随后的各维长度合法,则构造相应的数组A,并返回OK Status DestroyArray() //销毁数组A Status Locate(va_list ap,int &off) //若ap指示的各下标值合法,则求出该元素在A中相对地址off Status Value(ElemType &e,...) //A是n维数组,e为元素变量,随后是n个下标值.

  • C语言数据结构 链表与归并排序实例详解

    C语言数据结构 链表与归并排序实例详解 归并排序适合于对链表进行原址排序,即只改变指针的连接方式,不交换链表结点的内容. 归并排序的基本思想是分治法:先把一个链表分割成只有一个节点的链表,然后按照一定顺序.自底向上合并相邻的两个链表. 只要保证各种大小的子链表是有序的,那么最后返回的链表就一定是有序的. 归并排序分为分割和合并两个子过程.分割是用递归的方法,把链表对半分割成两个子链表:合并是在递归返回(回朔)的时候,把两个有序链表合并成一个有序链表. (注意:只有一个节点的链表一定是有序的) 这

  • 数据结构串的操作实例详解

    数据结构串的操作实例详解 串是一种特殊的线性表,它的每个结点是一个字符,所以串也称作字符串. 关于串的操作主要有求串长,串复制,串连接,求子串,串插入,串删除,子串定位等.串的操作也是C语言笔试中常考的一部分. 下面的代码实现了串的主要操作. #include <stdio.h> #include <stdlib.h> #define MAXSIZE 20 char *String_Create(); //创建串 int String_Length(char *s); //求串长

随机推荐