Go的数据结构与实现【Binary Search Tree】

news2024/11/25 22:25:03

介绍

本文用Go将实现二叉搜索树数据结构,以及常见的一些方法

二叉树

二叉树是一种递归数据结构,其中每个节点最多可以有两个子节点。

二叉树的一种常见类型是二叉搜索树,其中每个节点的值都大于或等于左子树中的节点值,并且小于或等于右子树中的节点值。

这是这种二叉树的直观表示:
在这里插入图片描述
实现细节方面,我们将使用一个辅助Node结构体来存储值,并保留对每个孩子的引用:

type T int

type Node struct {
   val   T
   left  *Node
   right *Node
}

然后我们将添加树的起始节点,通常称为根:

type BinSearchTree struct {
   sync.RWMutex
   root *Node
}

常见操作

现在让我们看看我们可以在二叉树上执行的最常见的操作。

插入

我们要介绍的第一个操作是插入新节点。
首先,我们必须找到要添加新节点的位置,以保持树的排序。我们将从根节点开始寻找,遵循这些规则:

  • 如果新节点的值小于当前节点的值,我们去左孩子
  • 如果新节点的值大于当前节点的值,我们去右孩子
  • 如果当前节点为空时,我们到达了一个叶节点,我们可以在该位置插入新节点

然后我们将创建一个递归方法来进行插入:

// insert internal function to find the correct place for a node in a tree
func insert(root, node *Node) {
   if node.val < root.val {
      if root.left == nil {
         root.left = node
      } else {
         insert(root.left, node)
      }
   } else {
      if root.right == nil {
         root.right = node
      } else {
         insert(root.right, node)
      }
   }
}

接下来我们将创建从根节点开始递归的公共方法:

// Insert inserts the val in the tree
func (bst *BinSearchTree) Insert(val T) {
   bst.Lock()
   defer bst.Unlock()

   node := NewTree(val)
   if bst.root == nil {
      bst.root = node
   } else {
      insert(bst.root, node)
   }
}

搜索

现在让我们添加一个方法来检查树是否包含特定值。

和以前一样,我们将首先创建一个遍历树的递归方法:

// search internal recursive function to search t in the tree
func search(root *Node, t T) bool {
   if root == nil {
      return false
   }

   if root.val == t {
      return true
   }

   if root.val > t {
      return search(root.left, t)
   } else {
      return search(root.right, t)
   }
}

在这里,我们通过将其与当前节点中的值进行比较来搜索该值;然后,我们将根据结果继续左或右孩子。

接下来我们将创建从根开始的公共方法:

// Search returns true if the t exists in the tree
func (bst *BinSearchTree) Search(t T) bool {
   bst.RLock()
   defer bst.RUnlock()

   return search(bst.root, t)
}

删除

另一种常见的操作是从树中删除一个节点。

首先,我们必须以与之前类似的方式找到要删除的节点:

// remove internal recursive function to remove t
func remove(root *Node, t T) {
   if root == nil {
      return
   }

   if root.val > t {
      remove(root.left, t)
   } else if root.val < t {
      remove(root.right, t)
   } else {
      if root.left == nil && root.right == nil {
         root = nil
         return
      } else if root.left == nil {
         root = root.right
         return
      } else if root.right == nil {
         root = root.left
         return
      } else {
         leftMostRightSide := root.right
         for {
            //find the smallest value on the right side
            if leftMostRightSide != nil && leftMostRightSide.left != nil {
               leftMostRightSide = leftMostRightSide.left
            } else {
               break
            }
         }

         root.val = leftMostRightSide.val
         remove(root.right, root.val)
         return
      }
   }
}

一旦我们找到要删除的节点,主要有 3 种不同的情况:

  • 没有孩子:这是最简单的情况;我们只需要在它的父节点中用nil替换这个节点
  • 只有一个孩子:在父节点中,我们用它唯一的孩子替换这个节点。
  • 有两个孩子:这是最复杂的情况,因为它需要树重组

最后,我们将创建从根开始删除的公共方法:

// Remove removes the t from the tree
func (bst *BinSearchTree) Remove(t T) {
   bst.Lock()
   defer bst.Unlock()

   remove(bst.root, t)
}

遍历树

