树形结构--有向无环图
- 树形结构有一个根节点
- 树形结构没有回路,是向下展开
- 树是图的一种
度:树中一个节点拥有的最多的分支数
深度:树最深有几层
叶子节点:没有分支的节点
节点:既不是叶子节点也不是根节点
特殊的树形结构(二叉树)
规定树的度最多为 2,也就是任何一个节点最多有两个分支
满二叉树
- 所有的叶子节点都在最底层
- 每个非叶子节点都有两个子节点
完全二叉树
国内定义:
- 叶子节点全都在最后一层或倒数第二层
- 非叶子节点,如果没有左孩子那么一定没有右孩子
国际定义
- 叶子节点全都在最后一层或倒数第二层
- 如果有叶子节点,就必然有两个叶子节点
二叉树中的子树
在二叉树中每个节点都认为自己是根节点
子树:二叉树中的每一个节点或者叶子节点,都是一棵子树的根节点。
左子树、右子树
二叉树的遍历(将集合中的每个元素进行遍历和查看)
传递二叉树要传入根节点
创建一个二叉树;
function Tree(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a = new Tree("A");
var b = new Tree("B");
var c = new Tree("C");
var d = new Tree("D");
var e = new Tree("E");
var f = new Tree("F");
var g = new Tree("G");
a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;
前序遍历(先根次序遍历)
根左右
// 前序遍历
function PreOrder(root) {
if (root == null) return;
console.log(root.value);
PreOrder(root.left);
PreOrder(root.right);
}
PreOrder(a);
中序遍历(中根次序遍历)
左根右
// 中序遍历
function inOrder(root) {
if (root == null) return;
inOrder(root.left);
console.log(root.value);
inOrder(root.right);
}
inOrder(a);
后序遍历(后根次序遍历)
左右根
// 后序遍历
function PostOrder(root) {
if (root == null) return;
PostOrder(root.left);
PostOrder(root.right);
console.log(root.value);
}
PostOrder(a);
还原二叉树
还原二叉树必须有中序遍历
通过代码实现前序、中序还原二叉树
var preOrder = ["a", "c", "f", "g", "b", "d", "e"];
var inOrder = ["f", "c", "g", "a", "d", "b", "e"];
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
function Restore(pre, inOrder) {
if (
pre == null ||
inOrder == null ||
pre.length == 0 ||
inOrder.length == 0 ||
pre.length != inOrder.length
)
return null;
var root = new Node(pre[0]); //找到根节点
var index = inOrder.indexOf(root.value); //找到位置然后截取
var pLeft = pre.slice(1, 1 + index); //前序遍历的左子树
var pRight = pre.slice(index + 1, pre.length); //前序遍历的右子树
var inLeft = inOrder.slice(0, index); //中序遍历的左子树
var inRight = inOrder.slice(index + 1, inOrder.length); //中序遍历的右子树
root.left = Restore(pLeft, inLeft); //还原左子树并赋值
root.right = Restore(pRight, inRight); //还原右子树并赋值
// 返回根节点
return root;
}
var r = Restore(preOrder, inOrder);
console.log(r.left);
通过代码实现中序、后序还原二叉树
var postOrder = ["f", "g", "c", "d", "e", "b", "a"];
var inOrder = ["f", "c", "g", "a", "d", "b", "e"];
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
function Restore(post, inOrder) {
if (
post == null ||
inOrder == null ||
post.length == 0 ||
inOrder.length == 0 ||
post.length != inOrder.length
)
return null;
var root = new Node(post[post.length - 1]); //找到根节点
var index = inOrder.indexOf(root.value); //找到位置然后截取
var pLeft = post.slice(0, index); //后续遍历的左子树
var pRight = post.slice(index, post.length - 1); //后续遍历的右子树
var inLeft = inOrder.slice(0, index); //中序遍历的左子树
var inRight = inOrder.slice(index + 1, inOrder.length); //中序遍历的右子树
root.left = Restore(pLeft, inLeft); //还原左子树并赋值
root.right = Restore(pRight, inRight); //还原右子树并赋值
// 返回根节点
return root;
}
var r = Restore(postOrder, inOrder);
console.log(r.left);
二叉树的搜索
二叉树的深度优先搜索
更适合探索未知
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");
a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;
// 深度优先搜索和二叉树前序遍历的顺序是一样的
function deepSearch(root, target) {
if (root == null) return false;
if (root.value == target) return true;
var left = deepSearch(root.left, target);
var right = deepSearch(root.right, target);
return left || right;
}
console.log(deepSearch(a, "g"));
二叉树的广度优先搜索
更适合探索局域
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");
a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;
// 深度优先搜索和二叉树前序遍历的顺序是一样的
function broadSearch(rootList, target) {
if (rootList == null || rootList.length == 0) return false;
var childList = []; //当前层所有节点的子节点,传入下一层递归
for (var i = 0; i < rootList.length; i++) {
if (rootList[i] != null && rootList[i].value == target) {
return true;
} else {
// 不等于目标值,把他的子节点放入,之后需要遍历
childList.push(rootList[i].left);
childList.push(rootList[i].right);
}
}
return broadSearch(childList, target);
}
console.log(broadSearch([a], "e"));
二叉树的比较
二叉树的左右子树交换位置算不算是相同的二叉树?
一般情况下,左右子树互换不算相同二叉树
左右子树不能互换的情况
这里是强调左右子树不能交换;
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a1 = new Node("a");
var b1 = new Node("b");
var c1 = new Node("c");
var d1 = new Node("d");
var e1 = new Node("e");
var f1 = new Node("f");
var g1 = new Node("g");
a1.left = c1;
a1.right = b1;
c1.left = f1;
c1.right = g1;
b1.left = d1;
b1.right = e1;
var a2 = new Node("a");
var b2 = new Node("b");
var c2 = new Node("c");
var d2 = new Node("d");
var e2 = new Node("e");
var f2 = new Node("f");
var g2 = new Node("g");
a2.left = c2;
a2.right = b2;
c2.left = f2;
c2.right = g2;
b2.left = d2;
b2.right = e2;
function compare(root1, root2) {
if (root1 == root2) return true; //同一棵树
if ((root1 == null && root2 != null) || (root2 == null && root1 != null))
return false;
if (root1.value != root2.value) return false; //相同位置的值不相等
var leftBool = compare(root1.left, root2.left); //判断左子树是否相同
var rightBool = compare(root1.right, root2.right); //判断右子树是否相同
return leftBool && rightBool;
}
var res = compare(a1, a2);
console.log(res);
左右子树可以互换的情况
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a1 = new Node("a");
var b1 = new Node("b");
var c1 = new Node("c");
var d1 = new Node("d");
var e1 = new Node("e");
var f1 = new Node("f");
var g1 = new Node("g");
a1.left = c1;
a1.right = b1;
c1.left = f1;
c1.right = g1;
b1.left = d1;
b1.right = e1;
var a2 = new Node("a");
var b2 = new Node("b");
var c2 = new Node("c");
var d2 = new Node("d");
var e2 = new Node("e");
var f2 = new Node("f");
var g2 = new Node("g");
a2.left = c2;
a2.right = b2;
c2.left = f2;
c2.right = g2;
b2.left = d2;
b2.right = e2;
function compare(root1, root2) {
if (root1 == root2) return true; //同一棵树
if ((root1 == null && root2 != null) || (root2 == null && root1 != null))
return false;
if (root1.value != root2.value) return false; //相同位置的值不相等
return (
(compare(root1.left, root2.left) && compare(root1.right, root2.right)) ||
(compare(root1.left, root2.right) && compare(root1.right, root2.left))
);
}
var res = compare(a1, a2);
console.log(res);
二叉树的 diff 算法
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a1 = new Node("a");
var b1 = new Node("b");
var c1 = new Node("c");
var d1 = new Node("d");
var e1 = new Node("e");
var f1 = new Node("f");
var g1 = new Node("g");
a1.left = c1;
a1.right = b1;
c1.left = f1;
c1.right = g1;
b1.left = d1;
b1.right = e1;
var a2 = new Node("a");
var b2 = new Node("i");
var c2 = new Node("c");
var d2 = new Node("p");
var e2 = new Node("e");
var f2 = new Node("f");
var g2 = new Node("g");
a2.left = c2;
a2.right = b2;
c2.left = f2;
c2.right = g2;
b2.left = d2;
// b2.right = e2;
// 二叉树的diff算法
// 用一个对象数组记录删除、新增、修改的情况
function diff(root1, root2, diffList) {
if (root1 == root2) return diffList;
if (root1 == null && root2 !== null) {
// 新增节点
diffList.push({ type: "新增", origin: null, now: root2 });
} else if (root1 != null && root2 == null) {
// 删除了节点
diffList.push({ type: "删除", origin: root1, now: null });
} else if (root1.value != root2.value) {
// 相同位置的节点值不同,修改了
diffList.push({ type: "修改", origin: root1.value, now: root2.value });
// 注意这里还要继续往下,因为删除节点那么就不会存在它的子节点,
// 但是只是修改就需要对它的子节点继续递归
diff(root1.left, root2.left, diffList);
diff(root1.right, root2.right, diffList);
} else {
//以上情况之外
diff(root1.left, root2.left, diffList);
diff(root1.right, root2.right, diffList);
}
}
var diffList = [];
diff(a1, a2, diffList);
console.log(diffList);