树结构对比于数组/链表/哈希表有哪些优势
数组:
优点:可以通过下标值访问,效率高;
缺点:在插入和删除元素时,需要大量的位移操作;
链表:
优点:数据的插入和删除操作效率都很高;
缺点:查找效率低,需要从头开始依次查找,直到找到目标数据为止;当需要在链表中间位置插入或删除数据时,插入或删除的效率都不高。
哈希表:
优点:哈希表的插入/查询/删除效率都非常高;
缺点:空间利用率不高,底层使用的数组中很多单元没有被利用;并且哈希表中的元素是无序的,不能按照固定顺序遍历哈希表中的元素;而且不能快速找出哈希表中最大值或最小值这些特殊值。
树结构:
优点:树结构综合了上述三种结构的优点,同时也弥补了它们存在的缺点(虽然效率不一定都比它们高),比如树结构中数据都是有序的,查找效率高;空间利用率高;并且可以快速获取最大值和最小值等。
总的来说:每种数据结构都有自己特定的应用场景
二叉树
二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。二叉树每个节点的子节点不允许超过两个。通过将子节点的个数限定为2,可以写出高效的程序在树中插入、查找和删除数据。
二叉查找树
二叉搜索树(BST,Binary Search Tree),也称为二叉排序树和二叉查找树。
二叉查找树是一种特殊的二叉树,相对较小的值保存在左节点中,较大的值保存在右节点中。这一特性使得查找的效率很高,这也就是二叉搜索树中"搜索"的来源。
二叉搜索树的结构
下图展现了二叉搜索树数据结构的组织方式
和链表一样,将通过指针来表示节点之间的关系(术语称其为边)。
在双向链表中,每个节点包含两个指针,一个指向下一个节点,另一个指向上一个节点。
对于树,使用同样的方式(也使用两个指针)。但是,一个指向左侧子节点,另一个指向右侧子节点。
因此,将声明一个Node类来表示树中的每个节点。值得注意的一个小细节是,不同于在之前的章节中将节点本身称作节点或项,我们将会称其为键。
键是树相关的术语中对节点的称呼。
二叉搜索树的方法
- insert(key):向树中插入一个新的键;
- search(key):在树中查找一个键,如果节点存在,则返回true;如果不存在,则返回false;
- inOrderTraverse:通过中序遍历方式遍历所有节点;
- preOrderTraverse:通过先序遍历方式遍历所有节点;
- postOrderTraverse:通过后序遍历方式遍历所有节点;
- min:返回树中最小的值/键;
- max:返回树中最大的值/键;
- remove(key):从树中移除某个键;
封装二叉搜索树
//封装二叉搜索树
function BinarySearchTree(){
//节点内部类
function Node(key){
this.key = key
this.left = null
this.right = null
}
//属性
this.root = null
}
向树中插入一个键
var insertNode = function(node, newNode){
// 新节点的key小于比较节点
if (newNode.key < node.key){
// 如果左子节点为空,左子节点指向新节点
if (node.left === null){
node.left = newNode;
// 如果左子节点不为空,就将新节点与左子节点进行比较
} else {
// 递归调用比较函数
insertNode(node.left, newNode);
}
// 新节点的key大于比较节点
} else {
// 如果右子节点为空,右子节点指向新节点
if (node.right === null){
node.right = newNode;
// 如果右子节点不为空,就将新节点与右子节点进行比较
} else {
// 递归调用比较函数
insertNode(node.right, newNode);
}
}
};
BinarySearchTree.prototype.insert = function(key){
// 创建一个表示新节点的类
var newNode = new Node(key);
// 判断插入的是否为根节点
if (this.root === null){
this.root = newNode;
} else {
// 正常节点插入
insertNode(this.root,newNode); //{3}
}
};
此时如果想插入一个值为6的键,执行流程及结果如下:
树的遍历
这里说的树的遍历不仅仅针对二叉搜索树,而是适用于所有的二叉树。由于树结构不是线性结构,所以便利方式有多种选择,常见的三种二叉树遍历方式为:先序遍历、中序遍历和后序遍历。其中中序遍历是以升序访问所有节点。
先序遍历
先序遍历的过程为:
首先,遍历根节点;
然后,遍历其左子树;
最后,遍历其右子树;
代码实现
BinarySearchTree.prototype.preOrderTraverse = function(callback){
preOrderTraverseNode(this.root, callback);
};
var preOrderTraverseNode = function (node, callback) {
if (node !== null) {
// 先访问并处理节点本身
// 然后访问左子节点,最后访问右子节点,每一层节点都是这样
callback(node.key);
preOrderTraverseNode(node.left, callback);
preOrderTraverseNode(node.right, callback);
}
};
中序遍历
中序遍历的过程为:
首先,遍历其左子树;
然后,遍历根(父)节点;
最后,遍历其右子树;
中序遍历是一种以上行顺序访问BST所有节点的遍历方式,也就是以从最小到最大的顺序访问所有节点。中序遍历的一种应用就是对树进行排序操作。
代码实现
//中序遍历
BinarySearchTree.prototype.inOrderTraverse = function(callback){
inOrderTraverseNode(this.root, callback)
}
var inOrderTraverseNode = function(node, callback){
if (node !== null) {
//1.遍历左子树中的节点,这里会层层遍历
// 直到找到最小值,对最小值执行callback方法
inOrderTraverseNode(node.left, callback)<