数据结构_二叉树

本文详细介绍了链式存储的二叉树结构,包括二叉链表的定义、二叉树的遍历(前序、中序、后序、层序)、二叉树的创建与销毁、计算节点和叶子节点数量、查找指定值节点以及判断是否为完全二叉树的方法。通过实例展示了如何使用递归和队列实现这些操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇博客在介绍堆之前简单介绍了数据结构中树的概念,堆的逻辑结构是二叉树树,但是物理结构却是顺序表。接下来在这篇博客中,将对物理结构是链表的二叉树作详细总结。

二叉树的遍历

首先再来回顾一下什么是二叉树:

  1. 空树。二叉树可以是空树
  2. 非空。由根结点,根结点左子树和根结点右子树组成。

二叉树的子树也是二叉树,所以可以看出二叉树可以通过递归的方式来遍历。

树的遍历:是指按某种方式访问树中的每个结点且每个结点只被访问一次。

举个例子来说,对于数组遍历时,可以通过for循环顺序访问数组中的每一个数据。
然而树作为一种非线性结构,它的遍历用简单的循环是不能完成的,需要用到专门的算法。

前序、中序、后序

用递归的方式遍历二叉树有三种方法:先序、中序和后序。这三种方式中的先、中、后指的是访问根结点的顺序。用’D’表示访问根结点;用’L’表示访问根结点的左子树;用’R’表示访问根结点的右子树。
下面逐一介绍上述三种遍历方式。


先序(DLR)
若二叉树为空,结束遍历;否则执行以下三步:

  1. 访问根结点
  2. 前序遍历根结点的左子树
  3. 前序遍历根结点的右子树

举例
在这里插入图片描述


中序(LDR)
若二叉树为空,结束遍历;否则执行以下三步:

  1. 中序遍历二叉树的左子树
  2. 访问根结点
  3. 中序遍历根结点的右子树

举例
在这里插入图片描述


后序(LRD)
若二叉树为空,结束遍历;否则执行以下三步:

  1. 后序遍历二叉树的左子树
  2. 后序遍历根结点的右子树
  3. 访问根结点

举例
在这里插入图片描述


相关例题
已知某二叉树的中序遍历序列为JGDHKBAELIMCF,后序遍历序列为JGKHDBLMIEFCA,则其前序遍历序列为
在这里插入图片描述
利用后序可以确定根结点,利用中序可以确定左右子树,因此后序+中序可以确定出一棵二叉树的结构。本题的确定过程如下图所示:
在这里插入图片描述
解释:(框代表各个子树的根结点,虚线划分开了不同子树)

  1. 可以通过后序判断出A是根结点,然后利用中序可以确定A左边的结点都是左子树的结点,A右边是右子树结点,用黑色虚线隔开;
  2. 接下来利用后序可以确定B是左子树的根结点,然后利用中序判断出B没有右子树,红色虚线左边是B的左子树结点;
  3. 接下来利用后序判断D是B的左子树的根结点,再利用前序将D的左右子树结点区分开,用黄色虚线隔开;
  4. 最后利用后序可以找到D的左右子树的根结点,然后利用中序可以判断出G没有右子树,H没有左子树;
  5. A的左子树结构已经确立,同样的也可以确实A的右子树结构。

总结:前序+中序 和 后序+中序 都可以重建树,但是前序+后序不行。


层序(广度)遍历

层序遍历:指对于一棵二叉树,每一层从左到右遍历,遍历完一层接着遍历下一层,直到遍历完所有结点。

前文中的二叉树层序遍历顺序是:ABCDEFG。

链式二叉树

结构

链式二叉树也有多种形式,本篇博客主要介绍的是二叉链式二叉树,即二叉树每个结点拥有两个指向其它结点的指针和一个存储数据的变量,如下图所示:
在这里插入图片描述

结构体定义

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

实现

二叉树创建

二叉树真正的创建比较复杂,在这篇博客中先手动创建一个二叉树。

BTNode* BinaryTreeNodeCreate(BTDataType data)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));

	node->data = data;
	node->left = NULL;
	node->right = NULL;

	return node;
}

int main()
{
	// 手动创建一个二叉树
	BTNode* n1 = BinaryTreeNodeCreate(1);
	BTNode* n2 = BinaryTreeNodeCreate(2);
	BTNode* n3 = BinaryTreeNodeCreate(3);
	BTNode* n4 = BinaryTreeNodeCreate(4);
	BTNode* n5 = BinaryTreeNodeCreate(5);
	BTNode* n6 = BinaryTreeNodeCreate(6);
	BTNode* n7 = BinaryTreeNodeCreate(7);

	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n4->left = n5;
	n4->right = n6;
	//n2->right = n7;
	n3->right = n7;

	return 0;
}

