二叉搜索树

125 阅读6分钟

二叉搜索树

二叉搜索树也叫二叉排序树:因为它具有排序的效果

  • 左子树的节点都比当前节点小
  • 右子树的节点都比当前节点大
var arr = [];
for (var i = 0; i < 20; i++) {
  arr.push(Math.floor(Math.random() * 100));
}

function Node(value) {
  this.value = value;
  this.left = null;
  this.right = null;
}

function addNode(root, num) {
  if (root == null) return;
  if (root.value == num) return;
  if (root.value < num) {
    // 比当前节点大
    if (root.right == null) root.right = new Node(num); //右侧为空创建节点
    else addNode(root.right, num); //如果右侧不为空就对右侧递归
  } else {
    //比当前节点小
    if (root.left == null) root.left = new Node(num); //左侧为空创建节点
    else addNode(root.left, num); //如果左侧不为空就对左侧递归
  }
}
// 创建二叉搜索树
function BuildSearchTree(arr) {
  if (arr == null && arr.length == 0) return null;
  var root = new Node(arr[0]);
  for (var i = 0; i < arr.length; i++) {
    // 把每个节点加进去
    addNode(root, arr[i]);
  }
  return root;
}

//使用二叉搜索树搜索
function searchTree(root, target) {
  if (root == null) return false;
  if (root.value == target) return true;
  if (root.value > target) return searchTree(root.left, target);
  else return searchTree(root.right, target);
}

var root = BuildSearchTree(arr);
var res = searchTree(root, 98);
console.log(res, arr);

同时,一棵二叉搜索树它的树的深度越深,要搜索的次数就越多,理想情况下,我们让这个二叉树越满,那么它的深度越浅,效率就越好,所以还可以在此基础上继续优化

平衡二叉树

将二叉搜索树变成一颗二叉平衡搜索树

  • 根节点的左右子树之间的高度差不超过 1
  • 每颗子树都符合它的左右子树的高度差不超过 1
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");
var h = new Node("h");
var j = new Node("j");

a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.left = f;
c.right = g;
d.right = h;
e.right = j;

// 获取树的深度
function getDeep(root) {
  if (root == null) return 0;
  var leftDeep = getDeep(root.left);
  var rightDeep = getDeep(root.right);
  return Math.max(leftDeep, rightDeep) + 1;
}

//是否是平衡二叉树
function isBalance(root) {
  if (root == null) return true;
  var leftDeep = getDeep(root.left);
  var rightDeep = getDeep(root.right);
  if (Math.abs(leftDeep - rightDeep) > 1) {
    //不平衡
    return false;
  } else {
    return isBalance(root.left) && isBalance(root.right);
  }
}
// console.log(isBalance(a));
// console.log(getDeep(e));

二叉树的单旋操作(左单旋、右单旋)

  • 某一结点不平衡,左边浅、右边深,左单旋
  • 旋转节点:不平衡的节点
  • 新根:旋转之后成为根几点的节点
  • 变化分支:父级节点发生变化的分支
  • 不变分支:父级节点不变的分支

左单旋时:

旋转节点:不平衡的节点

新根:右子树的根节点

变化分支:旋转节点的右子树的左子树

不变分支:旋转节点的右子树的右子树

function Node(value) {
  this.value = value;
  this.left = null;
  this.right = null;
}
var node2 = new Node("2");
var node5 = new Node("5");
var node3 = new Node("3");
var node6 = new Node("6");

node2.right = node5;
node5.left = node3;
node5.right = node6;

// 获取树的深度
function getDeep(root) {
  if (root == null) return 0;
  var leftDeep = getDeep(root.left);
  var rightDeep = getDeep(root.right);
  return Math.max(leftDeep, rightDeep) + 1;
}

function isBalance(root) {
  if (root == null) return true;
  var leftDeep = getDeep(root.left);
  var rightDeep = getDeep(root.right);
  if (Math.abs(leftDeep - rightDeep) > 1) {
    //不平衡
    return false;
  } else {
    return isBalance(root.left) && isBalance(root.right);
  }
}

