Bootstrap

实现链式结构的二叉树

一 . 结构与概念 

用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储位置。

二叉树是由递归定义的 , 预告以下 : 本篇博客将开启递归算法的暴力美学!

回顾二叉树的概念 : 二叉树分为空树 和 非空二叉树 ,非空二叉树游又由根节点,根节点的左子树,根节点的右子树组成的 ,;根节点的左子树和右子树分别又是由子树结点,子树结点的左子树,子树结点的右子树组成的,因此二叉树定义是递归式的! 

二 . 遍历(前/中/后)

 2.1 定义二叉树的结构

二叉树是由一个一个的结点组成的 , 定义二叉树的结构就是定义结点的结构 !

结点的成员变量由三个 , 数值域 , 左指针域 , 右指针域

typedef char BTDataType;
//定义二叉树结构
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

 2.2 手动构造一个二叉树

二叉树的创建方式比较复杂 , 未来更好的步入到二叉树内容中,我们先手动创建一棵链式二叉树。

//申请结点
BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->data = x;
	node->right = NULL;
	node->left = NULL;
	return node;
}
//手动构造一棵二叉树
BTNode* CreateTree()
{
	//二叉树由结点构成 -- 先有结点
	BTNode* nodeA = BuyNode('A');
	BTNode* nodeB = BuyNode('B');
	BTNode* nodeC = BuyNode('C');
	BTNode* nodeD = BuyNode('D');
	BTNode* nodeE = BuyNode('E');
	BTNode* nodeF = BuyNode('F');

	// 再联系起来
	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;

	return nodeA;
}

2.3 二叉树的遍历

二叉树的操作离不开树的遍历 , 这里先介绍前 / 中 / 后序遍历 :

  • 前序遍历 : ( 左右)先遍历根节点,再遍历左结点,最后遍历右结点
  • 中序遍历 : ( 左右)先遍历左结点,再遍历根节点,最后遍历右结点
  • 后序遍历 : ( 左右先遍历左结点,再遍历右结点,最后遍历根节点

前序遍历 : (根左右)

图解遍历 :

函数栈帧图解遍历 : 

//前序遍历
void preOrder(BTNode* root)
{
	//根左右
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	preOrder(root->left);
	preOrder(root->right);
}

 中序遍历 : (左根右)

//中序遍历
void InOrder(BTNode* root)
{
	//左根右
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}

后序遍历 : (左右根)

//后序遍历
void PostOrder(BTNode* root)
{
	//左右根
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}

 三 . 二叉树常见接口的实现:

3.1 二叉树结点的个数:

使用递归的思想来求解二叉树结点的个数 !

注意 : 使用局部变量 , 全局变量 , 静态变量时,不要忽视再次调用时,size初始化的问题

错误算法: 

局部变量 : 每次调用函数栈帧的时候 , size都会被初始化成 0 。

//size 局部变量
int BTNodeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int size = 0;
	size++;
	BTNodeSize(root->left);
	BTNodeSize(root->right);
	return size;
}

全局变量 : 第一次使用时 , 确实可以求解成功 , 但是第二次/第三/第n次求解 , size并不会初始化成 0 , 而是一直在累加!!! 

//size 全局变量
int size = 0;
int BTNodeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	size++;
	BTNodeSize(root->left);
	BTNodeSize(root->right);
	return size;
}

静态变量 : 也是累加! 

//size静态变量
static int size = 0;
int BTNodeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	size++;
	BTNodeSize(root->left);
	BTNodeSize(root->right);
	return size;
}

正确算法 : 1 + 左子树结点总数 + 右子树结点总数

1 )  如果root == NULL  --->  retrun 0 

2 )  递归计算左右子树的结点总数

int BTNodeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BTNodeSize(root->left) + BTNodeSize(root->right);
}

 3.2 二叉树叶子结点的个数

二叉树叶子结点的特征 :  度为 0 ( 无左右孩子) 

思路 : 左子树叶子结点的个数 + 右子树叶子结点的个数