二叉树销毁

void BinaryTreeDestory(BTNode* root)
{
	if (root == NULL)
		return;

	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->left);

	free(root);

	root = NULL;

}

计算二叉树结点个数

int BinaryTreeSize(BTNode* root)
{
	return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

计算二叉树叶结点个数

int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}

	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

二叉树第k层的结点个数

注意:根结点是第1层

int BinaryTreeLevelKSize(BTNode* root, int k)
{
	// 思路:二叉树k层结点数等于左右子树k-1层结点数之
	// k == 1 时,说明已经到了二叉树的第k层,那么当前结点算一个,返回1
	if (root == NULL)
	{
		return 0;
	}

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

	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

在二叉树中查找值为x的结点

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	// 先从根找,找不到找左子树,然后是右子树
	if (root == NULL)
		return NULL;

	if (root->data == x)
		return root;

	BTNode* find = BinaryTreeFind(root->left, x);

	if (find == NULL)
		find = BinaryTreeFind(root->right, x);

	return find;
}

二叉树前序遍历

void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	printf("%d ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);

}

二叉树中序遍历

void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	BinaryTreeInOrder(root->left);

	printf("%d ", root->data);

	BinaryTreeInOrder(root->right);

}

二叉树后序遍历

void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%d ", root->data);

}

层序遍历

void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

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

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);

		printf("%d ", front->data);
		QueuePop(&q);

		if (front->left != NULL)
		{
			QueuePush(&q, front->left);
		}

		if (front->right != NULL)
		{
			QueuePush(&q, front->right);
		}
	}

	QueueDestroy(&q);
}

对二叉树的层序遍历,一种方法是借助队列来实现。具体思路是:

  1. 从根结点开始,将根结点输入队列,然后执行出队列操作,每出一次队列,接着将出队列结点的左右孩子结点依次输入队列;
  2. 然后再出队列,直到将队列中所有结点输出。

举例
在这里插入图片描述
过程模拟
在这里插入图片描述

判断二叉树是否为完全二叉树

int BinaryTreeComplete(BTNode* root)
{
	// 把树的结点全部入队列(包括NULL),如果某个结点前面有空结点,说明不是完全二叉树
	Queue q;
	QueueInit(&q);

	if (root !=NULL)
	{
		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. 若队列元素出完时没出现非空结点,则说明是完全二叉树;若出现非空结点,则说明不是完全二叉树。

简单来说就是把二叉树最后一个结点前面所有结点都输入队列,按照层序遍历,看是否在最后一个结点前面存在空结点,如果有肯定就不是完全二叉树了。

测试程序及所有代码

一共三个文件:

  1. QueueForTree.h 包括头文件、函数声明和结构体
  2. QueueForTree.c 包括队列接口函数的实现
  3. Test_BinaryTree.c 关于二叉树的所有接口函数和测试代码
#pragma once
#include<stdio.h>
#include<assert.h>
#include<malloc.h>
#include<assert.h>
#include<stdbool.h>

struct BinaryTreeNode;

typedef struct BinaryTreeNode* QDataType;


// 链式结构:表示队列 
// 队列中的结点
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;
	QNode* rear;
}Queue;

// 初始化队列 
void QueueInit(Queue* q);

// 队尾入队列 
void QueuePush(Queue* q, QDataType data);

// 队头出队列 
void QueuePop(Queue* q);

// 获取队列头部元素 
QDataType QueueFront(Queue* q);

// 获取队列队尾元素 
QDataType QueueBack(Queue* q);

// 获取队列中有效元素个数 
int QueueSize(Queue* q);

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q);

// 销毁队列 
void QueueDestroy(Queue* q);
#include"QueueForTree.h"


void QueueInit(Queue* q)
{
	assert(q);

	q->front = NULL;
	q->rear = NULL;
}

void QueuePush(Queue* q, QDataType data)
{
	assert(q);

	QNode* newnode = (QNode*)malloc(sizeof(QNode));

	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	newnode->data = data;
	newnode->next = NULL;

	if (QueueEmpty(q))
	{
		q->front = q->rear = newnode;
	}
	else
	{
		q->rear->next = newnode;
		q->rear = newnode;
	}

}

