数据结构与算法——二叉树问题

一、二叉树的基本概念

从逻辑结构角度来看,链表、栈、队列都是线性结构;而“二叉树”属于树形结构。
在这里插入图片描述

  1. 多叉树的基本概念,以上图中“多叉树”为例说明
  • 节点:多叉树中的每一个点都叫节点;其中最上面的那个节点叫“根节点”;

  • 根节点:位于树顶部的节点;

  • 父节点:节点1是节点2/3/4/5/6的父节点,然后节点2/3/4/5/6是节点1的子节点;节点2/3/4/5/6又是互为兄弟节点,因为它们有父节点为同一个节点;

  • 叶子节点:没有子元素的节点;

  • 子树:子节点及子节点的后台节点形成的一个节点集合叫子树;对于只有两个子节点的节点,其左边的子节点叫左子树,右边的叫右子树;

  • 节点的深度:取决于它的祖先节点的数量;

  • 树的高度:取决于所有节点深度的最大值。

  1. 二叉树的特点
  • 每个节点的度最大为2(最多拥有2颗子树);

  • 左子树和右子树是有顺序的。即使某节点只有一颗子树,也要区分左右子树;

  • 非空二叉树的第i层,最多有2^(i-1)个节点(i >= 1);

  • 在高度为h的二叉树上最多有2^h - 1个节点(h >= 1);

  • 对于任何一颗非空二叉树,如果叶子节点个数为n0, 度为2的节点个数为n2, 则有n0 = n2 + 1

  • 假设度为1的节点个数为n1, 那么二叉树的节点总数 n = n0 + n1 + n2。则二叉树的边数T = n1 + 2 * n2 = n -1 = n0 + n1 + n2 -1 --> n2 + 1 = n0

  1. 真二叉树/满二叉树/完全二叉树
    在这里插入图片描述
    在这里插入图片描述
  • 真二叉树:所有节点的度都要么为2,要么为0;

  • 满二叉树

(1)所有节点的度都要么为2,要么为0,且所有的叶子节点都在最后一层;

(2)假设满二叉树的高度为h (h>=1),那么第i层的节点数量:2^(i-1), 叶子节点数量:2(^h-1), 总节点数量n = 2^h -1 = 2^0 + 2^1 + 2^2 + … + 2^(h-1)

等比数列公式:a1=1, 公比q=2, 则an = a1*q(n-1)=2(n-1), 前n项之和Sn = a1+a2+…+an = 2^0 + 2^1 + 2^2 + … + 2^(n-1) = a1(1-qn)/(1-q)=2n-1

(3)在同样高度的二叉树中,满二叉树的叶子节点数量最多、总节点数量最多;

(4)满二叉树一定是真二叉树,真二叉树不一定是满二叉树;

  • 完全二叉树

(1)叶子节点只会出现在最后两层,且最后一层的叶子节点都靠左对齐;

(2)完全二叉树从根节点到倒数第二层是一颗满二叉树;满二叉一定是完全二叉树,完全二叉树不一定是满二叉树;

(3)度为1的节点只有左子树;度为1的节点要么是1个,要么是0个;

(4)假设完全二叉树的高度为h(h>=1), 那么至少有2^(h-1) 个节点(2^0 + 2^1 + 2^2 + … + 2^(h-2) + 1), 最多有2^h - 1个节点(2^0 + 2^1 + 2^2 + …+ 2^(h-1), 满二叉树);总节点数量为n, 则有2^(h-1) <= n < 2^h --> h-1 <= log(2)(n) < h --> h = floor(log(2)(n)) + 1 (floor是向下取整,ceiling是向上取整)

  • 一颗有n个节点的完全二叉树(n > 0),从上到下,从左到右对节点从1开始编号,对任意第i个节点:
如果i = 1, 它是根节点
如果i > 1, 它的父节点编号为floor(i/2)
如果2i <= n, 它的左子节点编号为2i
如果2i > n, 它无左子节点
如果2i + 1 <= n, 它的右子节点编号为2i + 1
如果2i + 1 > n , 它无右子节点  

