二叉树的链式结构

目录

一、二叉树建立:

 二、分步解决所有问题:

1、创建二叉树:

2、前、中、后序遍历

 3、销毁:

4、计算结点个数:

5、计算第k层的节点个数:

6、计算高度/深度: 

7、求叶子结点个数:

8、 查找值为x的结点:

 9、层序遍历:

10、判断二叉树是否为完全二叉树:

三、代码分装实现:

1、Tree.h头文件(声明)

2、Tree.c源文件(实现)

3、test.c源文件(测试)

4、队列的头文件:Queue.h

5、队列的源文件:Queue.c


前言:在前一篇文章 树(Tree) 中我们提到了什么是二叉树的链式结构,但是并没有进行实现,这篇文章就是为了实现二叉树

一、二叉树建立:

目标:

1、通过前序遍历将一个数组创建成二叉树

2、前、中、后、层序遍历

3、销毁

4、计算结点个数

5、计算第k层的节点个数

6、计算高度/深度

7、求叶子结点个数

8、判断二叉树是否是完全二叉树

9、查找值为X的结点

 如下:二叉树的结构(底层逻辑就是链表)

 二、分步解决所有问题:

1、创建二叉树:

我们提供一个数组,对这个数据通过前序遍历来实现;将如下这个数组变成一个二叉树

// 通过前序遍历的数组"1,2,3,0,0,4,0,5,0,0,6,7,0,0,8,0,0"构建二叉树
TrNode* TrCreate(TrDataType* a, int n, int* pi)
{
	//判断是否为NULL
	if (a[(*pi)] == 0)
	{
		//下标要加一
		(*pi)++;
		return NULL;
	}
	if (*pi == n)
	{
		return NULL;
	}
	//创建节点
	TrNode* node = (TrNode*)malloc(sizeof(TrNode));
	if (node == NULL)
	{
		perror("malloc");
		return NULL;
	}
	node->data = a[(*pi)++];
	node->left = TrCreate(a,n,pi);
	node->right = TrCreate(a, n, pi);
	return node;
}

 结束条件就是数组元素为0时或者下表和pi相等时,这里用到了指针来表示下标,为了保证下标会发生改变(在函数改变形参不影响实参,要指针才能通过改变实参影响形参)

2、前、中、后序遍历

放到一起就是因为他们的遍历顺序,其实就是打印位置的区别,不就根的位置决定了遍历顺序么

//前序遍历:根 左 右
void TrInOrder1(TrNode* root)
{
	//递归结束条件:若是为空,结束
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->data);
	TrInOrder1(root->left);
	TrInOrder1(root->right);
}
//中序遍历:左 根 右
void TrInOrder2(TrNode* root)
{
	//递归结束条件:若是为空,结束
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	TrInOrder2(root->left);
	printf("%d ", root->data);
	TrInOrder2(root->right);
}
//后序遍历:左 右 根
void TrInOrder3(TrNode* root)
{
	//递归结束条件:若是为空,结束
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	TrInOrder3(root->left);
	TrInOrder3(root->right);
	printf("%d ", root->data);

}

这个就是递归图,:展开图,尝试画一下

 3、销毁:

也是递归实现,问题可以化为,先左右子树,在根结点进行销毁,转换为子问题:一般我直接就是去尾部来确认递归结束条件和里面要进行的操作,可以看见当3的左右子树不为NULL就销毁,若是为NULL就不要管,

 进行销毁的前提是根不为NULL,当根结点为NULL时结束递归,开始回推

//销毁:先销毁左孩子和右孩子
void TrDestroy(TrNode** root)
{
	if (*root != NULL)
	{
		if ((*root)->left)
		{
			TrDestroy(&(*root)->left);
		}
		if ((*root)->right)
		{
			TrDestroy(&(*root)->right);
		}
		//最后销毁根结点
		free(*root);
		*root = NULL;
	}
}
//销毁:先销毁左孩子和右孩子
//void TrDestroy(TrNode** root)
//{
//	if (*root == NULL)
//		return;
//	//左孩子
//	if ((*root)->left)
//		TrDestroy(&(*root)->left);
//	//右孩子
//	if ((*root)->right)
//		TrDestroy(&(*root)->right);
//	//销毁
//	free(*root);
//	*root = NULL;
//}

