数据结构(王道笔记)——二叉树

目录

一、介绍

1、二叉树

2、满二叉树

3、完全二叉树

4、二叉排序树

5、平衡二叉树

6、扩充二叉树

二、二叉树的实现及遍历

1、存储结构

2、前序遍历

3、中序遍历

4、后序遍历

5、层次遍历

三、由遍历序列构造二叉树

1、从前序与中序遍历序列构造二叉树

2、从中序与后序遍历序列构造二叉树 

四、线索二叉树


一、介绍

1、二叉树

特点:每个节点至多只有两棵子树,且子树有左右之分、次序不能任意颠倒。(有序树

与度为2的有序树的区别

  • 度为2的有序树至少3给节点
  • 度为2的有序树 只有一棵子树时不区分左右,而二叉树一定先有左子树

性质

  • n_0=n_2+1(度为0的节点数=度为2的节点数+1)
  • 前 i 层至多有2^i-1个节点,第 i 层至多有2^{i-1}个节点

2、满二叉树

  • 满树、度均为2
  • 从1开始按层编号,则 i 的左孩子为 2i ,右孩子为2i+1

3、完全二叉树

高为h的二叉树,且其每个节点按层编号 均与满二叉树的对应编号一致。(即和满二叉树相比,只最后一层不是满的。

  • 若有度1的节点,则只有1个(有1个左孩子)。
  • 从1开始按层编号,则从第一个度为1或0的节点开始,后面均为叶子节点。
  • 节点为偶数时才存在度为1的节点。

4、二叉排序树

左子树所有节点<根节点<右子树所有节点

5、平衡二叉树

树上任一结点的左子树和右子树的深度之差不超过1。

6、扩充二叉树

在原二叉树的空子树位置添加空树叶所形成的二叉树。结点度均为2。

外节点:扩充二叉树中的空树叶节结点。

内节点:非空结点。

外路径长度:扩充二叉树中所有外部结点到根结点的路径长度之和。

内路径长度:扩充二叉树中所有内部结点到根路径的路径长度之后和。

二、二叉树的实现及遍历

1、存储结构

顺序存储结构:用数组下标 代表 满二叉树按层的编号,0表示节点不存在。(满二叉树和完全二叉树比较适合

链式存储结构:数据域+左、右指针域。(含有n个节点的二叉链表中,含有n+1个空指针域)

  public class TreeNode {
      int val;
      TreeNode left;
      TreeNode right;
      TreeNode() {}
      TreeNode(int val) { this.val = val; }
      TreeNode(int val, TreeNode left, TreeNode right) {
          this.val = val;
          this.left = left;
          this.right = right;
      }
  }

2、前序遍历

先根节点,再左子树,再右子树。

递归前序遍历:

public void preorderTraversal(TreeNode root) {
    if (root!=null){
        System.out.print(root.val);//根
        preorderTraversal(root.left);
        preorderTraversal(root.right);
    }
}

非递归前序遍历:

    /**
     *@MethodName preorderTraversal
     *@Description TODO 144. 二叉树的前序遍历 https://leetcode.cn/problems/binary-tree-preorder-traversal/
     */
    public List<Integer> preorderTraversal(TreeNode root) {
        TreeNode now=root;
        List<Integer> result=new ArrayList<>();
        Stack<TreeNode> stack=new Stack<>();
        while (!stack.empty()||now!=null) {
            //访问当前元素,然后一直向左走,直到当前节点为null,就出栈其双亲
            if (now!=null){
                result.add(now.val);
                stack.push(now);
                now=now.left;
            }else {
                //栈顶元素出栈,确定其右子树情况
                now=stack.pop().right;
            }
        }
        return result;
    }

3、中序遍历

先左子树,再根节点,再右子树。

递归中序遍历

public void inorderTraversal(TreeNode root) {
    if (root!=null){
        inorderTraversal(root.left);
        System.out.print(root.val);//根
        inorderTraversal(root.right);
    }
}

非递归中序遍历

/**
     *@MethodName inorderTraversal
     *@Description TODO 94. 二叉树的中序遍历 https://leetcode.cn/problems/binary-tree-inorder-traversal/
     */
public List<Integer> inorderTraversal(TreeNode root) {
    TreeNode now=root;
    List<Integer> result=new ArrayList<>();
    Stack<TreeNode> stack=new Stack<>();
    while (!stack.empty()||now!=null) {
        //当前元素一致向左走,直到当前节点为null,就出栈其双亲
        if (now!=null){
            stack.push(now);
            now=now.left;
        }else {
            //栈顶元素出栈,并访问,确定其右子树情况
            TreeNode temp=stack.pop();
            result.add(temp.val);
            now=temp.right;
        }
    }
    return result;
}

前序序列和中序序列的关系相当于以前序序列为入栈次序,以中序序列为出栈次序。也就是说前序序列的出栈序列个数C^{2n}_n/(n+1)=其可能的不同二叉树个数。

4、后序遍历

先左子树,再右子树,再根节点。

递归中后序遍历

public void postorderTraversal(TreeNode root) {
    if (root!=null){
        postorderTraversal(root.left);
        postorderTraversal(root.right);
        System.out.print(root.val);//根
    }
}

非递归后序遍历

/**
     *@MethodName postorderTraversal
     *@Description TODO 145. 二叉树的后序遍历 https://leetcode.cn/problems/binary-tree-postorder-traversal/
     */
public List<Integer> postorderTraversal(TreeNode root) {
    //r 上次访问的对象
    TreeNode now=root,r=null;
    List<Integer> result=new ArrayList<>();
    Stack<TreeNode> stack=new Stack<>();
    while (!stack.empty()||now!=null) {
        //当前元素一直向左走,直到当前节点为null,就找其双亲右孩子
        if (now!=null){
            stack.push(now);
            now=now.left;
        }else {
            //栈顶元素不出栈,确定其右子树情况,
            //如果右子树为空,则出栈并访问;
            //如果右子树刚被访问过说明其左子树之前已进栈、并再出栈被访问了,此时左右子树都被访问,则出栈并访问
            now=stack.peek();
            if (now.right!=null&&now.right!=r){
                now=now.right;
            }else {
                //出栈、访问
                result.add(stack.pop().val);
                //记录
                r=now;
                now=null;
            }
        }
    }
    return result;
}

5、层次遍历

使用队列

/**
     *@MethodName levelOrder
     *@Description TODO 102. 二叉树的层序遍历 https://leetcode.cn/problems/binary-tree-level-order-traversal/
     */
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result=new ArrayList<>();
        Queue<TreeNode> queue=new ArrayDeque<>();

        int count=1;//每行节点的双亲数
        if(root!=null){
            queue.add(root);
        }
        while (!queue.isEmpty()) {
            List<Integer> temp=new ArrayList<>();
            for (int i = 0; i < count; i++) {
                TreeNode head=queue.remove();
                temp.add(head.val);
                if (head.left!=null){
                    queue.add(head.left);
                }
                if (head.right!=null){
                    queue.add(head.right);
                }
            }
            result.add(temp);
            count=queue.size();
        }
        return result;
    }

三、由遍历序列构造二叉树

1、从前序与中序遍历序列构造二叉树

非递归:


    /**
     *@MethodName buildTree
     *@Description TODO 105. 从前序与中序遍历序列构造二叉树 https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
     */
    public TreeNode buildTree(int[] preorder, int[] inorder) {

        if (preorder == null || preorder.length == 0) {
            return null;
        }
        TreeNode root=new TreeNode(preorder[0]);
        //用栈存储未确定右孩子的节点
        Stack<TreeNode> stack=new Stack<>();
        stack.push(root);
        int index=0;//中序 的指针,从左往右扫描元素的顺序 恰好是 前序做到最左端后的回序迭代。

        //流程:识别左孩子->入栈,直到没有左孩子,按一步步迭代回去 出栈 找右孩子 再迭代
        //从左开始扫描前序,如果一个节点不是它前一个结点的左孩子,那一定是它前面某个结点的右孩子(因为前面所有结点都确定了左孩子)
        //  且一定是离它最近的有右孩子的结点。
        for (int i = 1; i < preorder.length; i++) {
            TreeNode parent = stack.peek();

            //根据当前扫描的中序的结点 和 前序是否相等,判断是否前序是否遍历到了当前最左端。
            if (preorder[i - 1] != inorder[index]) {//不相等表示没到最左端,要一直向左构建左子树,相等的那个也要构建进去、所以是i-1
                parent.left = new TreeNode(preorder[i]);
                stack.push(parent.left);
            } else {//相等表示到了最左端,而没有右节点出现前,中序顺序和前序的回溯即栈中顺序 相同
                //找到当前结点(前序到达最左端后的下一个结点,是栈中第一个有右节点的 结点的右节点 )在中序中的位置
                while (!stack.empty() && stack.peek().val == inorder[index]) {
                    parent = stack.pop();
                    index++;
                }
                //找到后
                parent.right = new TreeNode(preorder[i]);
                stack.push(parent.right);
            }
        }
        return root;
    }

递归:

//left=0,riht=inorder.length-1,pos=0
public TreeNode buildTree(int[] preorder, int[] inorder, int left, int right ,int pos){
	if(pos>=preorder.lenth)
  	return null;
	if(left==right){
  	return new TreeNode(mid[left]);
  }
	TreeNode node=null;
	for(int i=left;i<=riht;i++){
  	if(preorder[pos]==inorder[i]);{
    	node=new TreeNode(inorder[i]);
    	node.left=buildTree(preorder,inorder,left,i-1,++pos);
    	node.right=buildTree(preorder,inorder,i+1,right,++pos);
    	break;
    }
	}
	return node;
}

2、从中序与后序遍历序列构造二叉树 

非递归:

    /**
     *@MethodName buildTree
     *@Description TODO 106. 从中序与后序遍历序列构造二叉树 https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
     */
    public TreeNode buildTree2(int[] inorder, int[] postorder) {

        if (inorder == null || inorder.length == 0) {
            return null;
        }
        int n=postorder.length;
        TreeNode root=new TreeNode(postorder[n-1]);
        //用栈存储未确定左孩子的节点
        Stack<TreeNode> stack=new Stack<>();
        stack.push(root);
        int index=n-1;//中序 的指针,从左往右扫描元素的顺序 恰好是 前序做到最左端后的回序迭代。

        //流程:向右检索 识别右孩子->入栈,直到没有右孩子,按一步步迭代回去 出栈 找左孩子 入栈再迭代
        //从右开始扫描后序,如果一个节点不是它后一个结点的右孩子,那一定是它前面某个结点的左孩子(因为前面所有结点都确定了右孩子)
        //  且一定是离它最近的有左孩子的结点。
        for (int i = n-2; i >=0; i--) {
            TreeNode parent = stack.peek();

            //根据当前扫描的中序的结点 和 后序是否相等,判断是否前序是否遍历到了当前最左端。
            if (postorder[i+1] != inorder[index]) {//不相等表示没到最右端,要一直向右构建左子树,相等的那个也要构建进去、所以是i+1
                parent.right = new TreeNode(postorder[i]);
                stack.push(parent.right);
            } else {//相等表示到了最右端,而没有左节点出现前,中序顺序和后序的回溯即栈中顺序 相同
                //找到当前结点(后序从右向左扫描到达最右端后的下一个结点,是栈中第一个有左节点的 结点的左节点 )在中序中的位置
                while (!stack.empty() && stack.peek().val == inorder[index]) {
                    parent = stack.pop();
                    index--;
                }
                //找到后
                parent.left = new TreeNode(postorder[i]);
                stack.push(parent.left);
            }
        }
        return root;
    }

递归:

//left=0,riht=inorder.length-1,pos=postorder.length
public TreeNode buildTree(int[] postorder, int[] inorder, int left, int right ,int pos){
	if(pos<0)
  	return null;
	if(left==right){
  	return new TreeNode(mid[left]);
  }
	TreeNode node=null;
	for(int i=right;i>=left;i--){
  	if(preorder[pos]==inorder[i]);{
    	node=new TreeNode(inorder[i]);
    	node.left=buildTree(preorder,inorder,left,i-1,--pos);
    	node.right=buildTree(preorder,inorder,i+1,right,--pos);
    	break;
    }
	}
	return node;
}

四、线索二叉树

        遍历二叉树是以一定的规则将二叉树中的结点排列成一个线性序列,从而得到几种遍历序 列,使得该序列中的每个结点(第一个和最后一个结点除外)都有一个直接前驱和直接后继。

        n个结点的二叉树有n+1个空指针。利用这些空指针存放指向其前驱或后记的指针。

规定:若无左子树,令 lchild指向其前驱结点;若无右子树,.令rchild指向其后继结点。 还需增加两个标志域标识指针域是指向左(右)孩子还是指向前驱(后继)。

lchild

ltag(0表存储左孩子,1表前驱)

data

rtag(0表存储右孩子,1表后继)

rchild

引入线索二叉树生是为了加快查找结点前驱和后继的速度

本文概念性描述及图示均来自王道考研数据结构一书

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SS上善

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值