1 ) root 为空(root == NULL) ----> return 0;

2 )    左右孩子为空 (root->left == NULL && root->right == NULL )  -----> return 1 ;

3 )   递归调用左右子树

//二叉树叶子结点的个数
int BTLeafNode(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BTLeafNode(root->left) + BTLeafNode(root->right);
}

3.3 二叉树第K层结点的个数

思路 : 第K层左子树结点个数 + 第K层右子树结点的个数

1 ) 当root 为空(root == NULL) ------> return 0 ;  

2 )   当 k ==1 时 , 是求根结点       -----> return 1;

3 )   递归调用 左右子树,k传递的参数为 k - 1 ; 

//二叉树第K层结点的个数
int BTLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BTLevelKSize(root->left, k - 1) + BTLevelKSize(root->right, k - 1);
}

3.4 二叉树的深度/高度

思路 : 根节点层次 + (左子树,右子树)中最大的层次

1 ) 如果 root = NULL , return 0

2 )    用两个变量分别存储计算的左右子树的层次的结果

3 ) 比较二者,求得最大层次(三目操作符)

//二叉树的深度/高度
int BTDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leafDep = BTDepth(root->left);
	int rightDep = BTDepth(root->right);
	return 1 + ((leafDep > rightDep ? leafDep : rightDep));
}

 3.5 查找值为X的结点 :

这里介绍的是通过前序遍历来查找结点x

思路 : 先遍历左子树 , 找到--> 返回结点 , 没找到 ---> 继续遍历右子树

1 ) 如果为空树 , return NULL

2 )  根节点为要找的结点 ,return root

3 ) 遍历左子树 , 找到 , 返回结点

4 )左子树没找到,遍历右子树 , 找到 , 返回结点

5 )两个子树都没找到 , return NULL

//查找值为x的结点
BTNode* BTFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* FindLeft = BTFind(root->left, x);
	if (FindLeft)
	{
		return FindLeft;
	}
	BTNode* FindRight = BTFind(root->right, x);
	if (FindRight)
	{
		return FindRight;
	}
	return NULL;
}

3.6 销毁二叉树

这里销毁二叉树,我们使用后序遍历 ( 左右根) , 如果使用前序遍历或者后序遍历 , 根结点会先销毁 , 那么就找不到左右结点了

1 ) 使用二级指针传参 , 因为需要形参的改变影响实参 ,使用传址传参。

2 ) 注意 -> 操作符 的 优先级 高于 * 操作符

//二叉树的销毁
void BTDestory(BTNode** root)
{
	if (*root == NULL)
	{
		return;
	}
	BTDestory(&((*root)->left));
	BTDestory(&((*root)->right));
	free(*root);
	*root = NULL;
}

 3.7 层序遍历

除了深度优先遍历(先序遍历 , 中序遍历 , 后序遍历 ) ,还可以对二叉树进行广度优先遍历 ( 层序遍历 )   ----> 从二叉树的根结点出发 , 首先访问第一层的树根结点 ,  然后从左到右访问第二层上的结点 ,  接着是第三层的结点 , 以此类推 , 自上而下 , 自左向右  逐层访问树的结点的过程就是层序遍历 。

 可以拿之前实现过的队列的数据结构的代码 , 栈与队列的常见接口的实现-CSDN博客

//层序遍历
void LevelOrder(BTNode* root)
{
	//借助数据结构 -- 队列
	Queue q;
	QueueInit(&q);
	//先让根结点入队
	QueuePush(&q,root);
	//判断队列是否为空 -- 不为空,出队头
	while (!QueueEmpty(&q))
	{
		BTNode* top = QueueFront(&q);
		QueuePop(&q);
		printf("%c ", top->data);
		if (top->left)
		{
			QueuePush(&q,top->left);
		}
		if (top->right)
		{
			QueuePush(&q,top->right);
		}
	}

	QueueDestory(&q);
}

 3.8 判断是否为完全二叉树

二叉树的特征 :

