| 常用数据结构
- 数组(Array)
- 栈(Stack)先进后出 (LIFO)
- 队列(Queue)先进先出(FIFO)
- 链表(Linked List)
- 树(Tree)
- 图(Graph)
- 堆(Heap)
- 散列表(Hash 哈希表
| 二叉树
既要保持顺序,又要快速查找、插入和删除,看来有序数组和散列表都不行
那还有什么数据结构可以选择?看看二叉树吧
二叉树也是基于结点的数据结构,与链表的区别在于树的结点可以有多个链指向
二叉树的特性
- 每个结点的子结点数量最多为2,最少为0
- 如有两个子结点,其中一个子节点大于结点,另一子节点小于结点
/**
* 实现一个树结点
* 1. 最多有左右子结点
* 2. 存储值
*/
class TreeNode {
value = null;
leftChildNode = null;
rightChildNode = null;
constructor(value) {
this.value = value;
}
}
插入
有序数组查找需要O(log N)
,插入需要O(N)
,而二叉树都是只要O(logN)
插入规则
- 插入的值大于当前结点值放右边。
- 插入的值小于当前结点值放左边。
- 重复上面的操作
/**
* 插入结点
* @param {*} value
* @param {*} node
* @returns
*/
insert(value, node = null) {
if (!this.firstNode) {
this.firstNode = new TreeNode(value);
}
let newNode = new TreeNode(value);
node = node || this.firstNode;
if (value < node.value) {
// 如果没有左结点
if (!node.leftChildNode) return (node.leftChildNode = newNode);
this.insert(value, node.leftChildNode);
} else if (value > node.value) {
// 如果没有右结点
if (!node.rightChildNode) return (node.rightChildNode = newNode);
this.insert(value, node.rightChildNode);
}
}
查询
平均情况下二叉树的查询效率是O(log N)
最坏情况下,二叉树的查找需要O(N)
查找规则
- 检视该结点的值。
- 如果正是所要找的值,返回值
- 如果要找的值小于当前结点的值,则在该结点的左子树查找。
- 如果要找的值大于当前结点的值,则在该结点的右子树查找。
/**
* 根据值查找结点
* @param {*} value
* @param {*} node
* @returns
*/
search(value, node) {
if (!node || value == node.value) {
return node;
}
if (value < node.value) {
return this.search(value, node.leftChildNode);
}
if (value > node.value) {
return this.search(value, node.rightChildNode);
}
}
删除
平均情况下二叉树的删除效率也是O(log N)
删除规则
- 删除的结点没有子结点,直接删掉
- 删除的结点有一个子结点,删掉它,将子结点填到被删除结点的位置上
- 删除的结点有两个子结点,则将该结点替换成其后继结点(比删除结点大的全部结点中最小的结点)
- 后继结点带有右子结点,则在后继结点填补被删除结点以后,用此右子结点替代后继结点的父节点的左子结点。
/**
* 根据值删除结点
* @param {*} value
* @param {*} node
* @returns
*/
delete(value, node) {
if (!node) return node;
// 去左结点找
if (value < node.value) {
node.leftChildNode = this.delete(value, node.leftChildNode);
return node;
}
// 去右结点找
if (value > node.value) {
node.rightChildNode = this.delete(value, node.rightChildNode);
return node;
}
// 找到了
if (value === node.value) {
// 如果只有左子节点,则让当前左子节点接替位置
if (!node.rightChildNode) {
return node.leftChildNode;
}
// 如果只有右子节点,则让当前右子节点接替位置
else if (!node.leftChildNode) {
return node.rightChildNode;
} else {
// 如果左右都有,当前结点值则为后继结点的值
node.right = this.lift(node.rightChildNode, node);
return node;
}
}
}
// 获取后继结点的值(比删除结点大的全部结点中最小的结点)
lift(node, nodeToDelete) {
if (node.leftChildNode) {
node.leftChildeNode = lift(node.leftChildNode, nodeToDelete);
return node;
} else {
node.rightChildNode = lift(node.rightChildNode, nodeToDelete);
}
}
| 最后
假若你要用有序数组里的数据来创建二叉树,最好先把数据洗乱。
因为只有用随意打乱的数据创建出来的树才有可能是比较平衡的。
要是插入的都是已排序的数据,那么这棵树就失衡了,它用起来也会比较低效。
如图
从中查找5,效率会是O(N)
整体代码
/**
* 实现一个树结点
* 1. 最多有左右子结点
* 2. 存储值
*/
class TreeNode {
value = null;
leftChildNode = null;
rightChildNode = null;
constructor(value) {
this.value = value;
}
}
class Tree {
firstNode = null;
constructor(value) {
value && (this.firstNode = new TreeNode(value));
}
/**
* 插入结点
* @param {*} value
* @param {*} node
* @returns
*/
insert(value, node = null) {
if (!this.firstNode) {
this.firstNode = new TreeNode(value);
}
let newNode = new TreeNode(value);
node = node || this.firstNode;
if (value < node.value) {
// 如果没有左结点
if (!node.leftChildNode) return (node.leftChildNode = newNode);
this.insert(value, node.leftChildNode);
} else if (value > node.value) {
// 如果没有右结点
if (!node.rightChildNode) return (node.rightChildNode = newNode);
this.insert(value, node.rightChildNode);
}
}
/**
* 根据值查找结点
* @param {*} value
* @param {*} node
* @returns
*/
search(value, node) {
if (!node || value == node.value) {
return node;
}
if (value < node.value) {
return this.search(value, node.leftChildNode);
}
if (value > node.value) {
return this.search(value, node.rightChildNode);
}
}
/**
* 根据值删除结点
* @param {*} value
* @param {*} node
* @returns
*/
delete(value, node) {
if (!node) return node;
// 去左结点找
if (value < node.value) {
node.leftChildNode = this.delete(value, node.leftChildNode);
return node;
}
// 去右结点找
if (value > node.value) {
node.rightChildNode = this.delete(value, node.rightChildNode);
return node;
}
// 找到了
if (value === node.value) {
// 如果只有左子节点,则让当前左子节点接替位置
if (!node.rightChildNode) {
return node.leftChildNode;
}
// 如果只有右子节点,则让当前右子节点接替位置
else if (!node.leftChildNode) {
return node.rightChildNode;
} else {
// 如果左右都有,当前结点值则为后继结点的值
node.right = this.lift(node.rightChildNode, node);
return node;
}
}
}
// 获取后继结点的值(比删除结点大的全部结点中最小的结点)
lift(node, nodeToDelete) {
if (node.leftChildNode) {
node.leftChildeNode = lift(node.leftChildNode, nodeToDelete);
return node;
} else {
node.rightChildNode = lift(node.rightChildNode, nodeToDelete);
}
}
}
let binaryTree = new Tree();
let treeNodeValue = [54, 25, 36, 47, 36, 88, 11, 86, 60];
treeNodeValue.forEach((item) => binaryTree.insert(item));
console.log(binaryTree.search(88, binaryTree.firstNode));
console.log(binaryTree.delete(88, binaryTree.firstNode));
console.log(binaryTree);