在本节中,我们将探索遍历树的不同方法,前序、中序和后序遍历,这里暂时只实现递归的方法。

前序

// PreOrder visits all nodes with pre-order traversing
func (bst *BinSearchTree) PreOrder(f func(T)) {
   bst.Lock()
   defer bst.Unlock()

   preOrder(bst.root, f)
}

// preOrder internal recursive function to traverse pre-order
func preOrder(root *Node, f func(T)) {
   if root != nil {
      f(root.val)
      inOrder(root.left, f)
      inOrder(root.right, f)
   }
}

中序

// InOrder visits all nodes with in-order traversing
func (bst *BinSearchTree) InOrder(f func(T)) {
   bst.Lock()
   defer bst.Unlock()

   inOrder(bst.root, f)
}

// inOrder internal recursive function to traverse in-order
func inOrder(root *Node, f func(T)) {
   if root != nil {
      inOrder(root.left, f)
      f(root.val)
      inOrder(root.right, f)
   }
}

后序

// PreOrder visits all nodes with pre-order traversing
func (bst *BinSearchTree) PreOrder(f func(T)) {
   bst.Lock()
   defer bst.Unlock()

   preOrder(bst.root, f)
}

// preOrder internal recursive function to traverse pre-order
func preOrder(root *Node, f func(T)) {
   if root != nil {
      f(root.val)
      inOrder(root.left, f)
      inOrder(root.right, f)
   }
}

其他

本文还提供了一些其他方法,如:

  • Max()、Min():获取二叉搜索树中最大或最小值
  • Print():打印二叉搜索树
// Min returns the minimal value stored in the tree
func (bst *BinSearchTree) Min() *T {
   bst.RLock()
   defer bst.RUnlock()

   root := bst.root
   if root == nil {
      return nil
   }
   for {
      if root.left == nil {
         return &root.val
      }
      root = root.left
   }
}

// Max returns the maximal value stored in the tree
func (bst *BinSearchTree) Max() *T {
   bst.RLock()
   defer bst.RUnlock()

   root := bst.root
   if root == nil {
      return nil
   }
   for {
      if root.right == nil {
         return &root.val
      }
      root = root.right
   }
}

func (bst *BinSearchTree) Print() {
   bst.Lock()
   defer bst.Unlock()

   fmt.Println("------------------------------------------------")
   stringify(bst.root, 0)
   fmt.Println("------------------------------------------------")
}

func stringify(root *Node, level int) {
   if root != nil {
      format := ""
      for i := 0; i < level; i++ {
         format += "       "
      }
      format += "---[ "
      level++
      stringify(root.left, level)
      fmt.Printf(format+"%d\n", root.val)
      stringify(root.right, level)
   }
}

单元测试

import (
   "testing"
)

const (
   t1 T = iota + 1
   t2
   t3
   t4
   t5
   t6
   t7
   t8
   t9
   t10
   t11
)

func InitTree() *BinSearchTree {
   bst := NewBinSearchTree()
   bst.Insert(t3)
   bst.Insert(t2)
   bst.Insert(t4)
   bst.Insert(t9)
   bst.Insert(t5)
   bst.Insert(t6)
   bst.Insert(t10)
   bst.Insert(t1)
   bst.Insert(t7)
   bst.Insert(t8)

   return bst
}

func TestBinSearchTree_Insert(t *testing.T) {
   bst := InitTree()
   bst.Print()

   bst.Insert(t11)
   bst.Print()
}

