二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”。一棵深度为k,且有(2^k)-1个结点的二叉树,称为满二叉树。最少结点为k,为斜二叉树。
了解了什么是二叉树后,自然要懂得它的三种遍历方法。前序(先序):中 -> 左 -> 右、中序:左 -> 中 -> 右、后序:左 -> 右 -> 中。(中是父节点,左是左节点,右是右节点)
递归版的三种遍历方式
先序
public static void PreOrder(TreeNode root) {
if(root == null) return; //判断根结点是不是为空
System.out.println(root.val); //直接输出根结点
PreOrder(root.left); //循环遍历左节点
PreOrder(root.right); //循环遍历右节点
}
中序
public static void InOrder(TreeNode root) {
if(root == null) return; //判断根结点是不是为空
InOrder(root.left); //循环遍历左节点
System.out.println(root.val); //直接输出根结点
InOrder(root.right); //循环遍历右节点
}
后序
public static void PostOrder(TreeNode root) {
if(root == null) return; //判断根结点是不是为空
PostOrder(root.left); //循环遍历左节点
PostOrder(root.right); //循环遍历右节点
System.out.println(root.val); //直接输出根结点
}
非递归版的三种遍历方式
先序
public static void PreOrder(TreeNode root) {
Stack <TreeNode> stack = new Stack <TreeNode>();
if(root==null) return;
while(!stack.empty() || root!=null){
while(root!=null){
stack.push(root);
System.out.println(root.val);
root = root.left; //指向左节点
}
root=root.pop(); //取出栈顶元素
root =root.right; //遍历右孩子
}
}
中序
public static void InOrder(TreeNode root) {
Stack <TreeNode> stack = new Stack <TreeNode>();
if(root==null) return;
while(!stack.empty() || root!=null){
while(root!=null){
stack.push(root);
root = root.left; //指向左节点
}
System.out.println(root.val);
root=root.pop(); //取出栈顶元素
root =root.right; //遍历右孩子
}
}
后序
public static void PostOrder(TreeNode root) {
Stack <TreeNode> stack = new Stack <TreeNode>();
if(root==null) return;
while(!stack.empty() || root!=null){
while(root!=null){
stack.push(root);
root = root.left; //指向左节点
}
root = stack.pop();
if (root.right == null || root.right == prev) { //prev标记右节点
System.out.println(root.val);
prev = root;
root = null;//这一步很重要,输出节点之后,栈顶元素变为了根节点,但是不能直接输出
} else {
stack.push(root);
root = root.right;
}
}
}
对于非递归版的遍历方法,需要大家新建棵树去每一步的认真走一下流程,这样才能真正的理解,不至于死记硬背。我写的非递归版的都是利用栈这种数据结构,它和js中数组一样,这样的话大家理解起来也比较容易。
深度优先遍历
深度优先遍历,也是利用栈来保存数据进行操作的。
//二叉树深度优先
* 1
* / \
* 2 3
* / \ /\
* 4 5 6 7
* 结果是:1,2,4,5,3,6,7
public static void getDFS(TreeNode root){
if(root == null) { return; }
Stack <TreeNode> stack = new Stack <TreeNode>();
stack.push(root);
while(!stack.empty()){
TreeNode node = stack.pop();
System.out.println(node.val);
if(node.right !=null) stack.push(node.right);
if(node.left !=null) stack.push(node.left);
}
}
广度优先遍历
上面的遍历都是运用栈来保存节点,而广度优先是利用队列来保存节点。二者的差别可自行百度
广度优先是值先打印根节点,然后依次打印左节点和右节点(是指同一层次中的所有节点)
//二叉树广度优先
* 1
* / \
* 2 3
* / \ /\
* 4 5 6 7
* 结果是:1,2,3,4,5,6,7
//代码实现
public static void getBFS(TreeNode root) {
if(root == null) { return; }
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
while(!queue.isEmpty() || root!=null){
TreeNode node = queue.poll();
System.out.println(node.val);
if(node.left!=null){ //队列是先进先出,每次出列的时候都把自己的左、右节点添加到队列中
queue.offer(node.left);
}
if(node.right!=null){
queue.offer(node.right);
}
}
}
二叉搜索树的第k个结点
二叉搜索树的特点是根节点大于左孩子,小于右孩子。
* 4
* / \
* 2 6
* / \ / \
* 1 3 5 7
* 结果是:1,2,3,4,5,6,7
*
* 主要是利用中序遍历,在弹出栈的时候记录count是否和 k 相等。
*
public static void KthNode(TreeNode root,int k){
Stack<TreeNode> stack = new Stack<TreeNode>();
int count = 0;
while(!stack.empty()||root!=null){
while(root!=null){
stack.push(root);
root = root.left;
}
count++; //计数
if(count==k) return root; //进行比较
root = stack.pop();
root = root.right;
}
}
二叉树中第k层结点个数
使用递归来实现,首先需要了解的问题是:
- 根结点是第0层,所以当 k<0 时返回 0
- k=0 时,只有一个根结点,应该返回 1
- k>0 时,递归求 k-1 层左右结点个数相加
public int K_nodes(int k){
if(k<0) return 0;
return kNodes(root,k);
}
private int kNodes(TreeNode root,int k){
if(root==null) return 0;
if(k==0) return 1; //只有根结点
return kNodes(root.left,k-1)+kNodes(root.right,k-1); //递归计数
}
打印二叉树中和等于k的路径
满足的和为k的路径是:最后一个结点要为叶子结点并且和等于k。
在这我们会利用栈来保存满足的路径,最后遍历栈打印输出路径。
* 4
* / \
* 2 5
* / \
* 1 3
* 若k=9 满足的两条路径是:4, 2, 3 和 4, 5 这两条路径
*
public class sumK(TreeNode root,int k){
Stack<TreeNode> stack = new Stack<TreeNode>();
findPath(stack,root,k);
void findPath(Stack<TreeNode> stack,TreeNode root,int k){
if(root==null) return;
stack.push(root);
k-=root.val; // k 减去当前结点值
if(k==0 && root.left==null && root.right==null){ //判断是否满足条件
for(TreeNode node:stack){ //把满足的路径打印出
System.out.print(node.val);
}
System.out.println();
}
findPath(srack,root.left,k); //遍历左节点
findPath(stack,root.right,k); //遍历右节点
stack.pop(); //如果当前遍历节点为叶子节点,并且k!=0 把当前节点从栈顶弹出
}
}
二叉树中全部的路径
//这题是LeetCode上的题目,可以去看看,自己做做
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
Stack<String> stack = new Stack<String>();
if(root==null) return stack;
String str="";
AllPaths(stack,root,str);
return stack;
}
void AllPaths(Stack<String> stack,TreeNode root,String str){
if(root==null){return;}
str+=root.val;
if(root.left==null && root.right==null){
stack.push(str);
return;
}
if(root.left!=null) AllPaths(stack,root.left,str+"->");
if(root.right!=null) AllPaths(stack,root.right,str+"->");
}
}
// ["1->2->3","1->5"]
js版本(打印出和为sum的全部路径)
function FindPath(root, sum){
if (root === null) return [];
let res = [];
const findSum = (root, stack, k, res) => {
if (root === null) return;
stack.push(root.val);
k -= root.val;
if (k === 0 && root.left === null && root.right === null) {
res.push(stack.slice());
}
findSum(root.left, stack, k, res);
findSum(root.right, stack, k, res);
stack.pop()
}
findSum(root, [], sum, res);
return res;
}
js版本(判断是否有和为sum的路径...和上一题有区别)
function hasPathSum( root , sum ) {
if (root === null) return false;
const findPath = (root, k) => {
if (root === null) return false;
k = k - root.val;
if (k === 0 && root.left === null && root.right === null) {
return true;
}
return findPath(root.left, k) || findPath(root.right, k); //重点
};
return findPath(root, sum);
}
额外的js简单编程题:
实现解析浏览器中url参数
/*最后 结果
{ user: ‘anonymous’,
id: [ 123, 456 ], // 重复出现的 key 要组装成数组
city: ‘北京’,
enabled: true, // 未指定值得 key 约定为 true
}
*/
url = ‘http://www.domain.com/?user=anonymous&id=123&id=456&city=“beijing”&enabled’;
function parseParam(url) {
var obj={};
var data=url.split('?')[1]; //若没有指定url,可以使用window.location.search
var arr=data.split('&'); //分割后,保存到数组中
for(let i=0;i<arr.length;i++){
let key = arr[i].split('=')[0];
let val = arr[i].split('=')[1];
if(obj[key]){ //存在的情况下
let res=[]; //定义一个数组,因为如果之前存在的话要保存为数组形式
res=res.concat(obj[key]); //把之前的值连接到数组中
res.push(val); //新解析的值添加进去 ["123","456"]
obj[key]=res; //别忘了添加到obj中
}else if(!val){
obj[key]=true; //如果没有值的,默认为true
}else{
obj[key]=val; //剩余情况
}
}
return obj;
}
斐波那契数列我想大家肯定都了解,之前写的时候可能会直接用递归和数组保存,这两种呢会消耗大量的内存空间,因为它保存了重复计算的值。
上次面试的时候被问到如何进行优化数列,下面就来讲讲优化的方法。
function fibo(n){
if(n<=0) return 0;
var a=1,b=1; //利用变量来保存结果
if(n===1||n===2){
return 1;
}else{
for(let i=3;i<=n;i++){
let c=a;
a=b; //每次计算后都把上次的值覆盖掉,大大节约了空间
b=c+b; //结果值
}
return b;
}
}
以上是本篇博客为大家分享的知识点,每天进步一点点。希望和大家一起进步?
也欢迎大家指出有误的地方,或者留下更好的解决方法?