当前位置:   article > 正文

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

扩充二叉树

目录

一、介绍

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个空指针域)

  1. public class TreeNode {
  2. int val;
  3. TreeNode left;
  4. TreeNode right;
  5. TreeNode() {}
  6. TreeNode(int val) { this.val = val; }
  7. TreeNode(int val, TreeNode left, TreeNode right) {
  8. this.val = val;
  9. this.left = left;
  10. this.right = right;
  11. }
  12. }

2、前序遍历

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

递归前序遍历:

  1. public void preorderTraversal(TreeNode root) {
  2. if (root!=null){
  3. System.out.print(root.val);//根
  4. preorderTraversal(root.left);
  5. preorderTraversal(root.right);
  6. }
  7. }

非递归前序遍历:

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

3、中序遍历

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

递归中序遍历

  1. public void inorderTraversal(TreeNode root) {
  2. if (root!=null){
  3. inorderTraversal(root.left);
  4. System.out.print(root.val);//根
  5. inorderTraversal(root.right);
  6. }
  7. }

非递归中序遍历

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

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

4、后序遍历

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

递归中后序遍历

  1. public void postorderTraversal(TreeNode root) {
  2. if (root!=null){
  3. postorderTraversal(root.left);
  4. postorderTraversal(root.right);
  5. System.out.print(root.val);//根
  6. }
  7. }

非递归后序遍历

  1. /**
  2. *@MethodName postorderTraversal
  3. *@Description TODO 145. 二叉树的后序遍历 https://leetcode.cn/problems/binary-tree-postorder-traversal/
  4. */
  5. public List<Integer> postorderTraversal(TreeNode root) {
  6. //r 上次访问的对象
  7. TreeNode now=root,r=null;
  8. List<Integer> result=new ArrayList<>();
  9. Stack<TreeNode> stack=new Stack<>();
  10. while (!stack.empty()||now!=null) {
  11. //当前元素一直向左走,直到当前节点为null,就找其双亲右孩子
  12. if (now!=null){
  13. stack.push(now);
  14. now=now.left;
  15. }else {
  16. //栈顶元素不出栈,确定其右子树情况,
  17. //如果右子树为空,则出栈并访问;
  18. //如果右子树刚被访问过说明其左子树之前已进栈、并再出栈被访问了,此时左右子树都被访问,则出栈并访问
  19. now=stack.peek();
  20. if (now.right!=null&&now.right!=r){
  21. now=now.right;
  22. }else {
  23. //出栈、访问
  24. result.add(stack.pop().val);
  25. //记录
  26. r=now;
  27. now=null;
  28. }
  29. }
  30. }
  31. return result;
  32. }

5、层次遍历

使用队列

  1. /**
  2. *@MethodName levelOrder
  3. *@Description TODO 102. 二叉树的层序遍历 https://leetcode.cn/problems/binary-tree-level-order-traversal/
  4. */
  5. public List<List<Integer>> levelOrder(TreeNode root) {
  6. List<List<Integer>> result=new ArrayList<>();
  7. Queue<TreeNode> queue=new ArrayDeque<>();
  8. int count=1;//每行节点的双亲数
  9. if(root!=null){
  10. queue.add(root);
  11. }
  12. while (!queue.isEmpty()) {
  13. List<Integer> temp=new ArrayList<>();
  14. for (int i = 0; i < count; i++) {
  15. TreeNode head=queue.remove();
  16. temp.add(head.val);
  17. if (head.left!=null){
  18. queue.add(head.left);
  19. }
  20. if (head.right!=null){
  21. queue.add(head.right);
  22. }
  23. }
  24. result.add(temp);
  25. count=queue.size();
  26. }
  27. return result;
  28. }

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

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

非递归:

  1. /**
  2. *@MethodName buildTree
  3. *@Description TODO 105. 从前序与中序遍历序列构造二叉树 https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
  4. */
  5. public TreeNode buildTree(int[] preorder, int[] inorder) {
  6. if (preorder == null || preorder.length == 0) {
  7. return null;
  8. }
  9. TreeNode root=new TreeNode(preorder[0]);
  10. //用栈存储未确定右孩子的节点
  11. Stack<TreeNode> stack=new Stack<>();
  12. stack.push(root);
  13. int index=0;//中序 的指针,从左往右扫描元素的顺序 恰好是 前序做到最左端后的回序迭代。
  14. //流程:识别左孩子->入栈,直到没有左孩子,按一步步迭代回去 出栈 找右孩子 再迭代
  15. //从左开始扫描前序,如果一个节点不是它前一个结点的左孩子,那一定是它前面某个结点的右孩子(因为前面所有结点都确定了左孩子)
  16. // 且一定是离它最近的有右孩子的结点。
  17. for (int i = 1; i < preorder.length; i++) {
  18. TreeNode parent = stack.peek();
  19. //根据当前扫描的中序的结点 和 前序是否相等,判断是否前序是否遍历到了当前最左端。
  20. if (preorder[i - 1] != inorder[index]) {//不相等表示没到最左端,要一直向左构建左子树,相等的那个也要构建进去、所以是i-1
  21. parent.left = new TreeNode(preorder[i]);
  22. stack.push(parent.left);
  23. } else {//相等表示到了最左端,而没有右节点出现前,中序顺序和前序的回溯即栈中顺序 相同
  24. //找到当前结点(前序到达最左端后的下一个结点,是栈中第一个有右节点的 结点的右节点 )在中序中的位置
  25. while (!stack.empty() && stack.peek().val == inorder[index]) {
  26. parent = stack.pop();
  27. index++;
  28. }
  29. //找到后
  30. parent.right = new TreeNode(preorder[i]);
  31. stack.push(parent.right);
  32. }
  33. }
  34. return root;
  35. }

