树与二叉树

简介(Introduction)

树是由 \(n(n \ge 0)\) 个有限节点组成一个具有层次关系的集合。
二叉树 是树形结构的一个重要类型,二叉树特点是每个节点最多只能有两棵子树,且有左右之分



描述(Description)

  • 树可大致分为 有根树无根树 两大类
  • 基本概念:
    • 父亲 \((Parent)\):从一个结点到根路径上的第二个结点 —— 根结点没有父结点
    • 祖先 \((Ancestor)\):对于一个结点,其父节点的父节点即为祖先节点
    • 儿子 \((Child)\):如一个节点 \(v\)\(u\) 的父亲,则 \(u\)\(v\) 的儿子
    • \((Degree)\)
      • 节点的度:一个节点含有的子节点的个数称为该节点的度
      • 树的度:一棵树中,最大的节点的度称为树的度
    • 深度 \((Depth)\):到根结点的路径上的边数
    • 树的高度 \((Height)\):所有结点的深度的最大值
    • 叶结点 \((Leaf)\):没有子结点的结点,度数为 \(0\) 的节点
    • 兄弟 \((Brother)\):同一个父亲的多个子结点互为兄弟
    • 子树 \((Subtree)\):由一个顶点(非根)以及所有后代构成的树
    • 森林 \((Forest)\):多棵互不相交的树的集合称为森林,一棵树也是森林


  • 二叉树的基本形态:空二叉树、单节点二叉树、左子树、右子树
  • 二叉树性质:
    1. 在非空二叉树中,第 \(i\) 层上至多有 \(2^{i - 1}\) 个结点
    2. 深度为 \(k\) 的二叉树至多有 \(2^{k} - 1\) 个结点
    3. 对任何一棵二叉树,若其叶子结点数为 \(n_0\) ,度为 \(2\) 的结点数为 \(n_2\) ,则 \(n_0 = n_2 + 1\)

    证明:
    假设:度数为 \(0\) 的节点为 \(n_0\),度数为 \(1\) 的节点为 \(n_1\),度数为 \(2\) 节点为 \(n_2\)
    即总点数 \(N = n_0 + n_1 + n_2\),总边数 \(E = N - 1\)
    根据点的度和边点关系得,\(E = 2 * n_2 + 1 * n_1 + 0 * n_0\)
    联立方程:\(n_0 + n_1 + n_2 - 1 = 2 * n_2 + 1 * n_1 + 0 * n_0\)
    即,\(n_0 - 1 = n_2 \cdots \cdots \cdots \cdots \cdots \cdots \cdots 证毕.\)

    1. \(n\) 个结点的完全二叉树深度为: \(\lfloor\log_2n\rfloor + 1\)
    2. 二叉树的堆式存储: 节点 \(x\) 的左儿子:\(2x\) ,右儿子:\(2x + 1\)


完全二叉树
   如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树,即——深度为 \(k\),有 \(n\) 个结点的二叉树,当且仅当其每个结点都与深度为 \(k\) 的满二叉树中编号从 \(1\)\(n\) 的结点一一对应

满二叉树
   如果二叉树中除了叶子结点,每个结点的度都为 \(2\) ,则此二叉树称为满二叉树,即——深度为 \(k\) 且有 \(2^k - 1\) 个结点

二叉排序树
   左子树上所有节点的关键字均 小于 根节点的关键字,右子树上所有节点的关键字 均大于等于 根节点的关键字;同时左子树和右子树又均是一棵二叉排序树

平衡二叉树
   树上任意一个节点的左子树和右子树的深度之差不超过 \(1\)

哈夫曼树
   带权路径最短的二叉树称为哈夫曼树或最优二叉树



示例(Example)


  • image

  • 二叉树
    image

  • 树的遍历
    image



