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

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

什么是二叉查找树

二叉查找树(Binary Search Tree,简称BST)是一种基础的数据结构,也是一种常用的搜索算法。它通过以二叉树的形式表示各个结点之间的关系,实现了快速查找、添加、删除等操作。对于任何一个节点,其左子树上的节点值均小于该节点的值,右子树上的节点值均大于该节点的值。

二叉查找树的实现

下面我们将分别介绍二叉查找树的构造方法、插入、查找和删除等操作的实现。

构造方法

构造方法的主要作用是创建一棵空树。由于我们要实现的是泛型的BST,因此需要在定义中使用泛型T。在新建二叉查找树时,我们仅需设置其根节点为null即可。

class BinarySearchTree<T> {
  private root: BSTNode<T> | null;

  constructor() {
    this.root = null;
  }
}

插入

插入操作分为两步:查找插入节点的位置和将新节点插入到该位置。

  1. 查找插入节点的位置

查找插入节点的位置需要从根节点开始递归查找。对于当前节点,如果插入节点的值小于该节点的值,则继续递归查找该节点的左子树;否则继续递归查找该节点的右子树。如果递归到最底层节点时,仍未找到合适的插入位置,则说明该节点为插入节点的位置。

private findInsertPos(val: T): BSTNode<T> {
  let current = this.root;
  let parent = null;

  while (current !== null) {
    parent = current;
    if (val < current.val) {
      current = current.left;
    } else if (val > current.val) {
      current = current.right;
    } else {
      // 已存在该节点,不需要插入
      return null;
    }
  }

  // parent为插入节点的父节点
  return parent;
}
  1. 将新节点插入到该位置

找到插入位置的父节点后,我们需要将新节点插入到该位置。由于二叉查找树要求,所有左子树上的节点值均小于该节点的值,右子树上的节点值均大于该节点的值,因此需要先将新节点的值与父节点进行比较,然后决定将新节点插入到左子树或右子树。

public insert(val: T): void {
  const parent = this.findInsertPos(val);

  if (parent === null) {
    // 已存在该节点,不需要插入
    return;
  }

  const node = new BSTNode(val);
  if (parent.val > val) {
    parent.left = node;
  } else {
    parent.right = node;
  }
}

查找

查找操作的实现与插入操作相似,也需要从根节点开始递归查找,但对于查找的值与当前节点的值大小关系,我们有三种情况需要考虑:查找值等于当前节点值、查找值小于当前节点值、查找值大于当前节点值。

private find(val: T, current: BSTNode<T>): BSTNode<T> {
  if (current === null) {
    return null;
  }

  if (val === current.val) {
    // 查找值等于当前节点值
    return current;
  } else if (val < current.val) {
    // 查找值小于当前节点值,递归查找左子树
    return this.find(val, current.left);
  } else {
    // 查找值大于当前节点值,递归查找右子树
    return this.find(val, current.right);
  }
}

public search(val: T): boolean {
  return this.find(val, this.root) !== null;
}

删除

删除操作最复杂的地方在于删除节点时需要查找该节点的后继(即以当前节点为根的子树上的最小节点)或前驱(即以当前节点为根的子树上的最大节点)。

  1. 查找目标节点

查找目标节点时需要从根节点开始递归查找,找到要删除的节点。

private findDeleteNode(val: T, current: BSTNode<T>, parent: BSTNode<T>): BSTNode<T> {
  if (current === null) {
    return null;
  } else if (val === current.val) {
    // 找到目标节点
    return { node: current, parent };
  } else if (val < current.val) {
    // 查找左子树
    return this.findDeleteNode(val, current.left, current);
  } else {
    // 查找右子树
    return this.findDeleteNode(val, current.right, current);
  }
}
  1. 删除目标节点

当找到目标节点后,我们需要考虑以下三种情况。

  • 目标节点没有子节点:直接删除目标节点即可;
  • 目标节点只有一个子节点:将目标节点的父节点指向目标节点的子节点;
  • 目标节点有两个子节点:找到目标节点的后继或前驱,并将其值赋给目标节点,然后删除后继或前驱节点。
public delete(val: T): void {
  let { node, parent } = this.findDeleteNode(val, this.root, null);

  if (node === null) {
    // 没有找到目标节点
    return;
  }

  if (node.left === null && node.right === null) {
    // 目标节点没有子节点
    if (parent === null) {
      // 删除根节点
      this.root = null;
    } else if (parent.left === node) {
      parent.left = null;
    } else {
      parent.right = null;
    }
  } else if (node.left === null || node.right === null) {
    // 目标节点只有一个子节点
    const child = node.left || node.right;
    if (parent === null) {
      // 删除根节点
      this.root = child;
    } else if (parent.left === node) {
      parent.left = child;
    } else {
      parent.right = child;
    }
  } else {
    // 目标节点有两个子节点,找到后继节点
    let successor = node.right;
    let successorParent = node;
    while (successor.left !== null) {
      successorParent = successor;
      successor = successor.left;
    }

    // 将后继节点的值赋给目标节点,然后删除后继节点
    node.val = successor.val;
    if (successorParent.left === successor) {
      successorParent.left = successor.right;
    } else {
      successorParent.right = successor.right;
    }
  }
}

示例

假设我们有一组整数需要插入到二叉查找树中。

const nums = [20, 5, 12, 30, 28, 15, 18, 35, 25, 40];
const bst = new BinarySearchTree<number>();

