Skip to content
New issue

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

1. 什么是二叉树 #1

Open
webVueBlog opened this issue May 23, 2022 · 3 comments
Open

1. 什么是二叉树 #1

webVueBlog opened this issue May 23, 2022 · 3 comments

Comments

@webVueBlog
Copy link
Member

webVueBlog commented May 23, 2022

什么是二叉树

树是用来模拟具有树状结构性质的数据集合。根据它的特性可以分为非常多的种类,对于我们来讲,掌握二叉树这种结构就足够了,它也是树最简单、应用最广泛的种类。

二叉树是一种典型的树树状结构。如它名字所描述的那样,二叉树是每个节点最多有两个子树的树结构,通常子树被称作“左子树”和“右子树”。

  1. 二叉树的基本操作(解答)

  2. 二叉树的前序遍历(解答)

  3. 二叉树的中序遍历(解答)

  4. 二叉树的后序遍历(解答)

请写出递归和非递归版本的,前中后序遍历。

题目

重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

求二叉树的遍历

给定一棵二叉树的前序遍历和中序遍历,求其后序遍历

输入描述:

两个字符串,其长度n均小于等于26。 第一行为前序遍历,第二行为中序遍历。 二叉树中的结点名称以大写字母表示:A,B,C....最多26个结点。

输出描述:

输入样例可能有多组,对于每组测试样例, 输出一行,为后序遍历的字符串。

样例:

输入
ABC
BAC
FDXEAG
XDEFAG

输出
BCA
XEDGAF
@webVueBlog
Copy link
Member Author

webVueBlog commented May 23, 2022

为什么学习二叉树

二叉树的知识更倾向于理论,我们在实际应用开发过程中直接使用得并不多,但是二叉树作为数据结构的一个重要的组成部分,底层很多东西都是基于二叉树实现的,比如TreeMap,TreeSet,Linux的虚拟内存管理,数据库系统等,所以掌握二叉树的基本操作是相当必要的。

二叉树的概念和特点

我们学习的数据结构中的树,不是现实生活中的树。它和现实生活中的树有着很多的相似之处,或者可以说数据结构的树是对现实生活中的树的抽象。

image

数据结构中的树:

image

树的定义:

树是由n(n>=1)个有限节点组成的一个具有层次关系的集合。

树的特点:

  1. 每个节点有零个或多个子节点。
  2. 没有父节点的节点称为根节点。
  3. 每一个非根节点有且只有一个父节点。
  4. 除了根节点外,每个子节点可以分多个不相交的子树。

二叉树的定义和创建

二叉树是一种特殊的树,在树的基础上添加约束条件:

  1. 每个节点下最多只有两棵子树。
  2. 左子树和右子树是有顺序的,次序不能颠倒。
  3. 即使某节点只有一个子树,也要区分左右子树。

二叉树就是一个集合,集合就是用来存储对象的容器,二叉树上存储的每一个元素,都是一个一个的对象。节点对象。

二叉树的实现分析

一个二叉树节点有三个部分,一个是指向左子树的部分,一个是指向右子树的部分,另外一个是数据部分。可以把整个节点抽象成一个节点对象。节点对象有两个节点对象属性和一个数据数据。

一个二叉树有且只有一个根节点,其余的都是根节点的直接或间接子节点。所以可以把二叉树抽象成一个对象。该对象有一个节点类型的数据,用来保存根节点。

image

二叉排序树的本质就是一颗二叉树,它具有如下特点:

  1. 所有左节点的元素值小于其父节点的元素值
  2. 所有右节点的元素值大于其父节点的元素值

image

image

树的分类

image

144. 二叉树的前序遍历

给你二叉树的根节点root,返回它节点值的前序遍历。

image

root = [1, null, 2, 3]
[1,2,3]

root = []
[]

root = [1]
[1]
var preorderTraversal = function(root) {
  if (!root) return [];
  var result = [];
  var stack = [root];

  while(stack.length) {
    var node = stack.pop();
    result.push(node.val);
    if (node.right) stack.push(node.right);
    if (node.left) stack.push(node.left);
  }
  return result;
};

94. 二叉树的中序遍历

image

var inorderTraversal = function(root) {
 const stack = [];
 const res = [];
 while(root || stack.length) {
  if(root) {
   stack.push(root);
   root = root.left;
  } else {
   root = stack.pop();
   res.push(root.val);
   root = root.right;
  }
 } 
 return res;
};