代码(Code)

  • 二叉树存储方式

    • 顺序存储(静态数组)方法一:
      struct Tree {
      	int val;
      	int left, right;
      } tr[N];
      

    • 顺序存储(静态数组)方法二:
      /*
        设当前节点:i
        left = a[i >> 1];
        right = a[i >> 1 | 1];
        val = a[i];
      */
      int tr[N];
      

    • 动态(链式存储):
      struct Tree {
      	int val;
      	Tree *left, *right;
      
      	Tree() : left(NULL), right(NULL) {}
      	Tree(int _val) : left(NULL), right(NULL), val(_val) {}
      };
      

  • 建树

    // C++ Version
    
    Tree* build(int cnt) {  // 前序建树
    	if (!cnt) return NULL;
    	int val;
    	cin >> val;
    	Tree *root = new Tree(val);
    	root->left = build(-- cnt);
    	root->right = build(cnt);
    	return root;
    }
    

  • 二叉树遍历

    • 前序遍历: 先输出父节点,再遍历左子树和右子树
      // C++ Version
      
      void pre_order(Tree *root) {
      	if (!root) return;
      	printf("%d ", root -> val);  // 输出根节点
      	pre_order(root -> left);  // 递归左子树
      	pre_order(root -> right);  // 递归右子树
      }
      

    • 中序遍历: 先遍历左子树,再输出父节点,再遍历右子树
      // C++ Version
      
      void in_order(Tree *root) {
      	if (!root) return;
      	in_order(root -> left);  // 递归左子树
      	printf("%d ", root -> val);  // 输出根节点
      	in_order(root -> right);  // 递归右子树
      }
      

    • 后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点
      // C++ Version
      
      void post_order(Tree *root) {
      	if (!root) return;
      	post_order(root -> left);  // 递归左子树
      	post_order(root -> right);  // 递归右子树
      	printf("%d ", root -> val);  // 输出根节点
      }
      



应用(Application)




重建二叉树

输入一棵二叉树前序遍历和中序遍历的结果,请重建该二叉树。

  • 二叉树中每个节点的值都互不相同;
  • 输入的前序遍历和中序遍历一定合法;

示例:
image

输入:

preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]

输出:

[3,9,20,null,null,15,7]

数据范围:

\(1 \le preorder.length \le 3000\)

\(-3000 \le preorder[i], inorder[i] \le 3000\)

  • 题解:
    /**
     * Definition for a binary tree node.
     * struct TreeNode {
     *     int val;
     *     TreeNode *left;
     *     TreeNode *right;
     *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     * };
     */
    
    /*
    	图解:
    	pre: ______________________
    		 ^          ^         ^
    		 pl         x         pr
    
    	mid  ______________________
    		 ^          ^         ^ 
    		 ml        pos        mr   
    	得: (pos - 1) - ml = x - (pl + 1)
    	即, x = pos + pl - ml
     */
    class Solution {
    public:
    	vector<int> preorder, inorder;
    	unordered_map<int, int> rel;
    	TreeNode* buildTree(vector<int>& _preorder, vector<int>& _inorder) {
    		preorder = _preorder, inorder = _inorder;
    		int n = inorder.size();
    		for (int i = 0; i < n; i ++ ) rel[inorder[i]] = i;
    		return dfs(0, n - 1, 0, n - 1);
    	}
    
    	TreeNode* dfs(int pl, int pr, int ml, int mr) {
    		if (pl > pr) return NULL;
    		TreeNode* root = new TreeNode(preorder[pl]);
    		int pos = rel[root->val];
    		root->left = dfs(pl + 1, pos + pl - ml, ml, pos - 1);
    		root->right = dfs(pos + pl - ml + 1, pr , pos + 1, mr);
    		return root;
    	}
    };
    

Hint:

  • “中序遍历 + 先序遍历“ 或 ”中序遍历 + 后续遍历“,都可以确定一棵二叉树,
  • “先序遍历 + 后续遍历”无法确定一棵二叉树
posted @ 2023-05-02 17:55  FFex  阅读(6)  评论(0编辑  收藏  举报