树形结构

220 阅读6分钟

树形结构--有向无环图

  • 树形结构有一个根节点
  • 树形结构没有回路,是向下展开
  • 树是图的一种

度:树中一个节点拥有的最多的分支数

深度:树最深有几层

叶子节点:没有分支的节点

节点:既不是叶子节点也不是根节点

特殊的树形结构(二叉树)

规定树的度最多为 2,也就是任何一个节点最多有两个分支

满二叉树

  1. 所有的叶子节点都在最底层
  2. 每个非叶子节点都有两个子节点

完全二叉树

国内定义:

  1. 叶子节点全都在最后一层或倒数第二层
  2. 非叶子节点,如果没有左孩子那么一定没有右孩子

国际定义

  1. 叶子节点全都在最后一层或倒数第二层
  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);