数据结构 —— 二叉树

1.树的概念及结构

1.1树的概念

树是一种非线性的数据结构,它有着多分支,层次性的特点。

由于其形态类似于自然界中倒过来的数,所以我们将这种数据结构称为"树形结构"

注意: 树形结构中,子树之间不能有交集,否它就不是树形结构

1.2 树的相关概念

  • 结点的度:一个结点含有的子树的个数称为该结点的度; 如上图:A的为6
  • 叶结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I...等结点为叶结点
  • 非终端结点或分支结点:度不为0的结点; 如上图:D、E、F、G...等结点为分支结点
  • 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点
  • 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点
  • 兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点
  • 树的度:一棵树中,最大的结点的度称为树的度; 如上图:树的度为6
  • 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推;
  • 树的高度或深度:树中结点的最大层次; 如上图:树的高度为4
  • 堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点
  • 结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先
  • 子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙
  • 森林:由m(m>0)棵互不相交的树的集合称为森林;

1.3树的表示

树的表示我们使用:孩子兄弟表示法

设计一个数的节点,其中包含数据域(存储数据)、指针域(左孩子指针,右兄弟指针)

typedef int DataType;
struct Node
{
 struct Node* firstChild1; // 第一个孩子结点
 struct Node* pNextBrother; // 指向其下一个兄弟结点
 DataType data; // 结点中的数据域
};

这种数的设计方法,我们可以通过左孩子指针找到 A节点 的第一个孩子(B),在通过孩子的右兄弟指针把 A节点 的所有孩子都找到

1.4 树在实际中的运用

树在实际中的运用:电脑中的数目录

2.二叉树的概念及结构

2.1二叉树的概念

在实际运用中,二叉树要比树更加实用

二叉树其实就是特殊的一种树,它的每个节点最多有两个子节点,通常被称为左子节点和右子节点

  • 二叉树不存在度大于2的结点
  • 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

2.2现实中的二叉树

2.3特殊的二叉树

  • 满二叉树:二叉树的每一层都是满的(特殊的完全二叉树)
  • 完全二叉树:二叉树的最后一层不一定是满的,但是它是连续的

像下面这个二叉树,最后一层并不连续,因此它并非是完全二叉树:

2.4二叉树的性质

2.5二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构

  1. 顺序存储
    顺序结构存储就是使用 数组来存储 ,一般使用数组 只适合表示完全二叉树 ,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储
    二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树
  2. 链式存储
    二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。
    通常的方法是:链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,目前我们使用二叉链的新式学习
    二叉链式实现的二叉树:

3.二叉树的顺序存储结构