1 ) 最后一层结点的总数不一定达到最大

2 )从左到右依次排序

思路 : 

借助数据结构 --> 队列,在层序遍历的算法思想上,结合二叉树的特征,在第一轮的循环中,如果队头结点为空,则跳出循环 ; 第二轮循环中,如果全部结点都为空,则是完全二叉树,反之,则不是完全二叉树!

3.9 总代码

test.c

#include "Tree.h"

//手动构造一个二叉树
BTNode* createTree()
{
	//二叉树由结点构成 -- 现有结点
	BTNode* nodeA = BuyNode('A');
	BTNode* nodeB = BuyNode('B');
	BTNode* nodeC = BuyNode('C');
	BTNode* nodeD = BuyNode('D');
	BTNode* nodeE = BuyNode('E');
	BTNode* nodeF = BuyNode('F');

	//再联系起来
	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;

	return nodeA;
}

void test()
{
	BTNode* root = createTree();
	//PreOredr(root);
	//InOrder(root);
	//PostOrder(root);
	printf("size : %d\n", BTNodeSize(root));
	printf("size : %d\n", BTNodeSize(root));
	printf("leafSize : %d\n", BTLeafNode(root));
	printf("第%d层结点个数: %d\n", 3, BTLevelKSize(root, 3));
	printf("Depth:%d\n", BTDepth(root));
	BTNode* Find =  BTFind(root, 'A');
	if (Find == NULL)
	{
		printf("没找到!\n");
	}
	else
	{
		printf("找到了!\n");
	}
	//LevelOrder(root);

	if (BTComplete(root))
	{
		printf("是完全二叉树!\n");
	}
	else
	{
		printf("不是完全二叉树!\n");
	}
	BTDestory(&root);
}

int main()
{
	test();
	return 0;
}

 Tree.c

#include "Tree.h"

//申请一个节点
BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->data = x;
	node->left = node->right = NULL;
	return node;
}

//前序遍历 
void PreOredr(BTNode* root)
{
	//根左右
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	PreOredr(root->left);
	PreOredr(root->right);
}

//中序遍历
void InOrder(BTNode* root)
{
	//左根右
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}

//后序遍历
void PostOrder(BTNode* root)
{
	//左右根
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}

//二叉树结点的个数
//size 局部变量
//int BTNodeSize(BTNode* root)
//{
//	if (root == NULL)
//	{
//		return 0;
//	}
//	int size = 0;
//	size++;
//	BTNodeSize(root->left);
//	BTNodeSize(root->right);
//	return size;
//}

size 全局变量
//int size = 0;
//int BTNodeSize(BTNode* root)
//{
//	if (root == NULL)
//	{
//		return 0;
//	}
//	size++;
//	BTNodeSize(root->left);
//	BTNodeSize(root->right);
//	return size;
//}

size静态变量
//static int size = 0;
//int BTNodeSize(BTNode* root)
//{
//	if (root == NULL)
//	{
//		return 0;
//	}
//	size++;
//	BTNodeSize(root->left);
//	BTNodeSize(root->right);
//	return size;
//}

int BTNodeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BTNodeSize(root->left) + BTNodeSize(root->right);
}


//二叉树叶子结点的个数
int BTLeafNode(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BTLeafNode(root->left) + BTLeafNode(root->right);
}

//二叉树第K层结点的个数
int BTLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BTLevelKSize(root->left, k - 1) + BTLevelKSize(root->right, k - 1);
}

//二叉树的深度/高度
int BTDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leafDep = BTDepth(root->left);
	int rightDep = BTDepth(root->right);
	return 1 + ((leafDep > rightDep ? leafDep : rightDep));
}

//查找值为x的结点
BTNode* BTFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* FindLeft = BTFind(root->left, x);
	if (FindLeft)
	{
		return FindLeft;
	}
	BTNode* FindRight = BTFind(root->right, x);
	if (FindRight)
	{
		return FindRight;
	}
	return NULL;
}