void QueuePop(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));

	// 如果只剩一个结点的情况
	if (q->front->next == NULL)
	{
		free(q->front);
		q->front = q->rear = NULL;
	}
	else
	{
		QNode* next = q->front->next;
		free(q->front);
		q->front = next;
	}

}

QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));

	return q->front->data;
}

QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));

	return q->rear->data;
}

int QueueSize(Queue* q)
{
	assert(q);

	int count = 0;

	QNode* cur = q->front;

	while (cur)
	{
		count++;
		cur = cur->next;
	}

	return count;
}

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);

	return q->front == NULL;
}

// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);

	QNode* cur = q->front;

	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	q->front = q->rear = NULL;
}

#include"QueueForTree.h"
typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

BTNode* BinaryTreeNodeCreate(BTDataType data)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));

	node->data = data;
	node->left = NULL;
	node->right = NULL;

	return node;
}

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}

	// 不是#那就构建根
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	root->data = a[(*pi)];
	(*pi)++;

	// 递归构建左子树
	root->left = BinaryTreeCreate(a, pi);

	// 递归构建右子树
	root->right = BinaryTreeCreate(a, pi);

	return root;
}

// 二叉树销毁
void BinaryTreeDestory(BTNode* root)
{
	if (root == NULL)
		return;

	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->left);

	free(root);

	root = NULL;

}

// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}

	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}

	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	// 思路:二叉树k层结点数等于左右子树k-1层结点数之
	// k == 1 时,说明已经到了二叉树的第k层,那么当前结点算一个,返回1
	if (root == NULL)
	{
		return 0;
	}

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

	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	// 先从根找,找不到找左子树,然后是右子树
	if (root == NULL)
		return NULL;

	if (root->data == x)
		return root;

	BTNode* find = BinaryTreeFind(root->left, x);

	if (find == NULL)
		find = BinaryTreeFind(root->right, x);

	return find;
}

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	printf("%d ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);

}

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	BinaryTreeInOrder(root->left);

	printf("%d ", root->data);

	BinaryTreeInOrder(root->right);

}

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%d ", root->data);

}
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);

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

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);

		printf("%d ", front->data);
		QueuePop(&q);

		if (front->left != NULL)
		{
			QueuePush(&q, front->left);
		}

		if (front->right != NULL)
		{
			QueuePush(&q, front->right);
		}
	}

	QueueDestroy(&q);
}
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{
	// 把树的结点全部入队列(包括NULL),如果某个结点前面有空结点,说明不是完全二叉树
	Queue q;
	QueueInit(&q);

	if (root !=NULL)
	{
		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;

}

int main()
{
	// 手动创建一个二叉树
	BTNode* n1 = BinaryTreeNodeCreate(1);
	BTNode* n2 = BinaryTreeNodeCreate(2);
	BTNode* n3 = BinaryTreeNodeCreate(3);
	BTNode* n4 = BinaryTreeNodeCreate(4);
	BTNode* n5 = BinaryTreeNodeCreate(5);
	BTNode* n6 = BinaryTreeNodeCreate(6);
	BTNode* n7 = BinaryTreeNodeCreate(7);

	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n4->left = n5;
	n4->right = n6;
	//n2->right = n7; // 取消这行注释可以测试判断是否为二叉树功能
	n3->right = n7;


	printf("前序遍历:  ");
	BinaryTreePrevOrder(n1);
	printf("\n");

	printf("中序遍历:  ");
	BinaryTreeInOrder(n1);
	printf("\n");

	printf("后序遍历:  ");
	BinaryTreePostOrder(n1);
	printf("\n");

	printf("层序遍历:  ");
	BinaryTreeLevelOrder(n1);
	printf("\n");

	printf("树的结点个数:  %d\n", BinaryTreeSize(n1));
	printf("树的叶子结点个数:  %d\n", BinaryTreeLeafSize(n1));
	int k = 3;
	printf("树的第%d层叶结点个数:  %d\n", k,BinaryTreeLevelKSize(n1,k));

	printf("查找3然后打印3的结果:  %d\n", BinaryTreeFind(n1, 3)->data);

	printf("是否为完全二叉树:  %d\n", BinaryTreeComplete(n1));

	int i = 0;
	char arr[20] = "ABD##E#H##CF##G##";

	BTNode* root = BinaryTreeCreate(arr, &i);

	BinaryTreePrevOrder(root);

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值