// 左旋
function leftRotate(root) {
  // 找到新的根
  var newRoot = root.right;
  // 找到变化分支
  var changeTree = root.right.left;
  // 当前节点的右孩子改变为变化分支
  root.right = changeTree;
  // 新根的左孩子为旋转节点
  newRoot.left = root;
  return newRoot;
}
// 右旋
function rightRotate(root) {
  // 新根
  var newRoot = root.left;
  // 变化分支
  var changeTree = root.left.right;
  // 当前节点的左孩子改变为变化分支
  root.left = changeTree;
  // 新根的右孩子为旋转节点
  newRoot.right = root;
  return newRoot;
}

// 传入不平衡二叉树变为平衡二叉树
function change(root) {
  //返回平衡之后的根节点
  if (isBalance(root)) return root;
  if (root.left != null) root.left = change(root.left);
  if (root.right != null) root.right = change(root.right);
  var leftDeep = getDeep(root.left);
  var rightDeep = getDeep(root.right);
  if (Math.abs(leftDeep - rightDeep) < 2) {
    return root; //平衡
  } else if (leftDeep > rightDeep) {
    // 不平衡,左边深,右旋
    return rightRotate(root);
  } else {
    // 不平衡,右编深,左旋
    return leftRotate(root);
  }
  return root;
}

console.log(isBalance(node2));
var newRoot = change(node2);
console.log(isBalance(newRoot));

二叉树的双旋

左右双旋、右左双旋(就是在单旋的基础上,多做一步判断,变化分支在最大深度上,就需要对他再单旋)

function Node(value) {
  this.value = value;
  this.left = null;
  this.right = null;
}
var node8 = new Node("8");
var node7 = new Node("7");
var node6 = new Node("6");
var node5 = new Node("5");
var node2 = new Node("2");

node8.left = node7;
node7.left = node6;
node6.left = node5;
node5.left = node2;

// 获取树的深度
function getDeep(root) {
  if (root == null) return 0;
  var leftDeep = getDeep(root.left);
  var rightDeep = getDeep(root.right);
  return Math.max(leftDeep, rightDeep) + 1;
}

function isBalance(root) {
  if (root == null) return true;
  var leftDeep = getDeep(root.left);
  var rightDeep = getDeep(root.right);
  if (Math.abs(leftDeep - rightDeep) > 1) {
    //不平衡
    return false;
  } else {
    return isBalance(root.left) && isBalance(root.right);
  }
}

// 左旋
function leftRotate(root) {
  // 找到新的根
  var newRoot = root.right;
  // 找到变化分支
  var changeTree = root.right.left;
  // 当前节点的右孩子改变为变化分支
  root.right = changeTree;
  // 新根的左孩子为旋转节点
  newRoot.left = root;
  return newRoot;
}
// 右旋
function rightRotate(root) {
  // 新根
  var newRoot = root.left;
  // 变化分支
  var changeTree = root.left.right;
  // 当前节点的左孩子改变为变化分支
  root.left = changeTree;
  // 新根的右孩子为旋转节点
  newRoot.right = root;
  return newRoot;
}

// 传入不平衡二叉树变为平衡二叉树
function change(root) {
  //返回平衡之后的根节点
  if (isBalance(root)) return root;
  if (root.left != null) root.left = change(root.left);
  if (root.right != null) root.right = change(root.right);
  var leftDeep = getDeep(root.left);
  var rightDeep = getDeep(root.right);
  if (Math.abs(leftDeep - rightDeep) < 2) {
    return root; //平衡
  } else if (leftDeep > rightDeep) {
    // 不平衡,左边深,右旋
    var changeTree = getDeep(root.left.right); //变化分支
    var noChangeTree = getDeep(root.left.left); //非变化
    if (changeTree > noChangeTree) {
      // 如果最大深度在变化分支上
      root.left = leftRotate(root.left);
    }
    return rightRotate(root);
  } else {
    // 不平衡,右编深,左旋
    var changeTree = getDeep(root.right.left); //变化分支
    var noChangeTree = getDeep(root.right.right); //非变化
    if (changeTree > noChangeTree) {
      // 如果最大深度在变化分支上
      root.right = rightRotate(root.right);
    }
    return leftRotate(root);
  }
}
let newRoot = change(node8);
console.log(newRoot);