4、计算结点个数:

 问题转化:根+左子树的结点个数+右子树的结点个数 == 总结点个数

根据树的根可以细化很多个,子问题:计算左、右子树的结点+1

 如下代码即可,你可以想一下当递归到最3的左子树是不是就是NULL,返回0个,右子树也是0个

然后加上1就是3的节点数,回推就计算好了结点个数,看上图

//求结点个数:大事化小->将树看成   子树和根    子树节点数+1 
int TrSize(TrNode* root)
{
	//结束条件:NULL时
	if (root == NULL)
		return 0;
	return TrSize(root->left) + TrSize(root->right) + 1;
}

 看不懂可以看下图,画了一部分递归展开图如下

5、计算第k层的节点个数:

逆着来,不就是对每个孩子找自己的爹一次,当k==1时,返回1,这个是我们能够利用的条件

int TrKSize(TrNode* root, int k)
{
	//空树:下限(结束条件)
	if (root == NULL)
	{
		return 0;
	}
	//当只有根
	if (k == 1)
	{
		return 1;
	}
	return TrKSize(root->left, k - 1) + TrKSize(root->right, k - 1);
}

6、计算高度/深度: 

明确问题:高度怎么算???可以将高度分为2部分,左子树的高度右子树的高度高的子树  +根

 子问题:左右谁高返回谁,然后加 根 

//求高度:左右子树高的那棵树+1;
int TrHigt(TrNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int Hleft = TrHigt(root->left);
	int Hright = TrHigt(root->right);
	return Hleft > Hright ? Hleft + 1 : Hright + 1;
}

 错误示范:这个程序能运行但是会不断重复,就是不记事;三目操作(条件操作符),先判断,判断完以后是不是要调用后面的函数,调进去右会走一遍三目操作符,所以说他没有记住,要重复好几次才能带值回来,就像老师找班长查本班在校人数,记录了值去和隔壁班比较,比完以后,又忘了我们班学生多少人,又回去让班长调查,班长又去查,查了又去比较,又会忘记;反反复复才记起

7、求叶子结点个数:

求叶子左右为空树就是叶子结点,即没有孩子的结点;

//求叶子结点个数:
int TrLeafSize(TrNode* root)
{
	if (root == NULL)
		return 0;
	else if (root->left == NULL && root->right == NULL)
		return 1;
	else
		return TrLeafSize(root->left) + TrLeafSize(root->right);
}

8、 查找值为x的结点:

可以通过前序遍历去遍历一遍当找到和x相同的值时,返回该结点即可;当然了,当我们遍历找到了以后就不需要继续去找右子树了,所以用到了一个if语句,当左子树为NULL,就去找右子树找结点;

// 二叉树查找值为x的节点
TrNode* TrFind(TrNode* root, TrDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	TrNode* ret = TrFind(root->left, x);
	if (ret == NULL)
	{
		return TrFind(root->right, x);
	}
	else
	{
		return ret;
	}
}

 9、层序遍历:

就是将每一层打印出来,也就是打印每一层的结点:

 这里我们不用递归的思想,需要以每行进行打印,有堂兄也有亲兄弟,这个关系和之前我们使用递归不一样,之前左右子树直接就可以联系到一起;要用队列的思想来实现先进先出的好处,先进来的结点 1 入队,然后先判断其左、再判断其右孩子是否存在,若是左孩子存在则让其做孩子入队,右孩子存在则让右孩子入队列,然后将1Pop掉(出队),大致流程如下:

 这里还要涉及到队的建立、插入、删除和销毁,最后会给出整个代码,先看一下层序遍历怎么实现的;队列里面的数据类型就变成了二叉树的节点类型,要存的是TrNode*;我们根节点就是*root的形式传进来的,我们会引用Queue的头文件来实现队列创建,注意数据元素要修改


