二叉树的基本操作及层序遍历(附加一道OJ题)

目录

一.  二叉树的各种基本操作

1.求树的节点个数

2. 求叶子节点的个数

3.求树的高度

4.求第k层的节点数量

5. 求值x所在的节点

6. 销毁二叉树

二. 层历遍历与判断是否是完全二叉树

1. 层序遍历

2. 判断二叉树是否是完全二叉树

三. OJ题,根据输入的先序字符串还原树并中序输出


由于之前讲过二叉树的性质,概念等。这里就不赘述了

 

一.  二叉树的各种基本操作

用以下树来演示

1.求树的节点个数

利用递归,到节点D时,D的左右都是NULL,所以D返回给B的值为0+0+1,E节点亦是如此,此时B的返回值就是D的返回值加E的返回值+1

int TreeSize(Tree* root)
{
	if (root == NULL)
		return 0;
	
	return TreeSize(root->left) + TreeSize(root->right) + 1;
}
2. 求叶子节点的个数

与求树的节点类似,但不同的地方是只有到叶子才会计数,也就是说只有当前节点的左右孩子都为NULL才会返回1

int TreeLeafSize(Tree* root)
{
	if (root == NULL)
		return 0;
	if (root->left == NULL && root->right == NULL)
		return 1;

	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
3.求树的高度

我们将此问题分解开来就是比较左子树和右子树的高,即比较左右的高,高的+1返回即可

但要注意

return TreeHeight(root->left) > TreeHeight(root->right) ? TreeHeight(root->left)+1 : TreeHeight(root->right)+1;

直接递归会造成大量的重复计算,效率非常低,我们可以将左右子树的高记录下来,来实现记忆化搜索,省下大量时间 

int TreeHeight(Tree* root)
{
	if (root == NULL)
		return 0;

	int leftHeight = TreeHeight(root->left);
	int rightHeight = TreeHeight(root->right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;

	//效率太低了
	/*return TreeHeight(root->left) > TreeHeight(root->right) ? TreeHeight(root->left)+1 : TreeHeight(root->right)+1;*/
}
4.求第k层的节点数量

我个人认为与求叶子节点个数类似,递归到第k层返回1即可,如果当前节点到达不了根节点了就返回0

int TreeLevelKSize(Tree* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}
5. 求值x所在的节点

如果走到空就返回NULL,如果找到了就返回此节点,使用find记录往左找的值,如果没有找到(find为空)就往右找,找到了就一直返回就可以

Tree* TreeFind(Tree* root, int x)
{
	if (root == NULL)
		return NULL;
	if (root->a == x)
		return root;
	Tree* find = TreeFind(root->left, x);
	if (find == NULL)
		find = TreeFind(root->right, x);

	return find;

	//if (find==NULL||find->a != x)
	//	return NULL;
	//else
	//	return find; 
}
6. 销毁二叉树

类似于后序遍历,先销毁左右子树最后再销毁根,因为通过根才能找到左右子树

void TreeDestory(Tree* root)
{
	if (root == NULL)
		return;
	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);

}

二. 层历遍历与判断是否是完全二叉树

以下两种功能都需要队列辅助实现,使用到的函数如下

typedef int TreeType;
typedef struct Tree
{
	TreeType a;
	struct Tree* left;
	struct Tree* right;
}Tree;

typedef Tree* QueueType;

typedef struct QueueNode
{
	struct QueueNode* next;
	QueueType val;

}QNode,*pQNode;

typedef struct Queue
{
	pQNode head;
	pQNode tail;
	int size;
}Queue;
void QueueInit(Queue* pq)
{
	pq->head = NULL;
	pq->tail = NULL;
	pq->size = 0;
}

void QueuePush(Queue* pq, QueueType x)
{
	pQNode tmp = (pQNode)malloc(sizeof(QNode));
	if (tmp == NULL)
	{
		perror("malloc:fail\n");
		return;
	}
	tmp->next = NULL;
	tmp->val = x;
	if (pq->tail == NULL)
	{

		pq->head = tmp;
		pq->tail = tmp;
	}
	else
	{
		pq->tail->next = tmp;
		pq->tail = tmp;
	}
	pq->size++;
}

QueueType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head);

	return pq->head->val;
}

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head);//要是没有节点就报错
	pQNode tmp = pq->head;
	if (pq->head == pq->tail)//如果只有一个节点
		pq->tail = NULL;
	pq->head = pq->head->next;
	free(tmp);
	tmp = NULL;
	pq->size--;
}

bool QueueEmpty(Queue* pq)
{
	return pq->head == NULL && pq->tail == NULL;
}