//二叉树的销毁
void BTDestory(BTNode** root)
{
	if (*root == NULL)
	{
		return;
	}
	BTDestory(&((*root)->left));
	BTDestory(&((*root)->right));
	free(*root);
	*root = NULL;
}

//层序遍历
void LevelOrder(BTNode* root)
{
	//借助数据结构 -- 队列
	Queue q;
	QueueInit(&q);
	//先让根结点入队
	QueuePush(&q,root);
	//判断队列是否为空 -- 不为空,出队头
	while (!QueueEmpty(&q))
	{
		BTNode* top = QueueFront(&q);
		QueuePop(&q);
		printf("%c ", top->data);
		if (top->left)
		{
			QueuePush(&q,top->left);
		}
		if (top->right)
		{
			QueuePush(&q,top->right);
		}
	}

	QueueDestory(&q);
}

//判断二叉树是否为完全二叉树
bool BTComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* top = QueueFront(&q);
		QueuePop(&q);
		if (top == NULL)
		{
			break;
		}
		QueuePush(&q, top->left);
		QueuePush(&q, top->right);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* top = QueueFront(&q);
		QueuePop(&q);
		if (top != NULL)
		{
			QueueDestory(&q);
			return false;
		}
	}
	return true;
	QueueDestory(&q);
}

Tree.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include "Queue.h"

typedef char BTDataType;
//定义二叉数(结点)的结构
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

//申请一个节点
BTNode* BuyNode(BTDataType x);

//前序遍历
void PreOredr(BTNode* root);

//中序遍历
void InOrder(BTNode* root);

//后序遍历
void PostOrder(BTNode* root);

//二叉树结点的个数
int BTNodeSize(BTNode* root);

//二叉树叶子结点的个数
int BTLeafNode(BTNode* root);

//二叉树第K层结点的个数
int BTLevelKSize(BTNode* root, int k);

//二叉树的深度/高度
int BTDepth(BTNode* root);

//查找值为x的结点
BTNode* BTFind(BTNode* root, BTDataType x);

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

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

//判断二叉树是否为完全二叉树
bool BTComplete(BTNode* root);

queue.c

#include "Queue.h"

//初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

//插入数据
void QueuePush(Queue* pq, QueueNode* x)
{
	assert(pq);
	//向操作系统申请一块结点大小的空间 -- newnode
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;


	//插入数据
	//队列为空
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	//队列不为空
	else {
		pq->ptail->next = newnode;
		pq->ptail = pq->ptail->next;
	}
	pq->size++;
}

//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->ptail == NULL;
}

//队列中的有效元素个数
int QueueSize(Queue* pq)
{
	//QueueNode* pcur = pq->phead;
	//int size = 0;
	//while (pcur)
	//{
	//	size++;
	//	pcur = pcur->next;
	//}
	return pq->size;
}

//删除数据
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	//一个结点
	if (pq->phead == pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	//多个结点
	else {
		QueueNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	--pq->size;
}

//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}

//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->ptail->data;
}

//销毁队列
void QueueDestory(Queue* pq)
{
	assert(pq);
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

queue,

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

//typedef int QDataType;
//更改点一 :
//前置声明:
typedef struct BinaryTreeNode* QDataType;

//定义队列结点的结构
typedef struct QueueNode
{
	QDataType data;
	struct QueneNode* next;
}QueueNode;
//定义队列的结构
typedef struct Queue
{
	QueueNode* phead;
	QueueNode* ptail;
	int size;
}Queue;

//初始化
void QueueInit(Queue* pq);

//插入数据
void QueuePush(Queue* pq, QueueNode* x);

//判断队列是否为空
bool QueueEmpty(Queue* pq);

//队列中的有效元素个数
int QueueSize(Queue* pq);

//删除数据
void QueuePop(Queue* pq);

//取队头数据
QDataType QueueFront(Queue* pq);

//取队尾数据
QDataType QueueBack(Queue* pq);

//销毁队列
void QueueDestory(Queue* pq);
;