树结构
树结构示意图
树的术语
树(tree):n个节点构成的有限集合,当n=0时称为空树,对于任一非空树,具有一个根节点用r表示,其余节点可分为m(m>0)个互不相交的有限集T1,T2,T3等,其中每一个集合本身又是一棵树,称为原来树的子树
节点的度(Degree):节点的子树个数
树的度:树的所有节点中最大的度数
叶节点(Leaf):度为0的节点(也称为叶子节点)
父节点(Parent):有子树的节点是其子树的根节点的父节点
子节点(Child):若A节点是B节点的父节点,则称B节点是A节点的子节点;子节点也称为孩子节点
兄弟节点(Sibling):具有同一父节点的各节点彼此是兄弟节点
路径和路径长度:从节点n1到nk的路径为一个节点序列n1,n2......nk,ni是ni+1的父节点,路径包含边的个数为路径的长度
节点的层次(Level):规定根节点在1层,其他任一节点的层数是其父节点的层数加一
树的深度(Depth):树中所有节点中的最大层次是这棵树的深度
树的表示方法
二叉树
如果树中每个节点最多只能有两个子节点,这样的树就称为二叉树
二叉树的定义:
1、二叉树可以为空,也就是没有节点
2、若不为空,则它是由根节点和称为其左子树TL和右子树TR的两个互不相交的二叉树组成
二叉树有五种形态:
二叉树的特性:
一个二叉树第i层的最大节点树为:2^(i-1),i>=1
深度为k的二叉树有最大节点总数为:2^k-1,k>=1
对任何非空二叉树T,若n0表示叶节点的个数,n2是度为2的非叶节点个数,那么两者满足关系n0=n2+1
完美二叉树
在二叉树中,除了最下一层的叶节点外,每层节点都有2个子节点,就构成了满二叉树
完全二叉树
除二叉树最后一层外,其他各层的节点数都达到最大个数,且最后一层从左向右的叶节点连续存在,只缺右侧若干节点,完美二叉树是特殊的完全二叉树
二叉树的存储
二叉树常见的存储方式是数组和链表
使用数组
完全二叉树:按从上至下、从左至右顺序存储
非完全二叉树:非完全二叉树要转成完全二叉树才可以按照上面的方案存储,但是会造成很大的空间浪费
二叉树最常见的方式还是使用链表存储
每个节点封装成一个Node,Node中包含存储的数据,左节点的引用,右节点的引用
二叉搜索树
二叉搜索树(BST,Binary Search Tree)也称二叉排序树或者二叉查找树
二叉搜索树是一颗二叉树,可以空
如果不为空,满足一下性质:
- 非空左子树的所有键值小于其根节点的键值
- 非空右子树的所有键值大于其根节点的键值
- 左、右子树本身也都是二叉搜索树
二叉搜索树的特点:
二叉搜索树的特点就是相对较小的值总是保存在左节点上,相对较大的值总是保存在右节点上,二叉搜索树的查找效率特别高
二叉搜索树常见的操作方法
<1> insert(key):向树中插入一个新的键
<2> search(key):在树中查找一个键,如果节点存在,则返回true,如果不存在,则返回false
<3> inOrderTraverse:通过中序遍历方式遍历所有节点
<4> preOrderTraverse:通过先序遍历方式遍历所有节点
<5> postOrderTraverse:通过后序遍历方式遍历所有节点
<6> min:返回树中最小的值/键
<7> max:返回树中最大的值/键
<8> remove(key):从树中移除某个键
封装二叉搜索树
//封装二叉搜索树 function BinarySearchTree() { function Node(key) { this.key = key this.left = null this.right = null } this.root = null }
insert方法
//封装二叉搜索树 function BinarySearchTree() { function Node(key) { this.key = key this.left = null this.right = null } this.root = null BinarySearchTree.prototype.insert = function (key) { //!、根据key创建节点 var newNode = new Node(key) //2、判断是否存在根节点 if (this.root == null) { this.root = newNode } else { this.insertNode(this.root, newNode) } } //判断节点大小的递归方法 BinarySearchTree.prototype.insertNode = function (node, newNode) { if (newNode.key < node.key) { //如果新节点key小于根节点的key就向左查找 if (node.left == null) { //没有左节点,就放在这里当做左节点 node.left = newNode } else { //如果有左节点,递归调用 this.insertNode(node.left, newNode) } } else { //如果新节点key大于根节点的key就向右查找 if (node.right == null) { //没有右节点,就放在这里当做右节点 node.right = newNode } else { //如果有右节点,递归调用 this.insertNode(node.right, newNode) } } } }
测试:
var tree = new BinarySearchTree() tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25)
二叉树的遍历
二叉树遍历常见的有三种方式:先序遍历、中序遍历、后序遍历、层序遍历(使用较少)
先序遍历
先访问根节点,然后再访问左子节点,最后访问右子节点
//遍历 BinarySearchTree.prototype.preOrderTraversal = function (handler) { this.preOrderTraversalNode(this.root, handler) } BinarySearchTree.prototype.preOrderTraversalNode = function (node, handler) { if(node != null) { //处理经过的节点 handler(node.key) //处理经过节点的左子节点 this.preOrderTraversalNode(node.left,handler) //处理经过节点的右子节点 this.preOrderTraversalNode(node.right,handler) } }
测试:
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) var result = '' tree.preOrderTraversal(function (key) { result += key + ' ' }) alert(result)
中序遍历
先访问左子节点,然后再访问根节点,最后访问右子节点
BinarySearchTree.prototype.midOrderTraversal = function (handler) { this.midOrderTraversalNode(this.root, handler) } BinarySearchTree.prototype.midOrderTraversalNode = function (node,handler) { if(node != null) { //处理经过的节点的左子节点 this.midOrderTraversalNode(node.left,handler) //处理经过的节点 handler(node.key) //处理经过节点的右子节点 this.midOrderTraversalNode(node.right,handler) } }
测试:
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) var result = '' tree.midOrderTraversal(function (key) { result += key + ' ' }) alert(result)
后序遍历
先访问左子节点,然后再访问右子节点,最后访问根节点
BinarySearchTree.prototype.postOrderTraversal = function (handler) { this.postOrderTraversalNode(this.root, handler) } BinarySearchTree.prototype.postOrderTraversalNode = function (node,handler) { if(node != null) { //处理经过的节点的左子节点 this.postOrderTraversalNode(node.left,handler) //处理经过节点的右子节点 this.postOrderTraversalNode(node.right,handler) //处理经过的节点 handler(node.key) } }
测试:
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) var result = '' tree.postOrderTraversal(function (key) { result += key + ' ' }) alert(result)
获取最大值最小值
BinarySearchTree.prototype.max = function () { //获取根节点 var node = this.root //依次向右查找,直到节点为null var key = null while (node != null) { key = node.key node = node.right } return key } BinarySearchTree.prototype.min = function () { //获取根节点 var node = this.root var key = null //依次向左查找,直到节点为null while (node != null) { key = node.key node = node.left } return key }
测试
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) alert(tree.max()) alert(tree.min())
搜索特定的值
BinarySearchTree.prototype.search = function (key) { //获取根节点 var node = this.root //循环搜索key while (node != null) { if (key < node.key) { node = node.left } else if (key > node.key) { node = node.right } else { return true } } return false }
测试:
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) alert(tree.search(25)) alert(tree.search(2))
删除操作
思路:
1、找到要删除的节点,如果没有找到,不需要删除
2、找到要删除的节点
1)删除叶子节点
2)删除只有一个子节点的节点
3)删除有两个子节点的节点
BinarySearchTree.prototype.remove = function (key) { //1、寻找删除的节点 //定义变量,保存一些信息 var current = this.root var parent = null var isLeftChild = true //寻找删除的节点 while (current.key != key) { parent = current if (key < current.key) { isLeftChild = true current = current.left } else { isLeftChild = false current = current.right } //没有找到,已经找到了最后的节点仍然没有找到 if (current == null) return false } //2、根据情况删除节点 //删除的节点是叶子节点 if (current.left == null && current.right == null) { if (current == this.root) { this.root = null } else if (isLeftChild) { parent.left = null } else { parent.right = null } } // 删除的节点有一个子节点 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 } } // 删除的节点有两个子节点 else { //获取后继节点 var successor = this.getSuccessor(current) //判断是否根节点 if(current == this.root) { this.root = successor }else if(isLeftChild) { parent.left = successor } else { parent.right = successor } successor.left = current.left } } //找后继节点的方法 BinarySearchTree.prototype.getSuccessor = function (delNode) { //定义变量 var successor = delNode var current = delNode.right var successorParent = delNode //循环查找 while(current!=null){ successorParent = successor successor = current current = current.left } //判断寻找的后继节点是否直接就是delNode的right节点 if(successor!=delNode.right){ successorParent.left = successor.right successor.right = delNode.right } return successor }
测试:
var tree = new BinarySearchTree(); tree.insert(11) tree.insert(7) tree.insert(15) tree.insert(5) tree.insert(3) tree.insert(9) tree.insert(8) tree.insert(10) tree.insert(13) tree.insert(12) tree.insert(14) tree.insert(20) tree.insert(18) tree.insert(25) tree.remove(9) tree.remove(7) tree.remove(15) var result = '' tree.postOrderTraversal(function (key) { result += key + ' ' }) alert(result)