递归:

  1. //left=0,riht=inorder.length-1,pos=0
  2. public TreeNode buildTree(int[] preorder, int[] inorder, int left, int right ,int pos)
  3. if(pos>=preorder.lenth)
  4. return null;
  5. if(left==right){
  6. return new TreeNode(mid[left]);
  7. }
  8. TreeNode node=null;
  9. for(int i=left;i<=riht;i++){
  10. if(preorder[pos]==inorder[i]);{
  11. node=new TreeNode(inorder[i]);
  12. node.left=buildTree(preorder,inorder,left,i-1,++pos);
  13. node.right=buildTree(preorder,inorder,i+1,right,++pos);
  14. break;
  15. }
  16. }
  17. return node;

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

非递归:

  1. /**
  2. *@MethodName buildTree
  3. *@Description TODO 106. 从中序与后序遍历序列构造二叉树 https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
  4. */
  5. public TreeNode buildTree2(int[] inorder, int[] postorder) {
  6. if (inorder == null || inorder.length == 0) {
  7. return null;
  8. }
  9. int n=postorder.length;
  10. TreeNode root=new TreeNode(postorder[n-1]);
  11. //用栈存储未确定左孩子的节点
  12. Stack<TreeNode> stack=new Stack<>();
  13. stack.push(root);
  14. int index=n-1;//中序 的指针,从左往右扫描元素的顺序 恰好是 前序做到最左端后的回序迭代。
  15. //流程:向右检索 识别右孩子->入栈,直到没有右孩子,按一步步迭代回去 出栈 找左孩子 入栈再迭代
  16. //从右开始扫描后序,如果一个节点不是它后一个结点的右孩子,那一定是它前面某个结点的左孩子(因为前面所有结点都确定了右孩子)
  17. // 且一定是离它最近的有左孩子的结点。
  18. for (int i = n-2; i >=0; i--) {
  19. TreeNode parent = stack.peek();
  20. //根据当前扫描的中序的结点 和 后序是否相等,判断是否前序是否遍历到了当前最左端。
  21. if (postorder[i+1] != inorder[index]) {//不相等表示没到最右端,要一直向右构建左子树,相等的那个也要构建进去、所以是i+1
  22. parent.right = new TreeNode(postorder[i]);
  23. stack.push(parent.right);
  24. } else {//相等表示到了最右端,而没有左节点出现前,中序顺序和后序的回溯即栈中顺序 相同
  25. //找到当前结点(后序从右向左扫描到达最右端后的下一个结点,是栈中第一个有左节点的 结点的左节点 )在中序中的位置
  26. while (!stack.empty() && stack.peek().val == inorder[index]) {
  27. parent = stack.pop();
  28. index--;
  29. }
  30. //找到后
  31. parent.left = new TreeNode(postorder[i]);
  32. stack.push(parent.left);
  33. }
  34. }
  35. return root;
  36. }

递归:

  1. //left=0,riht=inorder.length-1,pos=postorder.length
  2. public TreeNode buildTree(int[] postorder, int[] inorder, int left, int right ,int pos)
  3. if(pos<0)
  4. return null;
  5. if(left==right){
  6. return new TreeNode(mid[left]);
  7. }
  8. TreeNode node=null;
  9. for(int i=right;i>=left;i--){
  10. if(preorder[pos]==inorder[i]);{
  11. node=new TreeNode(inorder[i]);
  12. node.left=buildTree(preorder,inorder,left,i-1,--pos);
  13. node.right=buildTree(preorder,inorder,i+1,right,--pos);
  14. break;
  15. }
  16. }
  17. return node;

四、线索二叉树

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

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

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

lchild

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

data

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

rchild

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

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

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/843662
推荐阅读
相关标签
  

闽ICP备14008679号