//层序遍历:目的是打印每一层 递归实现不了,队列:先进先出的原则
void TrLevelOrder(TrNode* root)
{
	if (root == NULL)
		return;
	Queue q;
	//初始化
	QuInto(&q);
	QuPush(&q, root);
	while (!QuEmpty(&q))
	{
		//队头的元素
		TrNode* nwnode = QuFront(&q);
		printf("%d ", nwnode->data);
		if (nwnode->left != NULL)
		{
			QuPush(&q, nwnode->left);
		}
		if (nwnode->right != NULL)
		{
			QuPush(&q, nwnode->right);
		}
		//删除头结点!
		QuPop(&q);
	}
	QuDestroy(&q);
}

10、判断二叉树是否为完全二叉树:

 当层序遍历以后发现可以继续用队列的思想来做,首先是明白什么是完全二叉树,前k-1是节点是满的,第k可以是不满的,但必须从左往右连续

前面的思路是不为空入队这次不管是不是空,将结点的左、右孩子都放队里头,发现了么,当左子树的叶子结点到NULL时,如果第k-1层右孩子或者左孩子NULL,就会出现一个N在里面,那么只要判断N后面是否全是NULL还是存在结点,如果存在结点:说明不是完全二叉树,因为子叶节点不连续(通过入队来间接实现的顺序结构,之前的就讲过完全二叉树用顺序存储时是不会浪费空间,这里就发生了浪费空间的现象)思路和层序大差不差,这里我省略了步骤,直接画的结果图

// 判断二叉树是否是完全二叉树:
int TrComplete(TrNode* root)
{

	Queue q;
	QuInto(&q);
	QuPush(&q, root);
	TrNode* nwnode = q.head->a;
	while (!QuEmpty(&q))
	{
		nwnode = QuFront(&q);
		if (nwnode == NULL)
			break;
		QuPush(&q,nwnode->left);
		QuPush(&q,nwnode->right);
		QuPop(&q);
	}
	while (!QuEmpty(&q))
	{
		nwnode = QuFront(&q);
		QuPop(&q);
		if (nwnode != NULL)
		{
			QuDestroy(&q);
			return 0;
		}
		
	}
	QuDestroy(&q);
	return 1;
}

三、代码分装实现:

1、Tree.h头文件(声明)

#pragma once
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>
typedef int TrDataType;
//二叉树的结点
typedef struct TreeNode {
	//数据
	TrDataType data;
	//左结点
	struct TreeNode* left;
	//右结点
	struct TreeNode* right;
}TrNode;

typedef TrNode* QuDataType;
//队列的结点
typedef struct QueueNode {
	QuDataType a;
	struct QueueNode* next;
}QuNode;
//队列
typedef struct Queue {
	//记录头
	QuNode* head;
	//记录尾
	QuNode* tail;
	int size;
}Queue;
//初始化头尾节点
void QuInto(Queue* q);
//尾插
void QuPush(Queue* q, QuDataType x);
//头删
void QuPop(Queue* q);
//判空
bool QuEmpty(Queue* q);
//销毁队列
void QuDestroy(Queue* q);









//前序遍历:
void TrInOrder1(TrNode* root);
//中序遍历:
void TrInOrder2(TrNode* root);
//后序遍历:
void TrInOrder3(TrNode* root);
//求结点个数:
int TrSize(TrNode* root);
//求叶子结点个数:
int TrLeafSize(TrNode* root);
//求高度:
int TrHigt(TrNode* root);
//求第k层结点个数:
int TrKSize(TrNode* root, int k);
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
TrNode* TrCreate(TrDataType* a, int n, int* pi);
// 层序遍历:
void TrLevelOrder(TrNode* root);
// 判断二叉树是否是完全二叉树:
int TrComplete(TrNode* root);
//销毁
void TrDestroy(TrNode** root);
// 二叉树查找值为x的节点
TrNode* TrFind(TrNode* root, TrDataType x);


