• 二叉树的常见操作


    二叉树的常见操作

    注:二叉树的结构如下:

    typedef char BinaryTreeDataType;
    typedef struct BinaryTreeNode
    {
    	struct BinaryTreeNode* left;
    	struct BinaryTreeNode* right;
    	BinaryTreeDataType data;
    }BTNode;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    以下所有操作都以下面的树为例:

    1. 求各类节点个数

    1.1. 求二叉树的节点个数

    错误写法1

    int TreeSize(BTNode* root)
    {
    	static int size = 0;		//定义静态变量(也可以是全局变量),确保每一次调用函数size不会清零
    	if (!root)
    		return size;
    	//利用前序遍历的方式计算总结点数
    	size++;					
    	TreeSize(root->left);
    	TreeSize(root->right);
    	return size;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    计算一棵树时好像结果也没有问题:

    但计算两棵树的总结点时,却会出现以下情况:

    我们可以看到B这棵树的节点数确实是3,但A这棵树的节点怎么就为8了呢。原来,我们将size定义为了静态变量,当计算完B这棵树的节点再调用函数计算A树的结点树,size的大小已经为3了,这样最后得到的size就为8,显然错误。

    错误写法2

    那,我们将size变为全局变量并每次调用函数前将size归零不就行了吗?

    int size = 0;
    int TreeSize(BTNode* root)
    {
       if (!root)
       	return size;
       size++;
       TreeSize(root->left);
       TreeSize(root->right);
       return size;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以得到如下结果:

    这么做好像是可以,但如果考虑到多线程的情况,如果两个线程同时调用这个函数,那size的值必定会被改变,因此将size定义为静态变量是行不通的

    正确写法

    遍历思想
    void TreeSize(BTNode* root,int *size)		//因为要对size的值进行修改,因此要传地址
    {
       if (!root)
       	return;
       (*size)++;
       TreeSize(root->left,size);
       TreeSize(root->right,size);
       return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    可以发现,结果正确:

    分治思想
    • 将大问题分解成多个小问题。举个例子:树A的左子树是树B,右子树是树E,此时size为3,树B的左子树是树C,右子树是树D,此时size为5,C、D、E的左右子树都为空树,因此size为5

    • 采用了后序遍历的思想,先求左右子树的节点个数,再求根节点的节点个数

    int TreeSize(BTNode* root)
    {
    	return  root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
        //如果为空树,则返回0,如果不为空树,则先对左子树进行递归操作,再对右子树进行递归操作,递归完成后,返回1,因为这棵树不是空树
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    基本过程:

    递归分解图

    1.2 求二叉树叶子节点的个数

    叶子节点即度为零的节点,满足的条件即:

    assert(root);
    root->left == NULL && root->right == NULL;
    
    • 1
    • 2

    知道了这一基本常识,我们就可以利用后序遍历的思想来求得一棵二叉树叶子节点的个数了:

    int BinaryTreeLeafSize(BTNode* root)
    {
    	if (root == NULL)
    		return 0;
    
    	return (root->left == NULL && root->right == NULL) ? 1 : BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    基本过程:

    1.3 求第k层节点的个数

    ​ 我们将整数k也纳入函数形参,则函数原型为:

    int BinaryTreeLevelKSize(BTNode* root, int k)
    
    • 1

    这里的第k层,即相较于根节点的第k层;即相较于第二层的(k-1)层;即相较于第三层的(k-2)层;即相较于第n层的(k-n+1)层,n为当前的层数。因此,当k-n+1 = 1,即k = n时,第k层节点的个数就是第n层节点的个数。

    所以,要得到第k层节点的个数,只需要利用递归的思想,每一次递归都将k减一,当k == 1时,即到达第k层,节点数加1。

    int BinaryTreeLevelKSize(BTNode* root, int k)
    {
    	if (root == NULL)
    		return 0;
    
    	if (k == 1)
    		return 1;
    
    	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2. 通过前序遍历序列和#号创建二叉树

    题录描述:

    编写一个函数,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树。例如如下的先序遍历字符串:ABC##DE#G##F###其中“#”表示空格,空格字符代表空树NULL

    由于要利用一个字符串数组来构造一棵二叉树,为了能够方便读取数组的元素,作为函数参数,除了要将字符串传入,还应该传入一个指针,方便定位数组位置:

    BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
    
    • 1

    这里我们要用前序遍历的思想,先创建二叉树的根节点,再递归创建二叉树的左右子树:

    BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
    {
    	if (a[*pi] == '#')
    	{	
    		(*pi)++;
    		return NULL;
    	}
    
        //创建节点
    	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
    	if (NULL == root)
    	{
    		perror("malloc");
    		exit(-1);
    	}
    
    	root->data = a[*pi];
    	(*pi)++;
    	root->left = BinaryTreeCreate(a, pi);
    	root->right = BinaryTreeCreate(a, pi);
    
    	return root;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    基本过程:

    3. 查找二叉树值为x的节点,并返回该节点

    利用前序遍历的思想,先判断根节点的的值是否为x,如果是,则直接返回根节点即可,否则再遍历左右子树继续查找

    需要注意,利用递归查找左右子树是否有满足条件的节点时,应该对返回值进行记录,如果找到了直接返回找到的节点即可,不用再继续遍历。

    // 二叉树查找值为x的节点
    BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
    {
        //先判断根节点
    	if (root == NULL)
    		return NULL;
    	
    	if (root->data == x)
    		return root;
    
    	BTNode* ret = NULL;	//用来记录返回值
        
        //判断左子树是否有符合条件的节点
    	ret = BinaryTreeFind(root->left, x);
    	if (ret)	//如果找到了,直接返回
    		return ret;
        
        //判断右子树是否有符合条件的节点
    	ret = BinaryTreeFind(root->right, x);
    	if (ret)	//如果找到了,直接返回
    		return ret;
    	
        //如果都没找到,那么返回空
        return NULL;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    4. 判断一棵树是否为完全二叉树

    我们先来看一棵完全二叉树和一棵非完全二叉树:

    我们再来看看这两棵树的层序遍历序列:

    完全二叉树:A B E C D NULL NULL NULL NULL NULL NULL

    非完全二叉树:A B E NULL D NULL NULL NULL NULL

    可以发现完全二叉树的层序遍历序列的空树都是连续的,而非完全二叉树的层序遍历序列的空树不是连续的,我们可以利用这一性质来判断一棵树是不是完全二叉树

    注:如果对于二叉树的层序遍历操作不太了解,建议先看看👉二叉树层序遍历

    /*
    	QueueInit 表示初始化队列
    	QueuePush 表示入队
    	QueuePop 表示出队
    	QueueEmpty 表示判断队列是否为空,如果为空返回true
    	QueueFront 表示返回队头元素
    */
    bool BinaryTreeComplete(BTNode* root)
    {
    	if (root == NULL)
    		return true;
    
    	Queue* q = (Queue*)malloc(sizeof(Queue));
    	QueueInit(q);
    
    	QueuePush(q, root);
    
        //层序遍历队列的节点
    	while (!QueueEmpty(q))
    	{
    		BTNode* front = QueueFront(q);
    		QueuePop(q);
    		
            //如果此时队头元素为空,那么就可以退出循环,看队列剩余元素是否含有非空节点
    		if (front == NULL)
    			break;
    
    		QueuePush(q, front->left);
    		QueuePush(q, front->right);
    	}
    	
        //看队列剩余元素是否含有非空节点
    	while (!QueueEmpty(q))
    	{
    		BTNode* front = QueueFront(q);
    		QueuePop(q);
    		
         	//如果含有非空节点,就说明不是完全二叉树
    		if (front != NULL)
    		{
                //销毁队列
    			QueueDestroy(q);
    
    			return false;
    		}
    	}
    
    	QueueDestroy(q);
    
    	return true;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
  • 相关阅读:
    公开的免费API集锦
    【Vue】defineProperty与数据代理
    Python爬虫第一章(图片爬取与API接口爬取)
    如何让 Go 反射变快,你学会了吗?
    [量化投资-学习笔记006]Python+TDengine从零开始搭建量化分析平台-MACD
    kotlin中函数作为参数和函数作为返回值实例练习
    深度学习进度显示神器:tqdm详解
    日志管理:Syslog日志采集
    Python技能树——进阶语法讲解(2)
    kubernetes(K8S)学习笔记P1:基本概念和架构
  • 原文地址:https://blog.csdn.net/l315225/article/details/133470259