JS数据结构-树结构-基本理论

一.树的术语

  1. 树(tree):n(n>=0)个节点构成的有限集合,当n=0时,称为空树
  2. 对于任一棵非空树(n>0),它具备以下性质:
    a.树中有一个称为“根(root)”的特殊节点,用r表示
    b.其余节点可分为m(m>0)个互不相交的有限集T1,T2,…Tm,其中每个集合本身又是一棵树,称为原来树的“子树(SubTree)

二.二叉树

  1. 如果树中的每个节点最多只能有两个子节点,这样的树就称为“二叉树”。实际上,所有的树都可以用二叉树模拟出来
  2. 二叉树的特性:
    a.一个二叉树第i层的最大节点数为:2^(i-1),i>=1
    b.深度为k的二叉树有最大节点总数为:2^k-1,k>=1
    c.对任意非空二叉树T,若n0表示叶子节点个数,n2表示度为2的非叶节点个数,那么两者满足关系n0=n2+1
  3. 完美二叉树:也称为满二叉树,在二叉树中,除了最下一层的叶节点外,每层节点都有两个子节点,就构成了满二叉树
  4. 完全二叉树:除二叉树最后一层外,其他个层的节点数都达到最大个数;且最后一层从左向右的叶节点连续存在只缺右侧若干节点;完美二叉树是特殊的完全二叉树
  5. 二叉树的存储:二叉树常见的存储方式是采用链表存储,每个节点封装成一个node,node中包含存储的数据,左节点的引用,右节点的引用

三.二叉搜索树

  1. 二叉搜索树(BST,Binary Search Tree),也称为二叉排序树二叉查找树
  2. 二叉搜索树是一棵二叉树,可以为空
  3. 如果不为空,需要满足以下性质
    a.非空左子树的所有键值小于根节点的键值
    b.非空右子树的所有键值大于根节点的键值
    c.左、右子树本身也是二叉搜索树
  4. 二叉搜索树的特点
    a.二叉搜索树的特点就是相对较小的值总是保存在左节点上,相对较大的值总是保存在右节点
    b.通过这一特点,查找效率将会非常高
    c.获取最大值和最小值会非常方便,即最左侧的叶子节点值最小,最右侧的叶子节点值最大

四.二叉搜索树的基本操作

  1. 封装
// 封装二叉搜索树
function BinarySearchTree(){
	function Node(key){
		this.key = key
		this.left = null
		this.right = null
	}
	// 属性
	this.root = null
	// 方法
}
  1. 插入数据
// 插入数据
BinarySearchTree.prototype.insert = function(key){
	// 1.根据key创建节点
	var newNode = new Node(key)
	// 2.判断root是否存在
	if(this.root == null){
		this.root = newNode
	}else{
		this.insertNode(this.root,newNode)
	}
}

//递归操作
BinarySearchTree.prototype.insertNode = function(node,newNode){
	if(newNode.key < node.key){ // 向左查找
		if(node.left == null){
			node.left = newNode
		}else{
			this.insertNode(node.left,newNode)
		}
	}else{// 向右查找
		if(node.right == null){
			node.right = newNode
		}else{
			this.insertNode(node.right,newNode)
		}
	}
}

3.遍历二叉搜索树
遍历一棵树是指访问树的每一个节点,以下所提出的遍历方法,针对所有的二叉树都适用,不仅仅是二叉搜索树

二叉树的常见遍历有三种方法:

  • 先序遍历
    a.先访问根节点
    b.先序遍历其左子树
    c.先序遍历其右子树
    在这里插入图片描述
    代码示例:
BinarySearchTree.prototype.preOrderTraversal = function(handler){
	this.preOrderTraversalNode(this.root,handler)
}

BinarySearchTree.prototype.preOrderTraversalNode = function(node,handler){
	if(node != null){
		// 1.处理经过的节点
		handler(node.key)
		// 2.处理经过节点的左子节点
		this.preOrderTraversalNode(node.left,handler)
		// 3.处理经过节点的右子节点
		this.preOrderTraversalNode(node.right,handler)
	}
}

// 测试遍历
var resultString = ''
bst.preOrderTraversal(function(key){
	resultString += key + ' '
})
alert(resultString)
  • 中序遍历
    a.中序遍历其左子树
    b.访问根节点
    c.中序遍历其右子树
    在这里插入图片描述

代码示例:

BinarySearchTree.prototype.midOrderTraversal = function(handler){
	this.midOrderTraversalNode(this.root,handler)
}

BinarySearchTree.prototype.midOrderTraversalNode = function(node,handler){
	if(node != null){
		// 1.处理左子树中的节点
		this.midOrderTraversalNode(node.left,handler)
		// 2.处理节点
		handler(node.key)
		// 3.处理右子树中的节点
		this.midOrderTraversalNode(node.right,handler)
	}
}
  • 后序遍历
    a.后序遍历其左子树
    b.后序遍历其右子树
    c.访问根节点
    在这里插入图片描述
    代码示例:
BinarySearchTree.prototype.postOrderTraversal = function(handler){
	this.postOrderTraversalNode(this.root,handler)
}

BinarySearchTree.prototype.postOrderTraversalNode = function(node,handler){
	if(node != null){
		// 1.处理左子树中的节点
		this.postOrderTraversalNode(node.left,handler)
		// 2.处理右子树中的节点
		this.postOrderTraversalNode(node.right,handler)
		// 3.处理节点
		handler(node.key)
	}
}