func TestBinSearchTree_InOrder(t *testing.T) {
   var ret []T
   bst := InitTree()
   bst.InOrder(func(t T) {
      ret = append(ret, t)
   })

   expected := []T{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
   if !isSameSlice(ret, expected) {
      t.Errorf("Traversal order incorrect, got %v", ret)
   }
}

// isSameSlice returns true if the 2 slices are identical
func isSameSlice(a, b []T) bool {
   if a == nil && b == nil {
      return true
   }
   if a == nil || b == nil {
      return false
   }
   if len(a) != len(b) {
      return false
   }
   for i := range a {
      if a[i] != b[i] {
         return false
      }
   }
   return true
}

部分测试结果:

=== RUN   TestBinSearchTree_Insert
------------------------------------------------
              ---[ 1
       ---[ 2
---[ 3
       ---[ 4
                     ---[ 5
                            ---[ 6
                                   ---[ 7
                                          ---[ 8
              ---[ 9
                     ---[ 10
------------------------------------------------
------------------------------------------------
              ---[ 1
       ---[ 2
---[ 3
       ---[ 4
                     ---[ 5
                            ---[ 6
                                   ---[ 7
                                          ---[ 8
              ---[ 9
                     ---[ 10
                            ---[ 11
------------------------------------------------
--- PASS: TestBinSearchTree_Insert (0.00s)
PASS

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1559044.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaWeb】Day28.SpringBootWeb请求响应——请求(一)

前言&#xff1a; 我们在开发web程序时呢&#xff0c;定义了一个控制器类Controller&#xff0c;请求会被部署在Tomcat中的Controller接收&#xff0c;然后Controller再给浏览器一个响应。 而在请求响应的过程中是遵循HTTP协议的。 但是&#xff0c;在Tomcat这类Web服务器中&a…

redis学习-redis配置文件解读

目录 1.单位说明 2. include配置 3. network网络配置 3.1 bind绑定ip配置 3.2保护模式protected-mode配置 3.3端口号port配置​编辑 3.4超时断开连接timeout配置 4. general通用配置 4.1守护进程模式daemonize配置 4.2进程id存放文件pidfile配置 4.3日志级别loglevel配置 4.…

揭秘汽车制造神器:DeviceNET转Modbus TCP神操作

在现代汽车制造行业&#xff0c;汽车零部件的高效生产与精准控制是至关重要的。为了实现这一目标&#xff0c;上位机通过DeviceNET转Modbus TCP网关的技术应用越来越受到重视。这种技术不仅提高了生产线的自动化程度&#xff0c;而且确保了数据的准确传输和处理&#xff0c;为汽…

C++中的string类模拟实现

目录 string类的模拟实现 string类的构造函数 string类拷贝构造函数 string类析构函数 string类c_str()函数 string类中的[]运算符重载函数 string类中的赋值运算符重载 string类中获取字符串有效字符个数 string类中获取字符串存储空间大小&#xff08;不包括\0&…

力扣 1035. 不相交的线

题目来源&#xff1a;https://leetcode.cn/problems/uncrossed-lines/description/ C题解&#xff1a;经过细细一推导&#xff0c;就发现跟力扣 1143. 最长公共子序列-CSDN博客 换汤不换药。 直线不能相交&#xff0c;说明元素顺序不能改变&#xff0c;求可以绘制的最大连线数…

设计方案-定时任务接口数据存储及更新策略

前言 在没有使用ETL工具且不考虑多数据源的情况下&#xff0c;我们需要从别的系统获取数据时&#xff0c;一般会选择分页接口查询并存储。本文算是我对类似场景代码的提炼&#xff0c;旨在总结相关套路&#xff0c;提升自我对数据库和模块的设计能力。 ETL(英文 Extract-Trans…

LeetCode Python - 81. 搜索旋转排序数组 II

目录 题目描述解法运行结果 题目描述 已知存在一个按非降序排列的整数数组 nums &#xff0c;数组中的值不必互不相同。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转 &#xff0c;使数组变为 […

社交网络的未来:Facebook如何塑造数字社交的下一章

引言 社交网络已成为我们生活中不可或缺的一部分&#xff0c;而Facebook作为其领军者&#xff0c;一直在塑造着数字社交的未来。本文将深入探讨Facebook在未来如何塑造数字社交的下一章&#xff0c;并对社交网络的发展趋势进行展望和分析。 1. 引领虚拟社交的潮流 Facebook将…

vulnhub靶场之driftingblues-4

一.环境搭建 1.靶场描述 get flags difficulty: easy about vm: tested and exported from virtualbox. dhcp and nested vtx/amdv enabled. you can contact me by email for troubleshooting or questions. This works better with VirtualBox rather than VMware. 2.靶场…

O(1)空间复杂度反转/逆置单链表

写法1 王道书上的 从头结点开始&#xff0c;将链表中的每个节点取下来&#xff0c;逐个放在头结点后面&#xff0c; 维护三个指针 p,q,r &#xff0c;p指向头结点&#xff0c;q为当前操作节点&#xff0c;r为下一个节点 将q指向p的下一个节点&#xff08;也就是反转后的第一…

CSS3新增的语法(三)

CSS3新增的语法&#xff08;三&#xff09; 10.2D变换10.1. 2D位移10.2. 2D缩放10.3. 2D旋转10.4. 2D扭曲&#xff08;了解&#xff09;10.5. 多重变换10.6. 变换原点 11. 3D变换11.1. 开启3D空间11.2. 设置景深11.3. 透视点位置11.4. 3D 位移11.5. 3D 旋转11.6. 3D 缩放11.7. …

【数据结构与算法初阶(c语言)】插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序-全梳理(万字详解,干货满满,建议三连收藏)

目录 1.排序的概念及其运用 1.1排序的概念 1.2排序运用 1.3常见的排序算法 2.插入排序 2.1 原理演示&#xff1a;​编辑 2.2 算法实现 2.3 算法的时间复杂度和空间复杂度分析 3.希尔排序 3.1算法思想 3.2原理演示 3.3代码实现 3.4希尔算法的时间复杂度 4.冒泡排序 4.1冒泡排…

win11运行vmware报错“此平台不支持虚拟化的 amd-v/rvi”问题(已解决)

背景&#xff1a; Windows11 安装vmware17 player运行eve需要打开there &#xff08;reference:https://docs.vmware.com/cn/VMware-Workstation-Player-for-Windows/17.0/com.vmware.player.win.using.doc/GUID-3140DF1F-A105-4EED-B9E8-D99B3D3F0447.html&#xff09; 但是…

基于ARM内核的智能手环(day4)

回顾 单片机延时方法总结 空函数延时(delay) 使用空函数来进行延时操作。简单易用&#xff0c;但延时时间不够精确&#xff0c;且阻塞式。定时器延时(delay) 通过定时器的计数器进行延时操作&#xff0c;提供精确的延时时间&#xff0c;但是仍为阻塞式延时。定时器中断延时 利…

小白学Java成长日记第二篇

哈喽&#xff0c;小伙伴们&#xff0c;我又回来了&#xff0c;还记得上一篇我们讲了什么内容吗&#xff1f;what!你说已经忘记了&#xff1f;&#xff0c;没事那我们先复习一下吧。 上集回顾&#xff1a; Java的两层皮&#xff08;主体架构&#xff09;&#xff1a; public …

Python绘制线图之plt.plot()的介绍以及使用

在Python中plt.plot是matplotlib库中的一个函数,用于绘制点和线,并对其样式进行控制,下面这篇文章主要给大家介绍了关于Python绘制线图之plt.plot()的介绍以及使用的相关资料,需要的朋友可以参考下 plt.plot() 是Matplotlib库中用于绘制线图&#xff08;折线图&#xff09;的主…

MySQL学习之连接查询

笛卡尔乘积现象 在表的连接查询方面有一种现象被称为&#xff1a;笛卡尔积现象。 笛卡尔积现象&#xff1a; 当两张表进行连接查询的时候&#xff0c;没有任何条件进行限制&#xff0c;最终的查询结果条数是两张表记录条数的乘积。 select ename,dname from emp,dept; 避免…

力扣刷题Days28-第二题-11.盛水最多的容器(js)

目录 1&#xff0c;题目 2&#xff0c;代码 3&#xff0c;学习与总结 3.1思路回顾 1&#xff0c;如何遍历 2&#xff0c;算法流程 3.2剖析问题 1&#xff0c;题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, h…

ES学习日记(四)-------插件head安装和一些配套插件下载

前言 接上节,第三方插件选择了时间久,功能丰富,长得丑的head,head 插件在ES 5版本以前开箱即用非常简单&#xff0c;ES 5版本以后需要运行在node环境下&#xff0c;所以我们要先准备一下环境 一.安装Git yum -y install git 二.安装node 安装包位置node for linux下载 解压…

省级-能源结构数据(电力消费水平)(2000-2022年)

能源结构指能源总生产量或总消费量中各类一次能源、二次能源的构成及其比例关系。它是能源系统工程研究的重要内容&#xff0c;直接影响着国民经济各部门的最终用能方式&#xff0c;并反映了人民的生活水平。能源结构主要由生产结构和消费结构组成。 本数据通过电力消费水平来…