145. 二叉树的后序遍历

 var postorderTraversal = function(root) {
  if(!root) return [];

  const stack = [root];
  const result = [];
  while(stack.length > 0) {
      const node = stack.pop();
      result.push(node.val);
      if(node.left) stack.push(node.left);
      if(node.right) stack.push(node.right);
  }
  
  return result.reverse();
};

完全二叉树

function Node(data, left, right) {
 this.data = data;
 this.left = left;
 this.right = right;
}

树的查找

function getNode(data, node) {
 if(node) {
  if(data === node.data) {
   return node;
  } else if (data < node.data) {
   return getNode(data, node.left);
  } else {
   return getNode(data, node.right);
  }
 } else {
  return null;
 }
}

二分查找

二分查找的条件是必须是有序的线性表。

binarySearch(data, arr, start, end) {
 if(start>end) {
  return -1;
 }
 var mid = Math.floor((end + start) / 2);
 if(data == arr[mid]) {
  return mid;
 } else if ( data < arr[mid]) {
  return binarySearch(data, arr, start, mid - 1);
 } else {
  return binarySearch(data, arr, mid + 1, end);
 }
}

什么是中序遍历

中序遍历是二叉树遍历的一种,也叫做中根遍历、中序周游。 在二叉树中,中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。

左 中 右

输入: [1,null,2,3]
   1
    \
     2
    /
   3
输出: [1,3,2]
var inorderTraversal = function(root, array = []) }
 if(root) {
  inorderTraversal (root.left, array);
  array.push(root.val);
  inorderTraversal (root.right, array);
 }
 return array;
}
  • 取跟节点为目标节点,开始遍历
  • 1.左孩子入栈 -> 直至左孩子为空的节点
  • 2.节点出栈 -> 访问该节点
  • 3.以右孩子为目标节点,再依次执行1、2、3
var inorderTraversal = function (root) {
 const result = [];
 const stack = [];
 let current = root;
 while (current || stack.length > 0) {
   while (current) {
     stack.push(current);
     current = current.left;
   }
   current = stack.pop();
   result.push(current.val);
   current = current.right;
 }
 return result;
};

什么是前序遍历

前序遍历(VLR),是二叉树遍历的一种,也叫做先根遍历、先序遍历、前序周游,可记做根左右。前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。

中 左 右

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 
输出: [1,2,3]
var preorderTraversal = function (root, array = []) {
  if (root) {
    array.push(root.val);
    preorderTraversal(root.left, array);
    preorderTraversal(root.right, array);
  }
  return array;
};
  • 取跟节点为目标节点,开始遍历
  • 1.访问目标节点
  • 2.左孩子入栈 -> 直至左孩子为空的节点
  • 3.节点出栈,以右孩子为目标节点,再依次执行1、2、3
var preorderTraversal = function (root) {
  const result = [];
  const stack = [];
  let current = root;
  while (current || stack.length > 0) {
    while (current) {
      result.push(current.val);
      stack.push(current);
      current = current.left;
    }
    current = stack.pop();
    current = current.right;
  }
  return result;
};

什么是后序遍历

后序遍历(LRD)是二叉树遍历的一种,也叫做后根遍历、后序周游,可记做左右根。后序遍历有递归算法和非递归算法两种。在二叉树中,先左后右再根,即首先遍历左子树,然后遍历右子树,最后访问根结点。

左 右 中

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 
输出: [3,2,1]
var postorderTraversal = function (root, array = []) {
  if (root) {
    postorderTraversal(root.left, array);
    postorderTraversal(root.right, array);
    array.push(root.val);
  }
  return array;
};
var postorderTraversal = function (root) {
  const result = [];
  const stack = [];
  let last = null; // 标记上一个访问的节点
  let current = root;
  while (current || stack.length > 0) {
    while (current) {
      stack.push(current);
      current = current.left;
    }
    current = stack[stack.length - 1];
    if (!current.right || current.right == last) {
      current = stack.pop();
      result.push(current.val);
      last = current;
      current = null; // 继续弹栈
    } else {
      current = current.right;
    }
  }
  return result;
}
// dfs
var postorderTraversal = function (root) {
 let res = [];
 function dfs(node) {
  if(!node) return
  dfs(node.left);
  dfs(node.right);
  res.push(node.val);
 }
 dfs(root);
 return res;
}