2、Tree.c源文件(实现)

#define _CRT_SECURE_NO_WARNINGS 3

#include"Tree.h"



//前序遍历:根 左 右
void TrInOrder1(TrNode* root)
{
	//递归结束条件:若是为空,结束
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->data);
	TrInOrder1(root->left);
	TrInOrder1(root->right);
}
//中序遍历:左 根 右
void TrInOrder2(TrNode* root)
{
	//递归结束条件:若是为空,结束
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	TrInOrder2(root->left);
	printf("%d ", root->data);
	TrInOrder2(root->right);
}
//后序遍历:左 右 根
void TrInOrder3(TrNode* root)
{
	//递归结束条件:若是为空,结束
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	TrInOrder3(root->left);
	TrInOrder3(root->right);
	printf("%d ", root->data);

}



//求结点个数:大事化小->将树看成   子树和根    子树节点数+1 
int TrSize(TrNode* root)
{
	//结束条件:NULL时
	if (root == NULL)
		return 0;
	return TrSize(root->left) + TrSize(root->right) + 1;
}
//用于求高度式,判断左右子树
int MAX(int x, int y)
{
	return x > y ? x : y;
}
//求叶子结点个数:
int TrLeafSize(TrNode* root)
{
	if (root == NULL)
		return 0;
	else if (root->left == NULL && root->right == NULL)
		return 1;
	else
		return TrLeafSize(root->left) + TrLeafSize(root->right);
}

//求高度:左右子树高的那棵树+1;
//求高度:左右子树高的那棵树+1;
int TrHigt(TrNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int Hleft = TrHigt(root->left);
	int Hright = TrHigt(root->right);
	return Hleft > Hright ? Hleft + 1 : Hright + 1;
}

//求第k层结点个数:问题分为。第k层的节点数就是k-1层的父亲有几个孩子的问题;
// 第一层的根节点,石头蹦出来的k==1
// 左孩子的爹+右孩子的爹就等于这层的个数也就是左子树的k-1层加右子树的k-1层
int TrKSize(TrNode* root, int k)
{
	//空树:下限(结束条件)
	if (root == NULL)
	{
		return 0;
	}
	//当只有根
	if (k == 1)
	{
		return 1;
	}
	return TrKSize(root->left, k - 1) + TrKSize(root->right, k - 1);
}

// 通过前序遍历的数组"1,2,3,0,0,4,0,5,0,0,6,7,0,0,8,0,0"构建二叉树
TrNode* TrCreate(TrDataType* a, int n, int* pi)
{
	//判断是否为NULL
	if (a[(*pi)] == 0)
	{
		//下标要加一
		(*pi)++;
		return NULL;
	}
	if (*pi == n)
	{
		return NULL;
	}
	//创建节点
	TrNode* node = (TrNode*)malloc(sizeof(TrNode));
	if (node == NULL)
	{
		perror("malloc");
		return NULL;
	}
	node->data = a[(*pi)++];
	node->left = TrCreate(a,n,pi);
	node->right = TrCreate(a, n, pi);
	return node;
}


//销毁:先销毁左孩子和右孩子
void TrDestroy(TrNode** root)
{
	if (*root == NULL)
		return;
	//左孩子
	if ((*root)->left)
		TrDestroy(&(*root)->left);
	//右孩子
	if ((*root)->right)
		TrDestroy(&(*root)->right);
	//销毁
	free(*root);
	*root = NULL;
}
// 二叉树查找值为x的节点
TrNode* TrFind(TrNode* root, TrDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	TrNode* ret = TrFind(root->left, x);
	if (ret == NULL)
	{
		ret = TrFind(root->right, x);
	}
	else
	{
		return ret;
	}

}