四.二叉树的搜索

  1. 获取最大值&最小值
// 查找最大值
BinarySearchTree.prototype.max = function(){
	// 1.获取根节点
	var node = this.root
	// 2.持续向右查找,直到找到最右侧节点
	while(node.right != null){
			node = node.right
	}
	return node.key
}

// 查找最小值
BinarySearchTree.prototype.min = function(){
	// 1.获取根节点
	var node = this.root
	// 2.持续向左查找,直到找到最左侧节点
	while(node.left != null){
			node = node.left
	}
	return node.key
}
  1. 搜索特定的值
    提供两种方法实现这一功能:递归和循环

a.递归法:

BinarySearchTree.prototype.search = function(key){
	return this.searchNode(this.root,key)
}
BinarySearchTree.prototype.searchNode = function(node,key){
	// 1.如果传入的node为null,那么就退出递归
	if(node === null){
		return false
	}
	// 2.比较node节点的值和key值的大小
	if(node.key > key){ // 2.1. 传入的key较小,继续向左查找
		return this.searchNode(node.left,key)
	}else if(node.key < key){ // 2.2. 传入的key较大,继续向右查找
		return this.searchNode(node.right,key)
	}else{ // 2.3.表示相等,即找到了key
		return true
	}
}

b.循环法:

BinarySearchTree.prototype.search = function(key){
	var node = this.root
	while(node != null){
		if(node.key > key){
			node = node.left
		}else if(node.key < key){
			node = node.right
		}else{
			return true
		}
	}
	return false
}

五.二叉搜索树的删除

1.分析删除流程:
a.先找到要删除的节点,如果没有找到,则不需要删除
b.找到要删除的节点,需要考虑三种情况
情况一:删除叶子节点
情况二:删除只有一个子节点的节点
情况三:删除有两个子节点的节点

注意:执行删除操作时,一定要考虑根节点的情况

2.代码示例

BinarySearchTree.prototype.remove = function(key){
	// 1.寻找要删除的节点
	// 1.1.定义变量,保存一些信息
	var current = this.root
	var parent = null
	var isLeftChild = true
	// 1.2.开始寻找删除的节点
	while(current.key != key){
		parent = current
		if(current.key > key){
			isLeftChild = true
			current = current.left
		}else{
			isLeftChild = false
			current = current.right
		}
		// 某种情况:已经找到最后一个节点,依然没有找到key
		if(current == null) return false
	}
	// 2.找到了current.key == key
	
	// 2.1.删除的节点是叶子节点(没有子节点)
	if(current.left == null && current.right == null){
		if(current == this.root){ // 即整棵树只有根节点
			this.root = null
		}else if(isLeftChild){
			parent.left = null
		}else{
			parent.right = null 
		}
	}
	 // 2.2.删除的节点有一个子节点
	 else if(current.right == null){
	 	if(current == this.root){
	 		this.root = current.left
	 	}else if(isLeftChild){
	 		parent.left = current.left
	 	}else{
	 		parent.right = current.left
	 	}
	 }else if(current.left == null){
	 	if(current == this.root){
	 		this.root = current.right
	 	}else if(isLeftChild){
	 		parent.left = current.right
	 	}else{
	 		parent.right = current.right
	 	}
	 }
}

3.删除有两个子节点的节点是比较复杂的,逻辑上会选择其左子树的最大值右子树的最小值填补删除的空节点。

在二叉搜索树中,这两个特殊的节点有特殊的名字
比current小一点点的节点,称为current节点的前驱。比如下图中的节点5就是节点7的前驱;
比current大一点点的节点,称为current节点的后继。比如下图中的节点8就是节点7的后继;
在这里插入图片描述

  • 查找需要被删除的节点current的后继时,需要在current的右子树中查找最小值,即在current的右子树中一直向左遍历查找;
  • 查找前驱时,则需要在current的左子树中查找最大值,即在current的左子树中一直向右遍历查找

代码示例:

		 else{
         //1.获取后继节点
          let successor = this.getSuccessor(current)

          //2.判断是否根节点
          if (current == this.root) {
            this.root = successor
          }else if (isLeftChild){
            parent.left = successor
          }else{
            parent.right = successor
          }

          //3.将后继的左子节点改为被删除节点的左子节点
          successor.left = current.left
        }
      }

      //封装查找后继的方法
      BinarySearchTree.prototype.getSuccessor = function(delNode){
        //1.定义变量,保存找到的后继
        let successor = delNode
        let current = delNode.right
        let successorParent = delNode

        //2.循环查找current的右子树节点
        while(current != null){
          successorParent = successor
          successor = current
          current = current.left
        }

        //3.判断寻找到的后继节点是否直接就是删除节点的right节点
        if(successor != delNode.right){
          successorParent.left = successor.right
          successor.right = delNode.right 
        }
        return successor
      }

六.非平衡树

  • 比较好的二叉搜索树应该是左右分布均匀
  • 但是当插入连续数据后,分布的不均匀,称这种树为非平衡树
  • 对于一棵平衡二叉树来说,查找/插入等操作的效率是O(logN)
  • 对于一棵非平衡二叉树,相当于编写了一个链表,查找效率变为O(N)

1.树的平衡性

  • 为了能以较快的速度O(logN)操作一棵树,需要保证树总是平衡
  • 平衡树也是二叉搜索树
    - 目前常见的平衡树为–AVL树和红黑树
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值