二叉树的双旋

左左双旋、右右双旋特殊情况

function Node(value) {
  this.value = value;
  this.left = null;
  this.right = null;
}
var node8 = new Node("8");
var node7 = new Node("7");
var node6 = new Node("6");
var node5 = new Node("5");
var node2 = new Node("2");

node8.left = node7;
node7.left = node6;
node6.left = node5;
node5.left = node2;

// 获取树的深度
function getDeep(root) {
  if (root == null) return 0;
  var leftDeep = getDeep(root.left);
  var rightDeep = getDeep(root.right);
  return Math.max(leftDeep, rightDeep) + 1;
}

function isBalance(root) {
  if (root == null) return true;
  var leftDeep = getDeep(root.left);
  var rightDeep = getDeep(root.right);
  if (Math.abs(leftDeep - rightDeep) > 1) {
    //不平衡
    return false;
  } else {
    return isBalance(root.left) && isBalance(root.right);
  }
}

// 左旋
function leftRotate(root) {
  // 找到新的根
  var newRoot = root.right;
  // 找到变化分支
  var changeTree = root.right.left;
  // 当前节点的右孩子改变为变化分支
  root.right = changeTree;
  // 新根的左孩子为旋转节点
  newRoot.left = root;
  return newRoot;
}
// 右旋
function rightRotate(root) {
  // 新根
  var newRoot = root.left;
  // 变化分支
  var changeTree = root.left.right;
  // 当前节点的左孩子改变为变化分支
  root.left = changeTree;
  // 新根的右孩子为旋转节点
  newRoot.right = root;
  return newRoot;
}

// 传入不平衡二叉树变为平衡二叉树
function change(root) {
  //返回平衡之后的根节点
  if (isBalance(root)) return root;
  if (root.left != null) root.left = change(root.left);
  if (root.right != null) root.right = change(root.right);
  var leftDeep = getDeep(root.left);
  var rightDeep = getDeep(root.right);
  if (Math.abs(leftDeep - rightDeep) < 2) {
    return root; //平衡
  } else if (leftDeep > rightDeep) {
    // 不平衡,左边深,右旋
    var changeTree = getDeep(root.left.right); //变化分支
    var noChangeTree = getDeep(root.left.left); //非变化
    if (changeTree > noChangeTree) {
      // 如果最大深度在变化分支上
      root.left = leftRotate(root.left);
    }
    var newRoot = rightRotate(root);
    newRoot.right = change(newRoot.right); //右侧分支再平衡
    newRoot = change(newRoot);
    return newRoot;
  } else {
    // 不平衡,右编深,左旋
    var changeTree = getDeep(root.right.left); //变化分支
    var noChangeTree = getDeep(root.right.right); //非变化
    if (changeTree > noChangeTree) {
      // 如果最大深度在变化分支上
      root.right = rightRotate(root.right);
    }
    var newRoot = leftRotate(root);
    newRoot.left = change(newRoot.left);
    newRoot = change(newRoot);
    return newRoot;
  }
}

console.log(isBalance(node8));

let newRoot = change(node8);
console.log(newRoot);
console.log(isBalance(node8));

234 树的由来

树的分叉越多,层数越少,但是树的结构就会越复杂(经计算,4 个分叉时,树的性能最好)

234 树:我们希望有一棵树最多有 4 个分叉

  • 234 树的子节点永远在最后一层
  • 234 树永远平衡(每一个路径高度都相同)

但是分支变多,那么树的复杂度就会变大,所以希望对 234 树简化

红黑树

基础是234 树 性质:

  • 节点是红色或者黑色(红色代表虚拟节点不是一个单独的节点)
  • 根节点是黑色
  • 每个红色节点的两个子节点都是黑色,(从每个叶子到根的所有路径上不得有两个连续的红色节点)
  • 从任一节点到其每个叶子的所有路径包含相同数目的黑色节点(红色节点是虚拟的,不计数)

红黑树就是二次平衡排序树,只是每个节点赋予了它不同的意义