// 判断二叉树是否是完全二叉树:
int TrComplete(TrNode* root)
{

	Queue q;
	QuInto(&q);
	QuPush(&q, root);
	TrNode* nwnode = q.head->a;
	while (!QuEmpty(&q))
	{
		nwnode = q.head->a;
		if (nwnode == NULL)
			break;
		QuPush(&q,nwnode->left);
		QuPush(&q,nwnode->right);
		QuPop(&q);
	}
	while (!QuEmpty(&q))
	{
		nwnode = q.head->a;
		QuPop(&q);
		if (nwnode != NULL)
		{
			QuDestroy(&q);
			return 0;
		}
		
	}
	QuDestroy(&q);
	return 1;
}




//层序遍历:目的是打印每一层 递归实现不了,队列:先进先出的原则
void TrLevelOrder(TrNode* root)
{
	if (root == NULL)
		return;
	Queue q;
	//初始化
	QuInto(&q);
	QuPush(&q, root);
	while (!QuEmpty(&q))
	{
		//队头的元素
		TrNode* nwnode = q.head->a;
		printf("%d ", nwnode->data);
		if (nwnode->left != NULL)
		{
			QuPush(&q, nwnode->left);
		}
		if (nwnode->right != NULL)
		{
			QuPush(&q, nwnode->right);
		}
		//删除头结点!
		QuPop(&q);
	}
	QuDestroy(&q);
}

//队列
//初始化头尾节点
void QuInto(Queue* q)
{
	q->head = q->tail = NULL;
	q->size = 0;
	
}
//尾插
void QuPush(Queue* q, QuDataType x)
{
	assert(q);
	//申请看空间:
	QuNode* node = (QuNode*)malloc(sizeof(QuNode));
	if (node == NULL)
	{
		perror("malloc");
		return;
	}
	node->a = x;
	node->next = NULL;
	//当头尾都在初始处
	if (q->tail == NULL)
	{
		q->head = q->tail = node;
		
	}
	else
	{
		q->tail->next = node;
		q->tail = node;
	}
	q->size++;
}
//头删
void QuPop(Queue* q)
{
	assert(q);
	assert(q->size > 0);
	QuNode* ptal = q->head->next;
	if (q->head == q->tail)
	{
		free(q->head);
		q->head = q->tail = NULL;
	}
	else
	{
		free(q->head);
		q->head = ptal;
	}
	q->size--;
}
//判空
bool QuEmpty(Queue* q)
{
	assert(q);
	return q->size == 0;

}
//销毁
//销毁空间(写进数据,想要一次性释放完就来用)
void QuDestroy(Queue* q)
{
	assert(q);
	QuNode* cur = q->head;
	while (cur)
	{
		QuNode* next = cur->next;
		free(cur);
		cur = next;
	}
	q->head = q->tail = NULL;
	q->size = 0;
}

3、test.c源文件(测试)

#define _CRT_SECURE_NO_WARNINGS 3
#define _CRT_SECURE_NO_WARNINGS 3

#include"Tree.h"

//手搓建立二叉树
TrNode* TrInt(TrDataType x)
{
	TrNode* pts = (TrNode*)malloc(sizeof(TrNode));
	if (pts == NULL)
	{
		perror("malloc");
		return;
	}
	pts->data = x;
	pts->left = NULL;
	pts->right = NULL;
	return pts;
}
//二叉树:
void Game()
{
	//将数组建立成二叉树
	int i = 0;
	//1,2,3,0,0,4,0,5,0,0,6,7,0,0,8,0,0
	int a[] = { 1,2,3,0,0,0,4,5,0,0,6,0,0 };
	int n = sizeof(a) / sizeof(a[0]);
	TrNode* root =TrCreate(a, n, &i);
	//前序遍历;
	TrInOrder1(root);
	putchar('\n');
	//中序遍历:
	TrInOrder2(root);
	putchar('\n');
	//后序遍历;
	TrInOrder3(root);
	putchar('\n');
	//结点个数:
	int ret = TrSize(root);
	printf("结点个数:%d\n", ret);
	ret = TrLeafSize(root);
	printf("叶子节点个数:%d\n", ret);
	ret = TrHigt(root);
	printf("高度:%d\n", ret);
	ret = TrKSize(root, 3);
	printf("第k层结点个数:%d\n", ret);
	TrNode* node10 = TrFind(root, 7);
	if (node10)
	{
		printf("%d \n", node10->data);
	}
	TrLevelOrder(root);
	putchar('\n');
	ret = TrComplete(root);
	if (ret == 0)
	{
		printf("不是完全二叉树\n");
	}
	

	//销毁:
	TrDestroy(&root);
}