重建二叉树

function reConstructBinaryTree(pre, vin) {
    if(pre.length === 0){
        return null;
    }
    if(pre.length === 1){
        return new TreeNode(pre[0]);
    }
    const value = pre[0];
    const index = vin.indexOf(value);
    const vinLeft = vin.slice(0,index);
    const vinRight = vin.slice(index+1);
    const preLeft = pre.slice(1,index+1);
    const preRight = pre.slice(index+1);
    const node = new TreeNode(value);
    node.left = reConstructBinaryTree(preLeft, vinLeft);
    node.right = reConstructBinaryTree(preRight, vinRight);
    return node;
}

求二叉树的遍历

let pre;
let vin;
 
while((pre = readline())!=null){
    vin = readline();
    print(getHRD(pre,vin));
}
 
function getHRD(pre, vin) {
  if (!pre) {
    return '';
  }
  if (pre.length === 1) {
    return pre;
  }
  const head = pre[0];
  const splitIndex = vin.indexOf(head);
  const vinLeft = vin.substring(0, splitIndex);
  const vinRight = vin.substring(splitIndex + 1);
  const preLeft = pre.substring(1, splitIndex + 1);
  const preRight = pre.substring(splitIndex + 1);
  return getHRD(preLeft, vinLeft) + getHRD(preRight, vinRight) + head;
}

@dreamjean
Copy link
Collaborator

dreamjean commented May 24, 2022

二叉树的基本操作

(注:以下图文说明皆来自 wiki 百科 树的遍历

从二叉树的根节点出发,节点的遍历分为三个主要步骤:

  • 对当前节点进行操作(称为「存取」节点)
  • 遍历左边子节点
  • 遍历右边子节点

这三个步骤的先后顺序也是不同遍历方式的根本区别。

遍历方式的命名,源于其存取节点的顺序。

  • 深度优先可以按照根节点相对于左右子节点的存取先后来划分。

    • 如果把左节点和右节点的位置固定不动,那么根节点在左节点的左边,称为前序 (pre-order)
    • 根节点放在左节点和右节点的中间,称为中序 (in-order)
    • 根节点放在右节点的右边,称为后序 (post-order)
  • 对于广度优先而言,遍历没有前序中序后序之分

    • 给定一组已排序的子节点, 其「广度优先」的遍历只有一种唯一的结果

二叉树的前序遍历(Pre-Order Traversal)

  • 先存取根,然后存取树的遍历方式

Sorted_binary_tree_preorder

前序遍历:F, B, A, D, C, E, G, I, H

// 二叉树前序遍历模板
function preOrderTraversal(root) {
  if (!root) return // 判断root是否有到达这一层

  // Do Something with root 处理当前节点

  preOrderTraversal(root.left) // 若其中一侧的子树非空则会读取其子树
  preOrderTraversal(root.right) // 另一侧的子树也做相同的事
}

144. 二叉树的前序遍历

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */

// DFS 直接套用模板即可
var preorderTraversal = function (root) {
  return getTree(root)
}

const getTree = (node, arr = []) => {
  if (!node) return arr

  arr.push(node.val) // 处理当前节点
  getTree(node.left, arr)
  getTree(node.right, arr)

  return arr
}

二叉树的中序遍历(In-Order Traversal)

  • 先存取左(右)子树, 然后存取根,然后存取右(左)子树的遍历方式

Sorted_binary_tree_inorder

中序遍历:A, B, C, D, E, F, G, H, I

// 二叉树中序遍历模板
function preOrderTraversal(root) {
  if (!root) return // 判断root是否有到达这一层

  preOrderTraversal(root.left) // 若其中一侧的子树非空则会读取其子树

  // Do Something with root 处理当前节点

  preOrderTraversal(root.right) // 另一侧的子树也做相同的事
}

94. 二叉树的中序遍历

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
// DFS 方法一(直接套模板)
var inorderTraversal = function (root) {
  return getTree(root)
}

const getTree = (node, arr = []) => {
  if (!node) return arr

  getTree(node.left, arr)
  arr.push(node.val) // 处理当前节点
  getTree(node.right, arr)

  return arr
}

// DFS 方法二
var inorderTraversal = function (root) {
  const ans = []
  if (!root) return ans

  ans.push(
    ...inorderTraversal(root.left),
    root.val,
    ...inorderTraversal(root.right)
  )

  return ans
}

// 对方法一进行简化,可以按照中序遍历的顺序直接全部push进ans

二叉树的后序遍历(Post-Order Traversal)

  • 先存取子树, 然后存取根的遍历方式

Sorted_binary_tree_postorder

后序遍历:A, C, E, D, B, H, I, G, F

// 二叉树后序遍历模板
function preOrderTraversal(root) {
  if (!root) return // 判断root是否有到达这一层

  preOrderTraversal(root.left) // 若其中一侧的子树非空则会读取其子树
  preOrderTraversal(root.right) // 另一侧的子树也做相同的事

  // Do Something with root 处理当前节点
}

145. 二叉树的后序遍历

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */

// DFS 直接套模板
var postorderTraversal = function (root) {
  return getTree(root)
}

const getTree = (node, arr = []) => {
  if (!node) return arr

  getTree(node.left, arr)
  getTree(node.right, arr)
  arr.push(node.val) // 处理当前节点

  return arr
}

广度优先遍历

  • 和深度优先遍历不同,广度优先遍历会存取离根节点最近的节点。二叉树的广度优先遍历又称按层次遍历

Sorted_binary_tree_breadth-first_traversal

层次遍历:F, B, G, A, D, I, C, E, H

// 二叉树层次遍历模板
function level(node) {
  if (!node) return null

  const queue = [node]

  while (queue.length) {
    const curr = queue.shift()

    // Do Something with curr

    if (curr.left) queue.push(curr.left)
    if (curr.right) queue.push(curr.right)
  }
}

144. 二叉树的前序遍历

  • 前序遍历:中,左,右
  • 的特点是先进后出,因此出栈顺序为中,左,右
  • 那么入栈顺序必须调整为出栈顺序的倒序,也就是右,左,中
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */

// BFS 解法一
var preorderTraversal = function (root) {
  if (!root) return []

  const ans = []
  const stack = [root]

  while (stack.length) {
    const { val, left, right } = stack.pop()

    ans.push(val) // 处理当前节点
    if (right) stack.push(right) // 先右
    if (left) stack.push(left) // 后左
  }

  return ans
}

/* ------------------------------------------------ 分割线----------------------------------------------------- */

// BFS 解法二
var preorderTraversal = function (root) {
  if (!root) return []

  const ans = []
  const stack = [root]
  while (stack.length) {
    let curr = stack.pop()
    while (curr) {
      ans.push(curr.val)
      if (curr.right) stack.push(curr.right) // push右子节点
      curr = curr.left // 将指针指向左子节点
    }
  }

  return ans
}

// 了解到层序遍历的顺序之后,我们就可以通过循环利用指针的特性逐步推送到下一层
// 此方法在in-order和post-order的实现中较常用到

94. 二叉树的中序遍历

  • 中序遍历:左,中,右
  • 的特点是先进后出,因此出栈顺序为左,中,右
  • 那么入栈顺序必须调整为出栈顺序的倒序,也就是右,中,左
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var inorderTraversal = function (root) {
  const stack = []
  const ans = []

  while (root || stack.length) {
    while (root) {
      stack.push(root)
      root = root.left
    }

    root = stack.pop()
    ans.push(root.val)
    root = root.right
  }

  return ans
}

145. 二叉树的后序遍历

  • 后序遍历:左,右,中
  • 的特点是先进后出,因此出栈顺序为左,右,中
  • 那么入栈顺序必须调整为出栈顺序的倒序,也就是中,右,左
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */

// BFS
var postorderTraversal = function (root) {
  if (!root) return []

  const stack = []
  const ans = []
  let curr = root

  while (stack.length || curr) {
    if (curr) {
      ans.push(curr.val)
      stack.push(curr) // 储存节点
      curr = curr.right // 每次先遍历右节点,再遍历左节点
    } else {
      const node = stack.pop() // 右子树走到底之后,再从获取已经遍历过右子树的中间结果,将它出栈,并遍历它的左子树
      curr = node.left
    }
  }

  return ans.reverse()
}

@wangjbo
Copy link

wangjbo commented May 24, 2022

可恶,偷吃。
打卡占坑。明天摸鱼看。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants