首页 > 其他分享 >数据结构之二叉树

数据结构之二叉树

时间:2023-04-01 10:08:56浏览次数:52  
标签:结点 遍历 return 二叉树 数据结构 root 节点

树是一种非线性数据结构,是由n (n>=0)个有限节点组成的一个具有层次关系的集合。树的逻辑结构看起来像一棵倒挂的树,根朝上,叶子朝下。

树一般是递归定义的,每一棵树都可以认为是由根和其子树构成,且各个子树不相交


树的相关概念

节点的度:一个节点含有的子树的个数称为该节点的度;

叶节点或终端节点:度为 0 的节点称为叶节点;

双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;

孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点;

兄弟节点:具有相同父节点的节点互称为兄弟节点;

树的度:一棵树中,最大的节点的度称为树的度;

节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;

树的高度或深度:树中节点的最大层次;

堂兄弟节点:双亲在同一层的节点互为堂兄弟

节点的祖先:从根到该节点所经分支上的所有节点

子孙:以某节点为根的子树中任一节点都称为该节点的子孙

森林:由 m(m>0)棵互不相交的树的集合称为森林


树的表示

树最常用“左孩子右兄弟表示法”表示。在每个结点中定义两个指针,其中一个指针指向该结点的第一个孩子结点,另一个指针指向该节点右侧的第一个兄弟结点。这种表示方法可以表示所有结点个数的树。

数据结构之二叉树_顺序结构

树的实际应用

树一般用来表示文件系统的目录结构

数据结构之二叉树_顺序结构_02

二叉树

二叉树是一棵树,其中每个节点都不能有多于两个的孩子节点,即二叉树节点的度在 [0, 2] 之间;二叉树是有序树,二叉树的左、右孩子次序不能颠倒。

二叉树具有以下性质

  • 一棵有 n 个结点的二叉树有 n - 1 条边;
  • 度为 0 的结点数总比度为 2 的结点数多 1,即 n0 = n2 + 1
  • 若规定根结点的高度为 1,则一棵非空二叉树第 i 层的节点个数最多为2^(i - 1)


满二叉树和完全二叉树

一个二叉树,如果每一层的结点数都达到最大值,则这个二叉树就是满二叉树。若规定根节点的高度为 1,则一棵高度为 h 的满二叉树的结点个数为

数据结构之二叉树_二叉树_03

完全二叉树是由满二叉树引出的,一棵深度为 h 的完全二叉树,其前 h - 1层都是满的,第 h 层的节点从左到右依次排列。一棵有 n 个结点的完全二叉树的高度为 logn 向上取整。

满二叉树是一种特殊的完全二叉树。

二叉树的顺序结构

普通的二叉树不适合用数组存储,因为会存在大量的空间浪费,而完全二叉树适合用数组存储,因为完全二叉树的非空节点是连续的。使用数组进行二叉树存储最常见的例子就是

数据结构之二叉树_二叉树_04

堆的概念和实现

堆是一种非传统的数据结构,它不止用来存储数据,而且使数据排列具有一定的性质和规律,使其便于在数据中找到最具优先权的一个。

堆的逻辑结构是一棵完全二叉树,物理结构是一个数组,根据成立条件,堆主要分为两种类型:

  • 大根堆,所有节点的值大于或等于其子节点的值
  • 小根堆,所有节点的值小于或等于其子节点的值

数据结构之二叉树_顺序结构_05

关于堆的具体实现,以及堆的性质和应用,请参考数据结构之优先队列。

二叉树的链式结构

一般的二叉树更适合用链式结构实现,在树结点(TreeNode)中定义两个指针 leftChild 和 rightChild,分别指向该节点的左孩子和右孩子,对应节点进行链接即构成二叉树的链式结构。

二叉树的遍历

二叉树是递归定义的,对二叉树的大部分操作也是基于递归

前、中、后序遍历

二叉树的前序遍历(PreOrder)是按照“根、左子树、右子树”顺序进行遍历堆的,二叉树的前序遍历属于深度优先搜索(DFS)

void PreOrder(BTNode* root)
{
  if(root == NULL) {
    printf("NULL ");
  	return;
  }
  printf("%d ", root->data);
  PreOrder(root->leftChild);
  PreOrder(root->rightChild);
}

二叉树的中序遍历(InOrder)是按照 “左子树、根、右子树” 顺序进行遍历的;二叉树的后序遍历(PostOrder)是按照 “左子树、右子树、根” 顺序进行遍历的。

层序遍历