for (const num of nums) {
  bst.insert(num);
}

插入完成后,我们可以使用以下方法检查树是否构造正确,并输出中序遍历结果。

function inOrder(node: BSTNode<any>, arr: any[]): void {
  if (node !== null) {
    inOrder(node.left, arr);
    arr.push(node.val);
    inOrder(node.right, arr);
  }
}

const res: any[] = [];
inOrder(bst.root, res);
console.log(res);

输出结果为:

[5, 12, 15, 18, 20, 25, 28, 30, 35, 40]

接下来,我们可以使用以下代码测试删除操作。

bst.delete(20);  // 删除根节点
bst.delete(5);   // 删除叶子节点
bst.delete(30);  // 删除有一子节点的节点
bst.delete(15);  // 删除有两子节点的节点

const res: any[] = [];
inOrder(bst.root, res);
console.log(res);

输出结果为:

[12, 18, 25, 28, 35, 40]

从结果中可以看出,二叉查找树插入和删除操作都能够正常工作,可以使用该数据结构实现高效的查找、添加、删除等操作。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:数据结构TypeScript之二叉查找树实现详解 - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • Java数据结构之单链表详解

    下面是单链表攻略的详细讲解。 什么是单链表? 单链表是一种线性数据结构,它由一系列结点组成,每个结点包含数据域和指针域。数据域用于存储数据,指针域用于指向下一个结点。单链表的优点是插入和删除操作的时间复杂度为O(1),缺点是随机访问的时间复杂度为O(n)。 单链表的基本操作 单链表的基本操作包括插入操作、删除操作、查找操作和遍历操作。下面将分别介绍这些操作。…

    数据结构 2023年5月17日
    00
  • 带你了解Java数据结构和算法之哈希表

    带你了解Java数据结构和算法之哈希表 前言 哈希表是一种常用的数据结构,它可以高效地存储和查询数据。在计算机科学领域,哈希表广泛用于实现关联数组(Associative Array)和哈希集合(Hash Set)。本文将带领大家深入了解哈希表数据结构及常用算法实现。 哈希表的原理 哈希表是根据关键码值(Key Value)而直接进行访问的数据结构。也就是说…

    数据结构 2023年5月17日
    00
  • JavaScript数据结构之广义表的定义与表示方法详解

    JavaScript数据结构之广义表的定义与表示方法详解 什么是广义表 广义表是一种包含了无数元素的数据结构,可以是元素或者嵌套的子表。广义表可以表示为: $\quad\quad\quad GL = (a_0,a_1,…,a_n)$ 其中$a_i$可以是元素或者一个子表,如果$a_i$为一个子表,那么$a_i$本身也是一个广义表。广义表中,第一个元素$a…

    数据结构 2023年5月17日
    00
  • 详解数据结构C语言实现之循环队列

    详解数据结构C语言实现之循环队列 什么是循环队列 循环队列是一种数据结构,它可以存储一组固定大小的元素,并且支持在队列尾部插入元素和在队列头部删除元素,当队列尾部没有空间时可以将队列头部空余的位置用来插入元素,实现循环的效果。循环队列的主要优点在于插入和删除元素的时间复杂度均为O(1),而不是O(n)。 如何实现循环队列 循环队列可以使用数组来实现,需要定义…

    数据结构 2023年5月17日
    00
  • JavaScript的Set数据结构详解

    JavaScript中的Set数据结构详解 什么是Set? Set 是一种 Javascript 内置的数据结构。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set 本身是一个构造函数,可以通过new关键字来创建 Set 数据结构。 let mySet = new Set(); Set的基本用法 Set实例对象有以下常用方法: add(value):…

    数据结构 2023年5月17日
    00
  • C++数据结构之二叉搜索树的实现详解

    C++数据结构之二叉搜索树的实现详解 1. 什么是二叉搜索树? 二叉搜索树是一种二叉树,其中每个节点都包含一个键值,且每个节点的键值都大于其左子树中任何节点的键值,小于其右子树中任何节点的键值。如下图所示: 9 / \ 4 15 / \ 12 20 在上面的二叉搜索树中,节点的键值分别是9, 4, 15, 12, 20,且每个节点的键值都符合上述定义。 2.…

    数据结构 2023年5月17日
    00
  • C#数据结构之顺序表(SeqList)实例详解

    C#数据结构之顺序表(SeqList)实例详解 顺序表(SeqList)概述 顺序表(SeqList)是一种线性表存储结构,它的特点是元素的存储位置是连续的。因为它的存储结构是数组,所以在访问和修改元素时,可以通过数组下标进行快速定位。顺序表在内存中的存储相对紧凑,因此查找和修改效率都很高,适用于大多数元素较少、但是需要频繁访问的场景。 实现顺序表(SeqL…

    数据结构 2023年5月17日
    00
  • 「学习笔记」数位 DP

    「学习笔记」数位 DP 意义不大的题不写了。 点击查看目录 目录 「学习笔记」数位 DP 概述 例题 P2657 [SCOI2009] windy 数 思路 代码 P4317 花神的数论题 思路 P4124 [CQOI2016]手机号码 思路 代码 haha数 题意 思路 代码 0和1的熟练 题意 思路 代码 苍与红的试炼 题意 思路 代码 概述 数位 DP…

    算法与数据结构 2023年4月17日
    00
合作推广
合作推广
分享本页
返回顶部