int main()
{
	Game();

	return 0;
}

4、队列的头文件:Queue.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
//前置声明
typedef struct TreeNode TrNode ;

typedef TrNode* QUDataType;
//节点
typedef struct QueueNode{
	
	QUDataType a;
	//一定要用指针,不然结构体的大小就无法确定了
	struct QueueNode *next;
}QuNode;
//再建立一个保存头和尾的结构体
typedef struct Queue {
	QuNode* head;
	QuNode* tail;
	int size;
}Queue;

//初始化头尾节点
void QuInto(Queue* q);
//尾插
void QuPush(Queue* q,QUDataType x);
//头删
void QuPop(Queue* q);
//判空
bool QuEmpty(Queue* q);
//数据个数
int QuSize(Queue* q);
//取头数据
QUDataType QuFront(Queue* q);
//取尾数据
QUDataType QuBack(Queue* q);
//销毁空间(写进数据,想要一次性释放完就来用)
void QuDestroy(Queue* q);

5、队列的源文件:Queue.c

#define _CRT_SECURE_NO_WARNINGS 3

#include"Queue.h"

//初始化头尾节点
void QuInto(Queue* q)
{
	//不能传空指针  即(q=NULL)
	assert(q);
	q->head = NULL;
	q->tail = NULL;
	q->size = 0;
}

//尾插(2种情况:1.头尾都为NUL  2.又数据入队列了)
void QuPush(Queue* q, QUDataType x)
{
	//申请空间
	QuNode* newnode = (QuNode*)malloc(sizeof(QuNode));
	//判空
	if (newnode == NULL)
	{
		perror("malloc");
		return;
	}
	newnode->a = x;
	newnode->next = NULL;//节点创建完成
	//判断尾的位置
	if (q->tail == NULL)
	{
		q->head = q->tail = newnode;
	}
	else
	{
		q->tail->next = newnode;
		q->tail = newnode;
	}
	q->size++;
}

//头删(删除到尾以后,就不能再删了)
void QuPop(Queue* q)
{
	assert(q);
	//头的位置也不能为空
	assert(q->size!=0);
	//此时数据个数为1(也就是最后一个节点)
	if (q->head->next==NULL)
	{
		free(q->head);
		q->head = q->tail = NULL;
	}
	else//q->head != q->tail;
	{
		QuNode* next = q->head->next;
		free(q->head);
		q->head = next;
	}
	
	//数据个数也要减去
	q->size--;
	
}

//判空
bool QuEmpty(Queue* q)
{
	assert(q);
	//头尾都相等时到达同一个位置,此时就为真,其他的情况都为假;
	return q->size==0;
}

//取头数据
QUDataType QuFront(Queue* q)
{
	assert(q);
	assert(q->head);
	return q->head->a;
}

//取尾数据
QUDataType QuBack(Queue* q)
{
	assert(q);
	//队尾都为空了,已经没数据了
	assert(q->tail);
	return q->tail->a;
}
//数据个数
int QuSize(Queue* q)
{
	assert(q);
	return q->size;
}

//销毁空间(写进数据,想要一次性释放完就来用)
void QuDestroy(Queue* q)
{
	assert(q);
	QuNode* cur = q->head;
	while (cur)
	{
		QuNode* next = cur->next;
		free(cur);
		cur = next;
	}
	q->head = q->tail =NULL;
	q->size = 0;
}

如果有不理解的或者看不懂的地方,希望能留下你的评论;

  完                        结                 撒                        花

  • 18
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值