对二叉树进行层序遍历的基本思路是“出上一层,带下一层”,这就需要用到队列。具体操作为:当出队头结点时,将队头结点的左非空孩子结点和右非空孩子结点入队列,直至队列为空。

二叉树的层序遍历属于广度优先搜索(BFS)

解决链式二叉树问题的基本思想

分治是解决二叉树问题的基本思想。当面对一个关于二叉树的问题时,要考虑将该问题拆解为规模较小的子问题,然后对二叉树进行递归操作。

关于二叉树的基本问题一般有:

  • 求二叉树的深度
  • 求二叉树的结点个数
  • 求二叉树叶子结点的个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL) {
		return 0;
	}

	if (root->leftChild == NULL && 
		root->rightChild == NULL) {
		return 1;
	}

	return BinaryTreeLeafSize(root->leftChild) +
		BinaryTreeLeafSize(root->rightChild);
}
  • 求二叉树某层的结点个数
int TreeKLevel(BTNode* root, int k)
{
	if (root == NULL) {
		return 0;
	}

	if (k == 1) {
		return 1;
	}

	return TreeKLevel(root->leftChild, k - 1) +
		TreeKLevel(root->rightChild, k - 1);
}
  • 查找二叉树中值为 x 的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataTypt x)
{
	if (root == NULL || root->data == x) {
		return root;
	}
	//向左子树寻找
	BTNode* tmp = BinaryTreeFind(root->leftChild, x);
	if (tmp) {
		return tmp;
	}
	//向右子树寻找
	tmp = BinaryTreeFind(root->rightChild, x);
	if (tmp) {
		return tmp;
	}
}
  • 判断单值二叉树
  • 判断相等二叉树
  • 判断对称二叉树
bool JudgeTree(struct TreeNode* lRoot, struct TreeNode* rRoot)
{
    if(lRoot == NULL && rRoot == NULL) {
        return true;
    }

    if(!lRoot || !rRoot) {
        return false;
    }

    if(lRoot->val != rRoot->val) {
        return false;
    }

    return JudgeTree(lRoot->left, rRoot->right) &&
            JudgeTree(lRoot->right, rRoot->left);
}

bool isSymmetric(struct TreeNode* root)
{
    if(root == NULL) {
        return true;
    }

    return JudgeTree(root->left, root->right);
}
  • 判断完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root != NULL) {
		QueuePush(&q, root);
	}

	BTNode* front = QueueFront(&q);
	while (front != NULL)
	{
		QueuePop(&q);
		QueuePush(&q, front->leftChild);
		QueuePush(&q, front->rightChild);
		front = QueueFront(&q);
	}

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front != NULL) {
			return false;
		}
	}

	return true;
}

二叉树的创建和销毁

二叉树可以由其中序遍历结果前序、后序遍历结果中的某一个结合,判断出其完整结构;或者根据前序带空遍历结果、中序遍历带空结果和后序带空遍历结果中的某一个判断出其完整结构。


从前序与中序遍历序列构造二叉树
class Solution
{
private:
    unordered_map<int, int> index;

private:
    TreeNode* RecBuildTree(vector<int>& preorder, vector<int>& inorder, 
    int pre_left, int pre_right, int in_left, int in_right)
    {
        if(pre_left > pre_right){
            //全部遍历完成,结束递进
            return nullptr;
        }

        int pre_root = pre_left;//前序中的根(的索引)
        int in_root = index[preorder[pre_root]];//获取中序中的根
        int left_size = in_root - in_left;//计算左树大小

        TreeNode* newRoot = new TreeNode(preorder[pre_root]);//构造根
        //递归构造左树
        newRoot->left = RecBuildTree(preorder, inorder, 
        pre_root + 1, pre_left + left_size, in_left, in_root - 1);
        //递归构造右树
        newRoot->right = RecBuildTree(preorder, inorder,
        pre_left + left_size + 1, pre_right, in_root + 1, in_right);

        return newRoot;
    }

public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
    {
        int n = preorder.size();
        for(int i = 0; i < n; i++){
            //构造哈希映射,便于在inorder中找出根
            index[inorder[i]] = i;
        }
        return RecBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
    }
};


从先序遍历序列构造二叉树
typedef struct TreeNode
{
    char data;
    struct TreeNode* leftChild;
    struct TreeNode* rightChild;
}TreeNode;

TreeNode* CreatTree(char* arr, int* pi)
{
    if(arr[*pi] == '#') {
        (*pi)++;
        return NULL;
    }

    TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
    assert(root);
    root->data = arr[(*pi)++];
    root->leftChild = CreatTree(arr, pi);
    root->rightChild = CreatTree(arr, pi);

    return root;
}