void QueueDestroy(Queue* pq)
{
	assert(pq);
	while (pq->head)
	{
		pQNode tmp = pq->head;
		pq->head = pq->head->next;
		free(tmp);
		tmp = NULL;
	}
	pq->head = NULL;
	pq->tail = NULL;
	pq->size = 0;
}
1. 层序遍历

在外面先将根入队,进入循环后用newq记录队头的值将队头的左右孩子入队再将根出队,左右孩子为空时不入队。即可完成我们的想法

void TreeLevelOrder(Tree* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		Tree* newq = QueueFront(&q);
		QueuePop(&q);

		printf("%d  ",newq ->a);
		if (newq->left != NULL)
		{
			QueuePush(&q, newq->left);
		}
		if (newq->right != NULL)
		{
			QueuePush(&q, newq->right);
		}

	}
	QueueDestroy(&q);
}
2. 判断二叉树是否是完全二叉树

1.层序遍历走,但是NULL也进队列
2.队列走到一个空节点时,开始判断,如果后面全是空就是完全二叉树后面有节点就不是完全二叉树

遇到空时后面还有非空没进队列。假设空在树的第三层的第三个,没进去的一定是第五层并不会影响对二叉树的判断,因为第四层已经进队列的,NULL后面有节点说明是非完全二叉树。如下图所示

假设队列此时的队头为红圆圈出的NULL,G虽然没进队列但队列后有F节点证明其不是完全二叉树


后面的非空一定有前面非空的孩子 ,
当层序遍历出到空时,前面的非空都出队列了,那他们的孩子一定进队列了,如果他们的孩子不是空(即遍历到空,队列后还有非空时)那这个二叉树就不是完全二叉树

即上图中的节点F是节点E的孩子

不要忘记销毁队列

bool TreeCompelete(Tree* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		Tree* newq = QueueFront(&q);
		QueuePop(&q);

		if (newq == NULL)
		{
			while (!QueueEmpty(&q))
			{
				if (QueueFront(&q) != NULL)//如果有非空就不是完全二叉树
				{
					QueueDestroy(&q);
					return false;
				}
				QueuePop(&q);
			}
			break;
		}
		
		QueuePush(&q, newq->left);
		QueuePush(&q, newq->right);
	}
	QueueDestroy(&q);
	return true;
}

三. OJ题,根据输入的先序字符串还原树并中序输出

题目描述

编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。 例如如下的先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。

 根据先序遍历还原二叉树,我们给函数传两个参数,字符数组和下标(下标要用指针,因为要改变值

例如输入:abc##de#g##f###

构造树的函数运行过程大致如下

malloc节点后将其赋值为a,并将数组下标++,进入递归此时ch[si]指向b,开辟空间赋值,再往左递归开辟空间赋值为c,c给左子树遇见'#' (数组下标++)c的左子树接受返回值NULL,c给右子树赋值遇见'#'(数组下标++),c的右子树接受返回值为NULL,b的左子树接受返回值为c节点,b给右子树赋值开辟空间赋值为d,d给左子树赋值开辟空间赋值为e,e左子树赋值遇见'#',e左子树赋值为NULL,e右子树开辟空间赋值为g,g左右子树赋值都遇见'#',左右子树都接受返回值NLL,e右子树接受返回值为g的节点,d左子树接收返回值为e的节点,以此类推............

Tree* CreateOrder(char* ch,int* si)
{
    if(ch[*si]=='#')
   {
    (*si)++;
     return NULL;
     }
    Tree* root=(Tree*)malloc(sizeof(Tree));
    root->a=ch[(*si)++];
    root->left=CreateOrder(ch, si);
    root->right=CreateOrder(ch,si);

    return root;
}

完整AC代码如下

#include <stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
typedef char TreeType;
typedef struct Tree
{
	TreeType a;
	struct Tree* left;
	struct Tree* right;
}Tree;

Tree* CreateOrder(char* ch,int* si)
{
    if(ch[*si]=='#')
   {
    (*si)++;
     return NULL;
     }
    Tree* root=(Tree*)malloc(sizeof(Tree));
    root->a=ch[(*si)++];
    root->left=CreateOrder(ch, si);
    root->right=CreateOrder(ch,si);

    return root;
}
void InOrder(Tree* root)//中序遍历
{
	if (root == NULL)
	{
		return;
	}
	InOrder(root->left);//左子树
	printf("%c ", root->a);//根
	InOrder(root->right);//柚子树
}
int main() {
    char* ch[100];
    scanf("%s",ch);
    int i=0;
    Tree* root=CreateOrder(ch,&i);
    InOrder(root);
    return 0;
}


这篇文章就到这里啦!希望能对你有所帮助

(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

  • 22
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值