顺序存储结构只推荐完全叉树来进行存储,一般的二叉树容使用顺序结构进行存储,容易造成空间的大量浪费,现实中我们通常把堆**(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段**

堆在这篇文章中有所介绍 ------------ 数据结构 - 堆

4.二叉树的链式结构的实现

在进行二叉树链式结构的实现时,我们首先回顾二叉树是:

  1. 空树
  2. 非空:根结点,根结点的左子树、根结点的右子树组成的

每一颗二叉树都可以看做是递归形成的因为:

每一颗二叉树都可以拆分成:根节点 左子树 右子树

它的左子树可以被拆分成 :根节点 左子树 右子树

它的右子树右也可以被拆分成 :根节点 左子树 右子树

依次类推直到变成一颗空树,不能被拆分,所以才会说二叉树可以看做是递归形成,二叉树可以被拆分成一个一个的小问题(即一个一个的子树 根节点),直到变成空树不能再被拆分,因此后序基本操作中基本都是按照递归概念实现的

4.1二叉树的前置声明

typedef int BTDataType;

typedef struct BinaryTreeNode    //二叉树的单个节点
{
 BTDataType _data;
 struct BinaryTreeNode* _left;   //左孩子
 struct BinaryTreeNode* _right;  //右孩子
}BTNode;

4.2二叉树的遍历

4.2.1前序、中序以及后序遍历

二叉树的遍历是指按照某种规则访问树中的所有节点,并且每个节点只被访问一次。 访问结点所做的操作依赖于具体的应用问题。遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

按照规则,二叉树的遍历有:前序**/中序/**后序的递归结构遍历

  1. 前序遍历 (Preorder Traversal )------ 访问顺序:根节点 --->左子树 --->右子树
  2. 中序遍历(Inorder Traversal)------访问顺序:左子树 --->根节点 --->右子树
  1. 后序遍历(Postorder Traversal)------访问顺序:左子树 --->右子树--->根节
    前序遍历递归图解

前序遍历结果: 1 2 3 4 5 6
中序遍历结果: 3 2 1 5 4 6
后序遍历结果: 3 2 5 6 4 1
前中后序遍历的方式大抵相同,这里不做过多赘述
代码实现:

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root) {

	if (root == NULL)  //当访问的数为NULL树时停止访问
	{
		printf("N ");
		return;
	}

	printf("%d ",root->_data);//先便利根节点,整形的数据类型

	BinaryTreePrevOrder(root->_left);//左子树
	BinaryTreePrevOrder(root->_right);//右子树

}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root) {

	if (root == NULL)
	{
		printf("N ");
		return;
	}

	BinaryTreeInOrder(root->_left);//左子树
	printf("%d ", root->_data);//根节点
	BinaryTreeInOrder(root->_right);//右子树
}

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

	BinaryTreePostOrder(root->_left);//左子树
	BinaryTreePostOrder(root->_right);//右子树
	printf("%d ", root->_data);//根节点
}

4.2.2层序遍历

层序遍历 :除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根结点所在层数为1 ,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第 2 层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

代码实现:

二叉树的层序遍历并不是通过递归来完成的,而是通过 ------ 数据结构中的队列来实现的

遍历的原理是从根节点开始,首先访问根节点,然后将根节点的左右子节点依次入队。接下来,从队列中取出一个节点(队首节点),访问该节点,再将其未被访问的左右子节点入队。重复此过程,直到队列为空,即所有节点都被访问过。

动图理解:

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root) {
	//创建队列
	Queue qu;
	QueueInit(&qu);
	QueuePush(&qu, root);

	//开始拖家带口,当队列为NULL时,说明已经遍历完成,循环结束
	while (!QueueEmpty(&qu))
	{
		//先访问队头的元素
		BTNode* bt = QueueFront(&qu);//获取队头元素
		printf("%d ", bt->_data);

		//将树的左右孩子都带入队列中,NULL孩子除外
		if (bt->_left)
			QueuePush(&qu, bt->_left);
		if (bt->_right)
			QueuePush(&qu, bt->_right);

        //队头数据处队列
		QueuePop(&qu);
	}
    
    //销毁队列
	QueueDestroy(&qu);

}

5.二叉树总代码

队列的相关功能:

Queue.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef struct BinaryTreeNode* QDataType;   //队列中的元素是树的节点
// 链式结构:表示队列 
typedef struct QListNode
{
	struct QListNode* _next;
	QDataType _data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* _front;
	QNode* _rear;
	int size;
}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);

Queue.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"

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

	q->size = 0;
	q->_front = NULL;
	q->_rear = NULL;
}
// 队尾入队列 
void QueuePush(Queue* q, QDataType data) {
	assert(q);

	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("QueuePush()::malloc()");
		return;
	}

	newnode->_data = data;
	newnode->_next = NULL;
	
	//队列为NULL
	if (q->_front == NULL)
	{
		q->_front = q->_rear = newnode;
	}
	else
	{
		q->_rear->_next = newnode;
		q->_rear = q->_rear->_next;
	}

	q->size++;
}
// 队头出队列 
void QueuePop(Queue* q) {
	assert(q);
	assert(q->size != 0);

	//单个节点
	if (q->_front == q->_rear)
	{
		free(q->_front);
		q->_front = q->_rear = NULL;
	}
	//多个节点
	else
	{
		QNode* next = q->_front->_next;
		free(q->_front);
		q->_front = next;
	}

	q->size--;
}
// 获取队列头部元素 
QDataType QueueFront(Queue* q) {

	assert(q);
	assert(q->_front);//队头不能为NULL

	return q->_front->_data;
}
// 获取队列队尾元素 
QDataType QueueBack(Queue* q) {
	assert(q);
	assert(q->_rear);//队尾不能为NULL

	return q->_rear->_data;
}
// 获取队列中有效元素个数 
int QueueSize(Queue* q) {

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

	return q->size == 0;
}
// 销毁队列 
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;
	q->size = 0;

	//这个应该留给用户去释放
	/*free(q);
	q = NULL;*/
}

二叉树相关功能:

BinaryTree.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include"Queue.h"
typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;


// 二叉树销毁
void BinaryTreeDestory(BTNode** root);

// 二叉树节点个数
int BinaryTreeSize(BTNode* root);

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);

BTNode* CreatBinaryTree();

BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"BinaryTree.h"
BTNode* BuyNode(BTDataType x) {

	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("Buynode()::malloc()");
		return newnode;
	}

	newnode->_data = x;
	newnode->_left = NULL;
	newnode->_right = NULL;
	return newnode;
}

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root) {

	if (root == NULL)  //当访问的数为NULL树时停止访问
	{
		printf("N ");
		return;
	}

	printf("%d ",root->_data);//先便利根节点,整形的数据类型

	BinaryTreePrevOrder(root->_left);//左子树
	BinaryTreePrevOrder(root->_right);//右子树

}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root) {

	if (root == NULL)
	{
		printf("N ");
		return;
	}

	BinaryTreeInOrder(root->_left);//左子树
	printf("%d ", root->_data);//根节点
	BinaryTreeInOrder(root->_right);//右子树
}

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

	BinaryTreePostOrder(root->_left);//左子树
	BinaryTreePostOrder(root->_right);//右子树
	printf("%d ", root->_data);//根节点
}