void InOrder(TreeNode* root)
{
    if(root == NULL) {
        return;
    }
    
    InOrder(root->leftChild);
    printf("%c ", root->data);
    InOrder(root->rightChild);
}

int main()
{
    char str[100] = { 0 };
    scanf("%s", str);
    int i = 0;
    TreeNode* root = CreatTree(str, &i);
    InOrder(root);
    printf("\n");
    return 0;
}
二叉树的销毁

为了避免销毁二叉树时造成结点丢失造成野指针或非法访问的情况,我们使用后序遍历销毁,先销毁根节点的左、右子树,最后释放根节点。

标签:结点,遍历,return,二叉树,数据结构,root,节点
From: https://blog.51cto.com/158SHI/6163413

相关文章

  • 数据结构:栈的进出
    进栈序列为1,2,3,4,进栈过程中可以出栈,则下列不可能的出栈序列是(C)A、1,2,3,4 B、2,3,1,4 C、3,1,2,4 D、4,3,2,1[========]栈是先进后出。如果4先出,那么就是全部入栈了,只有4321一种情况。如果3先出,那么4还没有入栈,此时栈内只有1,2,3,出栈必有3→2→1的顺序,4可以在3,2,......
  • 【数据结构】顺序表
    线性表的定义和特点线性表是具有相同特性的数据元素的一个有限序列$$(a_1,a_2,…a_(i-1),a_i,a_(i+1),…,a_n)$$线性表(LinearList):由n(n>=0)个数据元素(结点)$$a_1,a_2,…,a_n$$组成的有限序列其中数据元素的个数n定义为表的长度当n为0时称为空表将非空的线性表记作:$$a_1,a_2,…,a......
  • LeetCode 94 二叉树的中序遍历
    LeetCode|94.二叉树的中序遍历给定一个二叉树的根节点root,返回它的中序 遍历。示例1:输入:root=[1,null,2,3]输出:[1,3,2]示例2:输入:root=[]输出:[]示例3:输入:root=[1]输出:[1]提示:树中节点数目在范围[0,100]内-100<=Node.val<=100迭代实现:......
  • 第8章 数据结构算法专题二
    线索二叉树与哈夫曼树线索二叉树线索二叉树的概念采用某种方法遍历二叉树的结果是一个结点的线性序列。修改空链域改为存放指向结点的前驱和后继结点的地址。这样的指向该线性序列中的”前驱“和”后继“的指针,称作线索(thread)。创建线索的过程称为线索化。线索化的二叉......
  • 之子形打印二叉树
    classSolution{public:vector<vector<int>>res;voidbfs(TreeNode*root){queue<TreeNode*>q;q.push(root);intlevel=0;while(q.size()){intsize=q.size();......
  • 不分行从上往下打印二叉树
    classSolution{public:vector<int>res;voidbfs(TreeNode*root){queue<TreeNode*>q;q.push(root);while(q.size()){autop=q.front();q.pop();res.push_back(......
  • 上下翻转二叉树
    给定一个二叉树的根节点root,按照如下的方式上下翻转二叉树,并返回新的根节点。1、原来的左子节点变成新的根节点2、原来的根节点变成新的右子节点3、原来的右子节点变成新的左子节点上面的步骤都是逐层进行的,题目数据保证每个右节点都有一个同级节点(共享同一个父节点的左......
  • JZ7 重建二叉树
     JZ7 重建二叉树方法一:递归做法前序的第一个结点就是根节点,而后在中序中将根节点的位置找出,根节点的左边是左子树,右边是右子树,而后再带入前序中分析,形成迭代。/***Definitionforbinarytree*structTreeNode{*intval;*TreeNode*left;*Tree......
  • 跟着查老四学Python Day 3:数据结构与函数
    老猫:请您扮演一个python讲师,帮助一个没有代码经验的人自学python,以下是此前你设置的学习计划制定学习计划:将学习过程分为四个阶段,每个阶段关注不同的内容。第一周:掌握Python基础语法、数据类型、控制结构等。同时,学会如何在本地搭建Python开发环境。第二周:学习函数、模块、文件操......
  • LeetCode 559.二叉树的最大深度
    1.题目:给定一个N叉树,找到其最大深度。最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。N叉树输入按层序遍历序列化表示,每组子节点由空值分隔(请参见示例)。示例1:输入:root=[1,null,3,2,4,null,5,6]输出:3来源:力扣(LeetCode)链接:https://leetcode.cn/problems/maximum......