数据结构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日

相关文章

  • 【ACM算法竞赛日常训练】DAY10题解与分析【月月给华华出题】【华华给月月出题】| 筛法 | 欧拉函数 | 数论

    DAY10共2题: 月月给华华出题 华华给月月出题 难度较大。 ? 作者:Eriktse? 简介:211计算机在读,现役ACM银牌选手?力争以通俗易懂的方式讲解算法!❤️欢迎关注我,一起交流C++/Python算法。(优质好文持续更新中……)?? 原文链接(阅读原文获得更好阅读体验):https://www.eriktse.com/algorithm/110…

    算法与数据结构 2023年4月17日
    00
  • java数据结构与算法数组模拟队列示例详解

    下面是“java数据结构与算法数组模拟队列示例详解”的完整攻略。 标题 Java数据结构与算法:数组模拟队列示例详解 简介 本文将以Java语言为例,详细讲解如何使用数组模拟队列。对于初学者来说,队列是一个非常基础的数据结构,掌握其实现方法可以帮助进一步理解其他的数据结构和算法。 队列的定义 队列(Queue)是一种先进先出(First In First O…

    数据结构 2023年5月17日
    00
  • 一起来看看C语言线性表的线性链表

    一起来看看C语言线性表的线性链表攻略 线性链表概述 线性链表是线性表的一种实现方式,它通过每个节点中包含指向下一个节点的指针来实现表中元素之间的链接,可以动态地增加、删除节点。线性链表分为带头节点的链表和不带头节点的链表,其中带头节点的链表更为常见。 实现思路 结构体定义 我们可以定义一个结构体来表示每个节点,例如: typedef struct ListN…

    数据结构 2023年5月17日
    00
  • 【ACM数论】和式变换技术,也许是最好的讲解之一

    在做数论题时,往往需要进行和式变换,然后变换成我们可以处理的和式,再针对和式做筛法、整除分块等操作。 本文将介绍一些常见的和式变换技术。 以下出现的概念大部分为个人总结,未必是学术界/竞赛界的统一说法,有不严谨的地方请谅解。 ? 作者:Eriktse? 简介:19岁,211计算机在读,现役ACM银牌选手?力争以通俗易懂的方式讲解算法!❤️欢迎关注我,一起交流…

    算法与数据结构 2023年4月17日
    00
  • 排序算法之详解冒泡排序

    引入 冒泡排序顾名思义,就是像冒泡一样,泡泡在水里慢慢升上来,由小变大。 虽然冒泡排序和冒泡并不完全一样,但却可以帮助我们理解冒泡排序。 思路 一组无序的数组,要求我们从小到大排列 我们可以先将最大的元素放在数组末尾 再将第二大的数放在数组的倒数第二个位置 再将第三大的数放在数组的倒数第三个位置 以此类推 那么现在问题的关键就是如何将 第 n 大的数 放在 …

    算法与数据结构 2023年4月25日
    00
  • C语言数据结构之算法的时间复杂度

    关于C语言数据结构之算法的时间复杂度,需要先了解一些基本概念。 什么是时间复杂度 时间复杂度是算法的一种衡量标准,用于评估算法的执行效率。表示代码执行的时间和数据量之间的关系,通常用大O符号来表示,称为“大O记法”。 时间复杂度的分类 时间复杂度可分为以下几类: 常数阶:O(1) 对数阶:O(log n) 线性阶:O(n) 线性对数阶:O(n log n) …

    数据结构 2023年5月17日
    00
  • C语言数据结构深入探索顺序表

    C语言数据结构深入探索顺序表攻略 一、概述 顺序表是一种线性结构,是计算机程序中最常见的数据结构之一。在C语言中,顺序表可以用数组来实现。本篇文章将深入讲解顺序表的原理和实现方法,帮助读者加深对顺序表的理解,并掌握如何用C语言代码实现顺序表。 二、顺序表的定义和特点 顺序表是指用一组地址连续的存储单元依次存储线性表中的各个元素,用于表示具有相同数据类型的n个…

    数据结构 2023年5月17日
    00
  • 「线段树」!(简单)的线段树

    本题为3月20日23上半学期集训每日一题中B题的题解 题面 题目描述 给你一个序列 \(A[1],A[2],…,A[n]\) .( \(|A[i]| \leq 15007, 1 \leq N \leq 50,000\) ). M( \(1 \leq M \leq 500,000\) ) 次询问,每次询问 \(Query(x, y) = Max{A[i] …

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