We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
有的笔者也称它为二叉搜索树,都是一个意思。
二叉树本身没有多大的意义,直到有位大佬提出一个 trick。
如果我们规定一颗二叉树上的元素拥有顺序,所有比它小的元素在它的左子树,比它大的元素在它的右子树,那么我们不就可以很快地查找某个元素了吗?
不得不说这是一个非常天才的想法,于是,二叉查找树诞生了。
所以,二叉查找树与二叉树不同的是,它在二叉树的基础上,增加了对二叉树上节点存储位置的限制:二叉搜索树上的每个节点都需要满足:
在二叉树中,所有子节点值都是没有固定规律的,所以使用二叉树存储结构存储数据时,查找数据的时间复杂度为 O(n),因为它要查找每一个节点。
而使用二叉查找树就不同了,例如上图,我们如果要查找 6 ,先从根节点 10 比较,6 比 10 小,则查找左子树,再与 8 比较,6 比 8 小,继续查找 8 的左子树,也就是 6,我们找到了元素,结束。
function BinarySearchTree() { let Node = function (key) { this.key = key this.left = null this.right = null } let root = null // 插入 this.insert = function(key){} // 查找 this.search = function(key){} // 删除 this.remove = function(key){} // 最大值 this.max = function(){} // 最小值 this.min = function(){} // 中序遍历 this.inOrderTraverse = function(){} // 先序遍历 this.preOrderTraverse = function(){} // 后序遍历 this.postOrderTraverse = function(){} }
function insert(key) { // 创建新节点 let newNode = new Node(key) // 判断是否为空树 if(root === null) { root = newNode } else { insertNode(root, newNode) } } // 将 insertNode 插入到 node 子树上 function insertNode(node, newNode) { if(newNode.key < node.key) { // 插入 node 左子树 if(node.left === null) { node.left = newNode } else { insertNode(node.left, newNode) } } else { // 插入 node 右子树 if(node.right === null) { node.right = newNode } else { insertNode(node.right, newNode) } } }
最小值:树最左端的节点
最大值:树最右端的节点
// 最小值 function min() { let node = root if(node) { while(node && node.left !== null) { node = node.left } return node.key } return null } // 最大值 function max() { let node = root if(node) { while(node && node.right !== null) { node = node.right } return node.key } return null }
function search(key) { return searchNode(root, key) } function searchNode(node, key) { if(node === null) return false if(key < node.key) { return searchNode(node.left, key) } else if(key > node.key) { return searchNode(node.right, key) } else { return true } }
function remove(key) { root = removeNode(root, key) } function removeNode(node, key) { if(node === null) return null if(key < node.key) { return removeNode(node.left, key) } else if(key > node.key) { return removeNode(node.right, key) } else { // key = node.key 删除 //叶子节点 if(node.left === null && node.right === null) { node = null return node } // 只有一个子节点 if(node.left === null) { node = node.right return node } if(node.right === null) { node = node.left return node } // 有两个子节点 // 获取右子树的最小值替换当前节点 let minRight = findMinNode(node.right) node.key = minRight.key node.right = removeNode(node.right, minRight.key) return node } } // 获取 node 树最小节点 function findMinNode(node) { if(node) { while(node && node.right !== null) { node = node.right } return node } return null }
顾名思义,中序遍历就是把根放在中间的遍历,即按先左节点、然后根节点、最后右节点(左根右)的遍历方式。
由于BST树任意节点都大于左子节点值小于等于右子节点值的特性,所以 中序遍历其实是对🌲进行排序操作 ,并且是按从小到大的顺序排序。
function inOrderTraverse(callback) { inOrderTraverseNode(root, callback) } function inOrderTraverseNode(node, callback) { if(node !== null) { // 先遍历左子树 inOrderTraverseNode(node.left, callback) // 然后根节点 callback(node.key) // 再遍历右子树 inOrderTraverseNode(node.right, callback) } } // callback 每次将遍历到的结果打印到控制台 function callback(key) { console.log(key) }
已经实现的中序遍历,先序遍历就很简单了,它是按根左右的顺序遍历
function preOrderTraverse() { preOrderTraverseNode(root, callback) } function preOrderTraverseNode(node, callback) { if(node !== null) { // 先根节点 callback(node.key) // 然后遍历左子树 preOrderTraverseNode(node.left, callback) // 再遍历右子树 preOrderTraverseNode(node.right, callback) } } // callback 每次将遍历到的结果打印到控制台 function callback(key) { console.log(key) }
后序遍历按照左右根的顺序遍历,实现也很简单。
function postOrderTraverse() { postOrderTraverseNode(root, callback) } function postOrderTraverseNode(node, callback) { if(node !== null) { // 先遍历左子树 postOrderTraverseNode(node.left, callback) // 然后遍历右子树 postOrderTraverseNode(node.right, callback) // 最后根节点 callback(node.key) } } // callback 每次将遍历到的结果打印到控制台 function callback(key) { console.log(key) }
在理想情况下,二叉树每多一层,可以存储的元素都增加一倍。也就是说 n 个元素的二叉搜索树,对应的树高为 O(logn)。所以我们查找元素、插入元素的时间也为 O(logn)。
当然这是理想情况下,但在实际应用中,并不是那么理想,例如一直递增或递减的给一个二叉查找树插入数据,那么所有插入的元素就会一直出现在一个树的左节点上,数型结构就会退化为链表结构,时间复杂度就会趋于 O(n),这是不好的。
而我们上面的平衡树就可以很好的解决这个问题,所以平衡二叉查找树(AVL树)由此诞生。
The text was updated successfully, but these errors were encountered:
findMinNode写错了吧,应该要查左子节点
// 获取 node 树最小节点 function findMinNode(node) { if(node) { while(node && node.left !== null) { node = node.left } return node } return null }
Sorry, something went wrong.
No branches or pull requests
一、二叉查找树(BST树)
有的笔者也称它为二叉搜索树,都是一个意思。
二叉树本身没有多大的意义,直到有位大佬提出一个 trick。
如果我们规定一颗二叉树上的元素拥有顺序,所有比它小的元素在它的左子树,比它大的元素在它的右子树,那么我们不就可以很快地查找某个元素了吗?
不得不说这是一个非常天才的想法,于是,二叉查找树诞生了。
所以,二叉查找树与二叉树不同的是,它在二叉树的基础上,增加了对二叉树上节点存储位置的限制:二叉搜索树上的每个节点都需要满足:
在二叉树中,所有子节点值都是没有固定规律的,所以使用二叉树存储结构存储数据时,查找数据的时间复杂度为 O(n),因为它要查找每一个节点。
而使用二叉查找树就不同了,例如上图,我们如果要查找 6 ,先从根节点 10 比较,6 比 10 小,则查找左子树,再与 8 比较,6 比 8 小,继续查找 8 的左子树,也就是 6,我们找到了元素,结束。
二、代码实现
插入:
最值:
最小值:树最左端的节点
最大值:树最右端的节点
查找:
删除:
中序遍历:
顾名思义,中序遍历就是把根放在中间的遍历,即按先左节点、然后根节点、最后右节点(左根右)的遍历方式。
由于BST树任意节点都大于左子节点值小于等于右子节点值的特性,所以 中序遍历其实是对🌲进行排序操作 ,并且是按从小到大的顺序排序。
先序遍历:
已经实现的中序遍历,先序遍历就很简单了,它是按根左右的顺序遍历
后序遍历:
后序遍历按照左右根的顺序遍历,实现也很简单。
三、BST树的局限
在理想情况下,二叉树每多一层,可以存储的元素都增加一倍。也就是说 n 个元素的二叉搜索树,对应的树高为 O(logn)。所以我们查找元素、插入元素的时间也为 O(logn)。
当然这是理想情况下,但在实际应用中,并不是那么理想,例如一直递增或递减的给一个二叉查找树插入数据,那么所有插入的元素就会一直出现在一个树的左节点上,数型结构就会退化为链表结构,时间复杂度就会趋于 O(n),这是不好的。
而我们上面的平衡树就可以很好的解决这个问题,所以平衡二叉查找树(AVL树)由此诞生。
The text was updated successfully, but these errors were encountered: