一. 树
树?
上面图片所示是是自然树的形态!下图是数据结构中的树,看起来就像倒挂的树(根朝上,叶子朝下)吧!
看一下定义吧。
树形数据结构是一类重要的非线性数据结构
。
树形数据结构可以表示数据元素之间一对多的关系。其中以树与二叉树最为常用,直观看来,树是以分支关系定义的层次结构。
节点: 树上每个元素(如:A、B、C、D 节点)
父子关系: 来连接相邻节点之间的关系(如:A 节点和 B 节点之间为父子关系)
父节点和子节点: 相对关系(如:A 节点是 B 节点的父节点,B 节点是 A 节点的子节点)
兄弟节点: 相对关系(如:B、C、D 节点互为兄弟节点)
根节点: 没有父节点的节点(如:E 节点)
叶子节点或者叶节点: 没有子节点的节点(如:G 节点、K 节点)
节点的高度: 节点到叶子节点的最长路径
节点的深度: 节点到根节点的路径长度
节点的层数: 节点的深度 + 1
树的高度: 根节点高度
二. 二叉树
如果一棵树,它的任意一个节点最多有2个节点,那它就是二叉树。
1. 满二叉树
如果一棵二叉树,它的叶子节点全部都在最底层,其它节点都有 2 个节点,那它就是满二叉树。
2. 完全二叉树
如果一棵二叉树,
它的叶子节点全部都在最后 2 层;
最后 1 层的节点全都靠左排列;
除最后 1 层外,其它层的节点树都达到最大,
那它就是完全二叉树。
三. 二叉树的存储方式
1. 链式存储法
基于指针或者引用的链式存储法。
每个节点有三个字段,其中一个存储数据,另外两个是指向左右子节点的指针。顺着根节点,就可以通过左右子节点的指针,把整棵树都串起来。
tip:
大部分二叉树都是使用这种方式存储的。
2. 顺序存储法
基于数组的顺序存储法。
如果节点 X 存储在数组中下标为 i 的位置,下标为 2 * i 的位置存储的就是左子节点,下标为 2 * i + 1 的位置存储的就是右子节点。反过来,下标为 i/2 的位置存储就是它的父节点。通过这种方式,我们只要知道根节点存储的位置(一般情况下,为了方便计算子节点,根节点会存储在下标为 1 的位置),这样就可以通过下标计算,把整棵树都串起来。
tip:
顺序存储法适用于完全二叉树
。对于非完全二叉树,根据下标构建存储数据后,会有一些存储空间没有使用,造成浪费。
四. 二叉树的遍历
javascript 实现一下吧!
// 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)
}
1. 前序遍历
递归实现:
/**
* @param {TreeNode} root
* @return {number[]}
*/
// 递归方法
var preorderTraversal = function (root) {
let result = [];
if (!root) {
return result;
}
let tracersal = function (root) {
if (root) {
result.push(root.val);
tracersal(root.left);
tracersal(root.right);
}
}
tracersal(root);
return result;
};
非递归实现:
/**
* @param {TreeNode} root
* @return {number[]}
*/
// 非递归方法(代码模拟实现栈)
var preorderTraversal = function (root) {
let stack = [root];
let result = [];
while (stack.length > 0) {
let node = stack.pop();
if (node) {
result.push(node.val);
node.right && stack.push(node.right);
node.left && stack.push(node.left);
}
}
return result;
};
2. 中序遍历
递归实现:
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function (root) {
let result = [];
if (!root) {
return result;
}
let traversal = function (node) {
if (node) {
traversal(node.left);
result.push(node.val);
traversal(node.right);
}
}
traversal(root);
return result;
};
非递归实现:
/**
* @param {TreeNode} root
* @return {number[]}
*/
var inorderTraversal = function (root) {
let stack = [];
let result = [];
while (root || stack.length > 0) {
while (root) {
stack.push(root);
root = root.left;
}
let node = stack.pop();
result.push(node.val);
root = node.right;
}
return result;
};
3. 后序遍历
递归实现:
/**
* @param {TreeNode} root
* @return {number[]}
*/
var postorderTraversal = function (root) {
let result = [];
if (!root) {
return result;
}
let traversal = function (node) {
if (node) {
traversal(node.left);
traversal(node.right);
result.push(node.val);
}
}
traversal(root);
return result;
};
非递归实现:
/**
* @param {TreeNode} root
* @return {number[]}
*/
var postorderTraversal = function (root) {
let stack = [];
let result = [];
// 标识上一次处理的节点
let prev = null;
if (!root) {
return result;
}
while (root || stack.length > 0) {
while (root) {
stack.push(root);
root = root.left;
}
let node = stack.pop();
// prev === node.right: 表示这个节点的右子树已经处理完毕了,可以处理该节点了
if (!node.right || prev === node.right) {
result.push(node.val);
prev = node;
root = null;
} else {
// 当前处理节点 node 有右子树且尚未处理
stack.push(node);
root = node.right;
}
}
return result;
};
4. 层序遍历
递归实现:
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function (root) {
let result = [];
if (!root) {
return result;
}
let helper = function (node, arr, level) {
if (!node) {
return;
}
if (!arr[level]) {
arr[level] = [];
}
arr[level].push(node.val);
helper(node.left, arr, level + 1);
helper(node.right, arr, level + 1);
}
helper(root, result, 0);
return result;
};