【问题】:如果一颗完全二叉树有768个节点,求叶子节点的个数?
【分析】:假设叶子节点个数为n0,度为1的节点个数为n1,度为2的节点个数为n2。
所以总节点个数n = n0 + n1 + n2,且n0 = n2 + 1 ,因此 n = 2n0 + n1 -1
根据完全二叉树的定义,n1要么为0,要么为1:
当n1为1时, n = 2n0, n必然为偶数。叶子节点个数n0 = n / 2,非叶子节点个数 n1 + n2 = n / 2 ;
当n1为0,n = 2n0 - 1,n必然为奇数。叶子节点个数n0 = (n + 1) / 2, 非叶子节点个数 n1 + n2 = (n - 1) / 2
因此可以判断出来当这个完全二叉树有768个节点时,它的叶子节点个数为:384

二、二叉查找树

二叉查找树(BST)是二叉树的一种,但是它只允许你在左侧节点存储(比父节点)小的值;在右侧节点存储(比父节点)大(或者等于)的值。

  1. 插入逻辑

(1)设根节点为当前节点

(2)如果待插入节点保存的数据小于当前节点,则设新的当前节点为原节点的左节点;反之,则设新的当前节点为原节点的右节点

(3)若当前节点的左节点为null, 则将新节点插入这个位置,退出循环;反之,继续执行下一次循环

(4)若当前节点的右节点为null, 就将新节点插入这个位置,退出循环;反之,继续执行下一次循环

首先创建一个类:

const BinarySearchTree=function(){
    var root=null;  二叉树根节点
    var size=0;  //节点个数
    ///element指根节点保存的数据  left指左节点保存的数据  right指右节点保存的数据
    function Node(element,left,right){
        this.element=element;
        this.left=left;
        this.right=right;
    }  
}

插入元素代码:

function insertBST(element){
    var node = new Node(element, null, null);  //待插入节点的值
    //根节点判断
    if (root == null){
        root = node;
    }
    else{
        var current = root;  //设根节点为当前节点
        while(true){
            //待插入节点的数据小于当前节点,则将该节点设为原节点的左节点
            if (element < current.element){
                //若当前节点的左节点为null,则将新节点插入该位置,退出循环
                if (current.left == null){
                    current.left = node;
                    break;
                }
                current = current.left;
            }
            else if (element > current.element){ //往右节点方向放
                if (current.right == null){
                    current.right = node;
                    break;
                }
                current = current.right;
            }
            else { //相等,替换
                current.element = element;
                return;
            }
        }
    }
    size++;
}
this.insertBST = insertBST;  //插入元素
  1. 二叉查找树的遍历,遍历有三种方式:中序、前序、后序

(1)中序指以升序的方式遍历所有节点,即以从最小到最大的顺序访问所有节点;

(2)前序是指先访问根节点,再以同样的方式访问左子树和右子树;

(3)后序指的是先访问叶子节点,再从左子树到右子树,最后到根节点。

  • 遍历走势分析图:
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
遍历代码:

//二叉树中序遍历:以升序方式访问二叉树中所有节点
function inOrder(){
    return inOrderByNode(root);  //用来定义我们对遍历到的每个节点进行的操作
}
function inOrderByNode(node){
    if (node){  //判断以参数形式传入的节点是否存在,这是停止递归继续执行的判断条件---递归算法的基本条件
        var str = "";
        str += inOrderByNode(node.left);
        str += node.element + ", ";
        str += inOrderByNode(node.right);
        return str;
    }
    return "";
}
//前序遍历:先访问根节点,再访问左子树和右子树
function preOrder(){
    return preOrderByNode(root);
}
function preOrderByNode(node){
    if (node){
        var str = '';
        str += node.element + ", "; //先访问根节点
        str += preOrderByNode(node.left); //再访问左子树
        str += preOrderByNode(node.right); //再访问右子树
        return str;
    }
    return "";
}
//后序遍历:先访问叶子节点,再左子树,再右子树,再到根节点
function postOrder(){
    return postOrderByNode(root);
}
function postOrderByNode(node){
    if (node){
        var str = "";
        str += postOrderByNode(node.left);
        str += postOrderByNode(node.right);
        str += node.element + ", ";
        return str;
    }
    return "";
}
this.inOrder = inOrder;      //中序遍历
this.preOrder = preOrder;    //前序遍历
this.postOrder = postOrder;  //后序遍历
  1. 查找二叉查找树的最大值、最小值、是否存在某个值

(1)最大值:较大的值都是在右子树上,则最大值一定是在右子树的最后一个节点上;

(2)最小值:较小的值都是在左子树上,则最小值一定在左子树的最后一个节点上;

(3)是否存在某个值:遍历查找

//查找最小值:因为较小的值都在左边,所以最小值一定是左子树的最后一个节点
function getMin(){
    var minNode = getMinNode(root);
    if (minNode) {
        return minNode.element;
    }
    return null;
}
//查找最小节点
function getMinNode(node){
    var current = node;
    while(current){
        if (current.left == null){
            return current;
        }
        current = current.left;
    }
    return null;
}
//查找最大值:因为较大的值都在右边,所以最大值一定是在右子树的最后一个节点
function getMax(){
    var maxNode = getMaxNode(root);
    if (maxNode){
        return maxNode.element;
    }
    return null;
}
//查找最大节点
function getMaxNode(node){
    var current = node;
    while(current){
        if (current.right == null){
            return current;
        }
        current = current.right;
    }
    return null;
}
//查找指定值,是否存在这个元素
function isExist(element){
    var current = root;
    while(current){
        if (element < current.element){   //左子树寻找
            current = current.left;
        }
        else if (element > current.element){   //右子树寻找
            current = current.right;
        }
        else{   //存在
            return true;
        }
    }
    return false;
}
this.getMin = getMin;        //获取最小元素
this.getMax = getMax;        //获取最大值
this.isExist = isExist;      //是否存在某个元素
  1. 删除二叉查找树中的指定元素
function remove(element){
    root = removeNode(root, element);
}
function removeNode(node, element){
    //如果正在检测的节点是null,说明键不存在于树中,返回null
    if (node == null) {
        return null;
    }
    //如果要找的键比当前节点的值小,就沿着树的左边找下一个节点
    if(element < node.element){
        //更新节点左指针的值
        node.left = removeNode(node.left, element);
        //返回更新后的节点
        return node;
    }
    //如果要找的键比当前节点的值大,就沿着树的右边找下一个节点
    else if(element > node.element){
        node.right = removeNode(node.right, element);
        return node;
    }
    //如果要找的键element与node.element相等,需处理三种情况
    else{
        //第一种情况:——一个叶节点
        if(node.left == null&&node.right == null){
            //这个节点没有任何子节点,但它有一个父节点,需要通过返回null来将对应的父节点指针赋予null值
            return null;
        }
        //第二种情况:——一个只有一个子节点的节点
        if (node.left == null){  //node没有左子树
            return node.right;
        }
        else if (node.right == null){   //node没有右子树
            return node.left;
        }
        //第三种情况:——一个有两个子节点的节点
        /**
        * node有左子树和右子树,这个时候要找出最接近node节点值的节点
        * 1、如果找出比node节点的element稍大的节点,则从node右节点的最小节点
        * 2、如果找出比node节点的element稍小的节点,则从node左节点的最大节点
        */
        //当找到需要移除的节点后,需找到它右边子树中最小的节点
        var minNode = getMinNode(node.right);
        //用它右侧子树中最小节点的键去更新这个节点的值(即改变了这个节点的键,也就是说它被移除了)
        node.element = minNode.element;
        //继续把右侧子树中的最小节点移除,毕竟它已经被移至要移除的节点的位置
        node.right = removeNode(node.right, minNode.element);
        //向它的父节点返回更新后节点的引用
        return node;
    }
}
this.remove = remove;       //删除某个元素

测试代码:

var bst = new BinarySearchTree();
bst.insertBST(23);
bst.insertBST(45);
bst.insertBST(16);
bst.insertBST(37);
bst.insertBST(3);
bst.insertBST(99);
bst.insertBST(22);

console.log("\n.......start test......");
console.log("中序遍历:", bst.inOrder());
console.log("前序遍历:", bst.preOrder());
console.log("后序遍历:", bst.postOrder());

console.log("最小值:", bst.getMin());
console.log("最大值:", bst.getMax());
console.log("是否存在100:", bst.isExist(100));
console.log("是否存在99:", bst.isExist(99));

//删除元素
bst.remove(37);
console.log("删除37后中序遍历为:", bst.inOrder());
console.log("删除37后前序遍历为:", bst.preOrder());
console.log("删除37后后序遍历为:", bst.postOrder());

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值