//求二叉树的高度
int maxDepth(BTNode* root) {

	if (root == NULL)   //如果为空树则返回 0 
	{
		return 0;
	}

	int lefthigh = maxDepth(root->_left);    //记录树的左子树高度
	int righthigh = maxDepth(root->_right); //记录树的右子树高度

    //左子树高则返回左子树的高度    右子树高则返回右子树高度
	return lefthigh > righthigh ? lefthigh + 1 : righthigh + 1; 

}

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root) {

	if (root == NULL)  //如果为空树则返回 0 
		return 0;

	if (root->_left == NULL && root->_right == NULL)  //如果是叶子节点就返回 1 
		return 1;

    //返回左子树 与 右子树总共的叶子节点
	return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}

// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k) {

    //将找第k层问题转化成:层序遍历按照树的层次进行遍历,每次遍历一层,直到遍历到第k层或者遍历完整个树。   

	if (root == NULL)    //如果为空树则返回 0 
		return 0;

	if (root != NULL && k == 1) //当不为空且k为1时,到达所找层,返回1
		return 1;
	
    //一层一层的往下找
	if (root != NULL && k > 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* find1 = NULL;
	BTNode* find2 = NULL;

	
	find1 = BinaryTreeFind(root->_left, x);  //记录所找的节点

	if (find1)//如果左边找到了就不用去右边找了
		return find1;

	find2 = BinaryTreeFind(root->_right, x);
		return find2;
}

// 二叉树销毁
void BinaryTreeDestory(BTNode** root) {

	if (*root == NULL)
		return;
	BinaryTreeDestory(&((*root)->_left));//先销毁左子树
	BinaryTreeDestory(&((*root)->_right));//在销毁右子树
	free(*root);
	*root = NULL;
}

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

	return BinaryTreeSize(root->_left)+ BinaryTreeSize(root->_right) + 1;
}

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root) {
	//创建队列
	Queue qu;
	QueueInit(&qu);
	QueuePush(&qu, root);

	//开始拖家带口,当队列为NULL时,说明已经遍历完成,循环结束
	while (!QueueEmpty(&qu))
	{
		//先访问队头的元素
		BTNode* bt = QueueFront(&qu);//获取队头元素

		printf("%d ", bt->_data);

		//将树的左右孩子都带入队列中
		if (bt->_left)
			QueuePush(&qu, bt->_left);
		if (bt->_right)
			QueuePush(&qu, bt->_right);

		QueuePop(&qu);
	}

	QueueDestroy(&qu);


}

// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root) {

	//创建队列
	Queue qu;
	QueueInit(&qu);
	QueuePush(&qu, root);

	//开始拖家带口,当队列为NULL时,说明已经遍历完成,循环结束
	while (!QueueEmpty(&qu))
	{
		//队列中存的数据是,树节点的指针,我们先访问队头的元素
		BTNode* bt = QueueFront(&qu);//获取队头元素

		if (bt == NULL)
		{
			break;
		}


		//将树的左右孩子都带入队列中,NULL也不例外
		QueuePush(&qu, bt->_left);
		QueuePush(&qu, bt->_right);

		QueuePop(&qu);
	}

	while (!QueueEmpty(&qu))
	{
		BTNode* bt = QueueFront(&qu);//获取队头元素

		//如果在遇到非空的节点,说明它不是一个完全二叉树返回false
		if (bt)
		{
			return false;
		}

		QueuePop(&qu);
	}

	QueueDestroy(&qu);

	return true;
}
相关推荐
闲仁人3 分钟前
数据结构预科
数据结构
卡戎-caryon5 分钟前
【项目实践】贪吃蛇
c语言·数据结构·算法
PeterClerk6 分钟前
PCA算法降维代码示例
人工智能·算法·机器学习·pca
计算机平台作业答案讲解9 分钟前
QT实现GIF动图显示(小白版,可直接copy使用)
服务器·开发语言·数据结构·数据库·c++·qt·动态规划
冲鸭嘟嘟可12 分钟前
【数据结构】使用C语言 从零实现一个栈的数据结构
c语言·数据结构·算法
ᯤ⁶ᴳ⁺⁵²⁰ ⌯˃̶ᗜ˂̶⌯ಣ14 分钟前
分支与循环
c语言
小白菜又菜31 分钟前
Leetcode 516. Longest Palindromic Subsequence
算法·leetcode·职场和发展
℡☞小白☜ღ1 小时前
信号量(semaphore)
算法
喉咙痛的恐龙1 小时前
《C语言》认识数据类型和理解变量
c语言·开发语言
鸽鸽程序猿1 小时前
【C语言】分支(选择)和循环语句
c语言·开发语言·学习·程序人生·学习方法·visual studio