**二叉树是一种数据结构。其特点是:
1.由一系列节点组成,具有层级结构。每个节点的特性包含有节点值、关系指针。节点之间存在对应关系。
2.树中存在一个没有父节点的节点,叫做根节点。树的末尾存在一系列没有子节点的节点,称为叶子节点。其他可以叫做中间节点。
3.树的根节点位于第一层,层级数越大,节点位置越深,层级数也叫做树高。
**排序二叉树为二叉树的一种类型,其特点是:
1.节点分为左右子树。
2.在不为空的情况下,左子树子节点的值都小于父节点的值。
3.在不为空的情况下,右子树子节点的值都大于父节点的值。
4.每个节点的左右子树都按照上述规则排序。
(一)生成二叉树
// 将值生成节点,节点包括:节点值、左指针、右指针
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
// 二叉树
class BinaryTree {
constructor() {
this.root = null; // 根节点
}
// 插入,插入的是一个节点,所以应该先把值生成节点(包括节点值,左指针,右指针)
insert(key) {
const newNode = new Node(key);
// 如果根节点为空,则新节点作为根节点,否则在根节点下进行插入
if (this.root === null) {
this.root = newNode;
}
this.insertNode(this.root, newNode);
}
// 有根节点的情况下插入值
insertNode(root, newNode) {
if (newNode.key < root.key) {
// 进入左子树
if (root.left === null) {
// 左子树为空
root.left = newNode;
} else {
// 左子树已存在
this.insertNode(root.left, newNode);
}
} else if (newNode.key > root.key) {
// 进入右子树
if (root.right === null) {
// 右子树为空
root.right = newNode;
} else {
// 右子树已存在
this.insertNode(root.right, newNode);
}
}
}
}
const binaryTree = new BinaryTree();
var keys = [19, 8, 15, 24, 45, 12, 5];
keys.forEach((key) => binaryTree.insert(key));
console.log(binaryTree);
(二)二叉树的遍历
1. 中序遍历
(1)递归方法
// 中序遍历:递归方法
var inorderTraversal = function (root) {
var result = [];
pushRoot(root, result);
return result;
};
function pushRoot(root, result) {
if (root !== null) {
// 左
if (root.left !== null) {
pushRoot(root.left, result);
}
// 中
result.push(root.key);
// 右
if (root.right !== null) {
pushRoot(root.right, result);
}
}
}
// 注意这里传入的是binaryTree.root,而不是binaryTree
inorderTraversal(binaryTree.root); // [5, 8, 12, 15, 19, 24, 45]
(2)非递归方法(栈)
var inorderTraversal = function (root) {
let result = [];
let stack = [];
let node = root;
while (node !== null || stack.length !== 0) {
if (node !== null) {
stack.push(node);
node = node.left; // 遍历左,存入栈中
} else {
debugger;
// node===null时说明左边没有了,那么栈顶就是最左边的(最小的)
node = stack.pop();
result.push(node.key);
node = node.right; //看右边有没有
}
}
console.log(result); // [5, 8, 12, 15, 19, 24, 45]
return result;
};
inorderTraversal(binaryTree.root);
2.前序遍历
(1)递归方法
// 前序遍历 递归
var preOrderTraversal = function (root) {
var res = [];
preOrder(root, res);
return res;
};
function preOrder(root, res) {
if (root !== null) {
// 中
res.push(root.key);
// 左
preOrder(root.left, res);
// 右
preOrder(root.right, res);
}
}
console.log(preOrderTraversal(binaryTree.root)); // [19, 8, 5, 15, 12, 24, 45]
(2)非递归方法(栈)
var preOrderTraversal = function (root) {
var res = [];
var stack = [];
var node = root;
while (node !== null || stack.length !== 0) {
if (node !== null) {
res.push(node.key);
stack.push(node);
node = node.left;
} else {
node = stack.pop();
node = node.right;
}
}
return res
};
console.log(preOrderTraversal(binaryTree.root)) // [19, 8, 5, 15, 12, 24, 45]
3.后序遍历
(1)递归方法
var afterOrderTraversal = function (root) {
var res = [];
afterOrder(root, res);
return res;
};
function afterOrder(root, res) {
if (root !== null) {
// 左
afterOrder(root.left, res);
// 右
afterOrder(root.right, res);
// 中
res.push(root.key);
}
}
console.log(afterOrderTraversal(binaryTree.root)); // [5, 12, 15, 8, 45, 24, 19]
(2)非递归方法
首先要搞清楚先序、中序、后序的非递归算法共同之处:用栈来保存先前走过的路径,以便可以在访问完子树后,可以利用栈中的信息,回退到当前节点的双亲节点,进行下一步操作。
后序遍历的非递归算法是三种顺序中最复杂的,原因在于,后序遍历是先访问左、右子树,再访问根节点,而在非递归算法中,利用栈回退到时,并不知道是从左子树回退到根节点,还是从右子树回退到根节点,如果从左子树回退到根节点,此时就应该去访问右子树,而如果从右子树回退到根节点,此时就应该访问根节点。所以相比前序和后序,必须得在压栈时添加信息,以便在退栈时可以知道是从左子树返回,还是从右子树返回进而决定下一步的操作。