文章目录
- 树
- 与其他数据结构进行对比
- 常见表示方法
- 二叉树
- 二叉搜索树(BST)
- leetcode例题
- [144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/)
- [94. 二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/)
- [145. 二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/)
- [102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/)
- [104. 二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/)
- [101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/)
- [700. 二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/)
- [701. 二叉搜索树中的插入操作](https://leetcode.cn/problems/insert-into-a-binary-search-tree/)
- [112. 路径总和](https://leetcode.cn/problems/path-sum/)
- [226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/)
- [剑指 Offer 32 - III. 从上到下打印二叉树 III](https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/)
- [98. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/)
- [653. 两数之和 IV - 输入 BST](https://leetcode.cn/problems/two-sum-iv-input-is-a-bst/)
- [235. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/)
树
与其他数据结构进行对比
-
数组
- 优点:下标值访问效率高;
- 缺点:查询、插入、删除效率低;
- 查询一般使用先排序、后二分;
- 插入和删除在数组前面的位置时,会产生大量位移操作,效率低
-
链表
- 优点:插入和删除效率高;
- 缺点:查询效率低,需要从头开始访问
-
哈希表
- 优点:插入、查询、删除效率高
- 缺点:空间利用率不高;哈希表中的元素是无序的
-
树:树结构综合了上面的部分优点,每种树结构都有自己独特的应用场景。
常见表示方法
普通表示法
儿子-兄弟表示法
只记录左儿子和右兄弟
儿子-兄弟表示法旋转形成二叉树
将左儿子和右兄弟作为父节点的二叉,所以二叉树能够模拟所有的树
二叉树
满二叉树
除了最下一层叶子节点外,每层节点都有两个子节点
完全二叉树
除了最下一层叶子节点外,每层节点都有两个子节点
最下一层叶子节点从左向右连续存在,只缺右侧若干节点
满二叉树是特殊的完全二叉树
二叉树的表示
用数组表示非完全二叉树时,空着的节点也要占位,会造成空间浪费,所以一般我们采用链表的方式
二叉搜索树(BST)
满足条件:
- 非空左子树的所有键值小于其根节点的键值
- 非空右子树的所有键值大于其根节点的键值
- 左、右子树本身也是二叉搜索树
当我们进行搜索的时候,如果节点大于搜索值,就向左边继续查找;节点小于搜索值,就向右边继续查找
二叉搜索树的封装
基本封装以及插入操作
// 封装二叉搜索树
// 此二叉搜索树只包含key,如果需要value的话在Node构造函数中添加value即可
function BinarySearchTree() {
// 每一个节点包含key、left、right
function Node(key){
this.key = key
this.left = null
this.right = null
}
// 属性
this.root = null
// 方法
// 实现插入
BinarySearchTree.prototype.insert = function(key){
// 1.根据key创建节点
var newNode = new Node(key)
// 2.判断根节点是否有值
if(this.root == null){
this.root = newNode
} else{
// 如果节点有值,就调用insertNode比较
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)
}
}
}
}
遍历树
-
深度优先
- 先序遍历:根–>左子树–>右子树
// 2.实现遍历 // (1)先序遍历 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) } } // handler是一个回调函数,参数为每一次遍历的key
- 中序遍历:左子树–>根–>右子树
// 调换一下位置即可 BinarySearchTree.prototype.midOrderTraversalNode = function(node,handler){ if(node != null){ this.preOrderTraversalNode(node.left,handler) handler(node.key) this.preOrderTraversalNode(node.right,handler) } }
- 后序遍历:左子树–>右子树–>根
// 调换一下位置即可 BinarySearchTree.prototype.postOrderTraversalNode = function(node,handler){ if(node != null){ this.preOrderTraversalNode(node.left,handler) this.preOrderTraversalNode(node.right,handler) handler(node.key) } }
- 先序遍历:根–>左子树–>右子树
-
广度优先(使用队列完成)
基本思路就是:谁出队列,就把它的左右子节点入队列
// (2) 层序遍历(广度遍历)
BinarySearchTree.prototype.levelOrder = function(handler){
this.levelOrderNode(this.root,handler)
}
// 层序遍历辅助方法
BinarySearchTree.prototype.levelOrderNode = function(node,handler){
let queue = []
if(node != null){
queue.push(node)
while(queue.length>0){
let nowNode = queue.shift()
handler(nowNode.key)
if(nowNode.left) queue.push(nowNode.left)
if(nowNode.right) queue.push(nowNode.right)
}
}
}
搜索特定值
//3. 实现搜索
BinarySearchTree.prototype.search = function(key){
// 获取根节点
var node = this.root
// 循环搜索
while(node != null){
if(key<node.key){
node = node.left
}else if(key>node.key){
node = node.right
}else{
return true
}
}
return false
}
leetcode例题
144. 二叉树的前序遍历
var preorderTraversal = function(root) {
let res = []
preorderTraversalNode(root,res)
return res
};
var preorderTraversalNode = function (root,res){
if(root != null){
res.push(root.val)
preorderTraversalNode(root.left,res)
preorderTraversalNode(root.right,res)
}
}
94. 二叉树的中序遍历
var inorderTraversal = function(root) {
let res = []
inorderTraversalNode(root,res)
return res
};
var inorderTraversalNode = function (root,res){
if(root != null){
inorderTraversalNode(root.left,res)
res.push(root.val)
inorderTraversalNode(root.right,res)
}
}
145. 二叉树的后序遍历
var postorderTraversal = function(root) {
let res = []
postorderTraversalNode(root,res)
return res
};
var postorderTraversalNode = function (root,res){
if(root != null){
postorderTraversalNode(root.left,res)
postorderTraversalNode(root.right,res)
res.push(root.val)
}
}
102. 二叉树的层序遍历
var levelOrder = function(root) {
//二叉树的层序遍历
let res=[],queue=[];
queue.push(root);
if(root===null){
return res;
}
while(queue.length!==0){
// 记录当前层级节点数
let length=queue.length;
//存放每一层的节点
let curLevel=[];
for(let i=0;i<length;i++){
let node=queue.shift();
curLevel.push(node.val);
// 存放当前层下一层的节点
if(node.left) queue.push(node.left);
if(node.right) queue.push(node.right);
}
//把每一层的结果放到结果数组
res.push(curLevel);
}
return res;
};
104. 二叉树的最大深度
var maxDepth = function(root) {
if(!root){
return 0
}else{
// 递归计算每层的左子节点和右子节点谁最深
const left = maxDepth(root.left)
const right = maxDepth(root.right)
return Math.max(left,right) + 1
}
};
101. 对称二叉树
const isSymmetric = (root) => {
const check = (left, right) => {
if (left == null && right == null) { // 左右为null,对称
return true;
}
if (left && right) { // 左右相等,一个的左子节点等于另一个的右子节点,之后同理
return left.val == right.val && check(left.left, right.right) && check(left.right, right.left);
}
return false; // 一个子树存在一个不存在,不对称
};
return check(root.left, root.right); // 继续递归判断
};
700. 二叉搜索树中的搜索
// 要善于使用三元表达式简化if语句
var searchBST = function(root, val) {
while(root){
if(val == root.val){
return root
}
root = val < root.val ? root.left : root.right
}
return null
};
701. 二叉搜索树中的插入操作
var insertIntoBST = function(root, val) {
// 遇到节点为空的时候,即为可插入位置,创建一个新的节点
if(root == null){
return new TreeNode(val)
}
if(root.val < val){
root.right = insertIntoBST(root.right,val)
}else{
root.left = insertIntoBST(root.left,val)
}
return root
};
112. 路径总和
使用递归,将这个问题划分成多个小问题
即,targetSum减去当前节点的值就是要求的剩余节点的值
我们递归接下来的左子树和右子树
边界为,当前节点的下一个节点为空
// 递归
var hasPathSum = function(root, targetSum) {
// 遍历到空节点,没有满足返回false
if(root == null){
return false
}
// 遍历到叶子节点
if(root.left == null && root.right == null){
return targetSum-root.val == 0
}
// 其他情况下,递归
return hasPathSum(root.left, targetSum-root.val) || hasPathSum(root.right,targetSum-root.val)
};
226. 翻转二叉树
递归保存当前节点的左右节点,当前节点的左节点等于保存的右节点;同理当前节点的右边节点等于保存的左节点
var invertTree = function(root) {
if(root == null){
return root
}else{
const left = invertTree(root.left)
const right = invertTree(root.right)
root.left = right
root.right = left
}
return root
};
剑指 Offer 32 - III. 从上到下打印二叉树 III
本题主要还是遍历二叉树,只是在中间多了一步判断是否需要翻转
var levelOrder = function(root) {
let res = [] ,queue = []
if(root == null){
return res
}
queue.push(root)
let flag = false
while(queue.length != 0){
let cur = []
let length = queue.length
for(let i=0;i<length;i++){
let node = queue.shift()
cur.push(node.val)
if(node.left) queue.push(node.left)
if(node.right) queue.push(node.right)
}
flag = !flag
if(flag){
res.push(cur)
}else{
res.push(cur.reverse())
}
}
return res
};
98. 验证二叉搜索树
// 递归
// max是为了帮助node.left判断
// min是为了帮助node.right判断
let fun = function(node,min,max){
if(node == null){
return true
}
if(node.val <= min || node.val>=max){
return false
}
return fun(node.left,min,node.val) && fun(node.right,node.val,max)
}
var isValidBST = function(root) {
return fun(root,-Infinity,Infinity)
};
653. 两数之和 IV - 输入 BST
// 一直递归向map中set
// 直到找到返回true
// 或者递归完所有都没找到返回false
var findTarget = function(root, k) {
let map = new Map()
let fun = function(root,k){
if(!root){
return false
}
if(map.has(k-root.val)){
return true
}
map.set(root.val)
return fun(root.left,k) || fun(root.right,k)
}
return fun(root,k)
};
235. 二叉搜索树的最近公共祖先
// 如果p,q都小于根节节点,则继续递归根节点的左边进行比较
// 如果p,q都大于根节节点,则继续递归根节点的右边进行比较
// 最终返回该节点
var lowestCommonAncestor = function(root, p, q) {
if(p.val < root.val && q.val < root.val){
return lowestCommonAncestor(root.left,p,q)
}
if(p.val > root.val && q.val> root.val){
return lowestCommonAncestor(root.right,p,q)
}
return root
};