二叉树的所有用法

注:二叉树还是很难的,主要思想是递归,二叉树的创建,遍历是关键,做题还需掌握二叉树的拷贝翻转等等

1.二叉树节点结构体的定义

typedef char BTDataType;  //方便修改二叉树中的数据类型

typedef struct BinaryTreeNode  //定义二叉树结点的结构体
{
    BTDataType _data;
    struct BinaryTreeNode* _left;
    struct BinaryTreeNode* _right;
}BTNode;

二叉树节点的结构体包含三个内容(定义的数据类型,左指针,右指针)

想要改二叉树中的数据类型只需要把上面的char改了就ok了

 

2.通过遍历数组实现二叉树

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* str, int* i)  //这里写成str,是因为BTDataType是char类型,好区分,一般a是int类型
{
	if (str[*i] == '#' )
	{
		(*i)++;
		return NULL;
	}
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));  //为二叉树结点申请空间并指向root指针
	if (root == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}
	root->_data = str[(*i)++];
	root->_left = BinaryTreeCreate(str, i);
	root->_right = BinaryTreeCreate(str, i);
	return root;
}

很简单的递归思想,根节点的左指针指向左子树,左子树怎么来,创建,左子树的根节点的左子树怎么来,创建,一步步递归 ,右边同理

递归思想弄明白加几个判断就ok了

1.数组里的数据如果是‘#’返回NULL空指针

2.如果不是‘#’,新建一个二叉树节点(用malloc在堆上开辟空间,申请失败吧啦吧啦)

3.存数据,左指针递归,右指针递归,都完事了return刚刚建好的二叉树节点的指针

注:通过传过来的指针变量控制数组下标(递归的实现跨越了很多函数,必须通过地址控制变量)

 

3.二叉树的拷贝 

怎么说呢,二叉树的拷贝还是很重要的,先拷贝之后在进行操作,这样可以保留原来的二叉树

//二叉树拷贝
struct TreeNode* CopyBinaryTree(struct TreeNode* root)
{
    if (root == NULL)
    {
        return NULL;
    }
    //拷贝左子树
    struct TreeNode* left = CopyBinaryTree(root->left);
    //拷贝右子树
    struct TreeNode* right = CopyBinaryTree(root->right);
    //创建结点
    struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    //将当前结点的信息拷贝到新结点中
    newNode->val = root->val;
    newNode->left = left;
    newNode->right = right;
    return newNode;
}

这里建立两个指针 left 和 right 来接收递归的值,其实也可以不接收,直接左右指针指向递归值

    newNode->left = Copy_invertBinaryTree(root->left);
	newNode->right = Copy_invertBinaryTree(root->right);	

想法就是尽量保存递归值,避免多次递归(比如递归值不保存直接用来判断,结果还得算一次) 

 

4.二叉树翻转 

二叉树翻转就是用了拷贝的思想,想明白很简单,左边指向右边 (拷贝加翻转)

 

//226. 翻转二叉树
struct TreeNode* Copy_invertBinaryTree(struct TreeNode* root)
{
    if (root == NULL)
    {
        return NULL;
    }
    //左指针指向源节点右子树
    struct TreeNode* left = Copy_invertBinaryTree(root->right);
    //右指针指向源节点左子树
    struct TreeNode* right = Copy_invertBinaryTree(root->left);
    //创建结点
    struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    //将当前结点的信息拷贝到新结点中
    newNode->val = root->val;
    newNode->left = left;
    newNode->right = right;
    return newNode;
}

 

 5.二叉树的层序遍历

层序遍历,主要思想是利用一个队列,队列的特点是先进先出,这样插入的顺序就是后面打印的顺序,总结就是打印并头删根节点,尾插子节点

具体实现过程:

1.把根节点的地址尾插到队列中

2.写一个while循环,判断条件是队列不为空

3.新建front指针来保存队首元素(队列存放的是int类型,得转换成结构体指针才能使用箭头)

4.打印front指针指向的二叉树节点的值

5.队列头删

6.队列尾插左子树地址,右子树地址(不为空的时候才插入)

7.队列为空,也尽量销毁队列,因为不清楚是否有哨兵位

// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)  //利用一个队列进行层序遍历
{
	if (root == NULL)
		return 0;
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);  //这里是把地址进行尾插(因为root的值就是一个地址)
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);  //获取队列头部元素(一个地址)保存到front指针中
		printf("%c ", front->_data);  //打印一下队头元素的值
		QueuePop(&q);
		if (front->_left) 
			QueuePush(&q, front->_left);  //把左子树的地址尾插
		if (front->_right)
			QueuePush(&q, front->_right);  //再把右子树的地址尾插
	}
	printf("\n");
	QueueDestroy(&q);
}

 

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

判断是否是完全二叉树的思想与层序遍历的思想相似,都是按层将数据存入队列,不过这次空指针也要存入队列(值为0)

具体实现过程:

1.和层序遍历一样,先把队头元素尾插到队列中

2.建立一个while循环(进入循环条件是队列不为空)

3.新建二叉树结构体指针 front 用来存放队首元素的值 

4.增加一条 if 语句,如果队首值为0( front 是空指针),退出循环(如果是完全二叉树的话后面的应该都为0了)

5..建立第二次while循环(进入循环条件还是队列不为空)

6.如果队首是非零元素,销毁队列,返回false

7.头删,继续循环

8.循环完毕都没有返回false,说明全是0(空指针),销毁队列,返回true

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	if (root == NULL)
		return false;
	Queue q;  //定义一个队列
	QueueInit(&q);  //初始化
	QueuePush(&q, root);  //把root中的地址存入队首
	while (!QueueEmpty(&q))  //队列不为空进入循环
	{
		BTNode* front = QueueFront(&q);  //定义一个指针变量front,指针中存放的值为队首元素的值
		if (!front)  //队首存的值为0,退出循环
			break;
		QueuePush(&q, front->_left);  //左子结点的地址数放入队列
		QueuePush(&q, front->_right);  //右子节点的地址数放入队列
		QueuePop(&q);  //删除队首元素
	}
	while (!QueueEmpty(&q))
	{
		if (QueueFront(&q))  //队首为非零元素
		{
			QueueDestroy(&q);
			return false;  //非完全二叉树
		}
			
		QueuePop(&q);
	}
	QueueDestroy(&q);
	return true;  //队列全为0,就是完全二叉树
}

 

7.二叉树的其他功能 

1)二叉树销毁

void BinaryTreeDestory(BTNode* root)
{
	if (root==NULL)
	{
		return;
	}
	BinaryTreeDestory(root->_left);
	BinaryTreeDestory(root->_right);
	free(root);
}

2)二叉树节点个数

int BinaryTreeSize(BTNode* root)
{
	//如果是空节点,为0,返回左子树节点个数加上右子树节点个数
	return root == NULL ? 0 : BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}

3)二叉树叶子节点个数

int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)  //空节点返回0
	{
		return 0;
	}
	if (root->_left == NULL && root->_right == NULL)  //没有左右子结点那么就返回一个1(本结点)
	{
		return 1;
	}
	return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right);  //左子树结点个数加右子树节点个数
}

4)二叉树第k层节点个数

int BinaryTreeLevelKSize(BTNode* root, int k)  //传过来的是根节点指针, 和在第k层结点
{
	if (root == NULL)  //空节点返回0
	{
		return 0;
	}
	
	if (k == 1)  
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
}

5)二叉树查找值为x的节点

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;	

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

	BTNode* ret1 = BinaryTreeFind(root->_left, x);  //保存一下结点,避免二次查找
	if (ret1)
		return ret1;
	BTNode* ret2 = BinaryTreeFind(root->_right, x);
	if (ret2)
		return ret2;
	return NULL;
}

6)二叉树前序遍历

void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->_data);
	BinaryTreePrevOrder(root->_left);
	BinaryTreePrevOrder(root->_right);
}

7)二叉树中序遍历

void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	BinaryTreeInOrder(root->_left);
	printf("%c ", root->_data);
	BinaryTreeInOrder(root->_right);
}

8)二叉树后序遍历

void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NUll ");
		return;
	}
	BinaryTreePostOrder(root->_left);
	BinaryTreePostOrder(root->_right);
	printf("%c ", root->_data);
}

8.全部代码,包含队列

 

BinaryTree.h

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

typedef char BTDataType;

typedef struct BinaryTreeNode  //定义二叉树结点的结构体
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi);  //传过去两个参数,数组和初始值为0的参数i的地址
//BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);  //传过去的是遍历的数组,数组元素个数,初始值为0的参数i的地址

// 二叉树销毁
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);
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);

 

 

BinaryTree.c 

#define _CRT_SECURE_NO_WARNINGS 1
#include"BinaryTree.h"
#include"Queue.h"


// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
//法一(传两个参数)
BTNode* BinaryTreeCreate(BTDataType* str, int* i)  //这里写成str,是因为BTDataType是char类型,好区分,一般a是int类型
{
	if (str[*i] == '#' )
	{
		(*i)++;
		return NULL;
	}
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));  //为二叉树结点申请空间并指向root指针
	if (root == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}
	root->_data = str[(*i)++];
	root->_left = BinaryTreeCreate(str, i);
	root->_right = BinaryTreeCreate(str, i);
	return root;
}

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
// 法二(三个参数)-其实没啥区别
//BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)  //传过去的是遍历的数组,数组元素个数,初始值为0的参数地址
//{
//	if (a[*pi] == '#' || *pi >= n)  //注意这里'#'别写成双引号了,这个错误找了好久
//	{
//		(*pi)++;
//		return NULL;
//	}
//	
//	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
//	if (node == NULL)
//	{
//		printf("malloc fail");
//		exit(-1);
//	}
//	node->_data = a[(*pi)++];
//	node->_left = BinaryTreeCreate(a, n, pi);
//	node->_right = BinaryTreeCreate(a, n, pi);
//	return node;
//}

// 二叉树销毁
void BinaryTreeDestory(BTNode* root)
{
	if (root==NULL)
	{
		return;
	}
	BinaryTreeDestory(root->_left);
	BinaryTreeDestory(root->_right);
	free(root);
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	//如果是空节点,为0,返回左子树节点个数加上右子树节点个数
	return root == NULL ? 0 : BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)  //空节点返回0
	{
		return 0;
	}
	if (root->_left == NULL && root->_right == NULL)  //没有左右子结点那么就返回一个1(本结点)
	{
		return 1;
	}
	return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right);  //左子树结点个数加右子树节点个数
}
// 二叉树第k层节点个数(定义k>=1)也就是根节点所在层为第一层
int BinaryTreeLevelKSize(BTNode* root, int k)  //传过来的是根节点指针, 和在第k层结点
{
	if (root == NULL)  //空节点返回0
	{
		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* ret1 = BinaryTreeFind(root->_left, x);  //保存一下结点,避免二次查找
	if (ret1)
		return ret1;
	BTNode* ret2 = BinaryTreeFind(root->_right, x);
	if (ret2)
		return ret2;
	return NULL;
}


// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->_data);
	BinaryTreePrevOrder(root->_left);
	BinaryTreePrevOrder(root->_right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	BinaryTreeInOrder(root->_left);
	printf("%c ", root->_data);
	BinaryTreeInOrder(root->_right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NUll ");
		return;
	}
	BinaryTreePostOrder(root->_left);
	BinaryTreePostOrder(root->_right);
	printf("%c ", root->_data);
}
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)  //利用一个队列进行层序遍历
{
	if (root == NULL)
		return 0;
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);  //这里是把地址进行尾插(因为root的值就是一个地址)
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);  //获取队列头部元素(一个地址)保存到front指针中
		printf("%c ", front->_data);  //打印一下队头元素的值
		QueuePop(&q);
		if (front->_left) 
			QueuePush(&q, front->_left);  //把左子树的地址尾插
		if (front->_right)
			QueuePush(&q, front->_right);  //再把右子树的地址尾插
	}
	printf("\n");
	QueueDestroy(&q);
}

// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	if (root == NULL)
		return false;
	Queue q;  //定义一个队列
	QueueInit(&q);  //初始化
	QueuePush(&q, root);  //把root中的地址存入队首
	while (!QueueEmpty(&q))  //队列不为空进入循环
	{
		BTNode* front = QueueFront(&q);  //定义一个指针变量front,指针中存放的值为队首元素的值
		if (!front)  //队首存的值为0,退出循环
			break;
		QueuePush(&q, front->_left);  //左子结点的地址数放入队列
		QueuePush(&q, front->_right);  //右子节点的地址数放入队列
		QueuePop(&q);  //删除队首元素
	}
	while (!QueueEmpty(&q))
	{
		if (QueueFront(&q))  //队首为非零元素
		{
			QueueDestroy(&q);
			return false;  //非完全二叉树
		}
			
		QueuePop(&q);
	}
	QueueDestroy(&q);
	return true;  //队列全为0,就是完全二叉树
}

 

 

Queue.h

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

typedef int QDataType;  //这样不用每次都写 int QDataType
typedef struct QueueNode  //定义结点
{
	QDataType data;  //存放数据
	struct QueueNode* next;  //存放指针指向下一个结点
}QNode;

typedef struct Queue  //凡是结构都写一下typedef,不然每次都写struct很难受
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

void QueueInit(Queue* pq); 
void QueueDestroy(Queue* pq); 
void QueuePush(Queue* pq, QDataType x);  //尾插传参数传的队列指针和尾插的数据
void QueuePop(Queue* pq);  //头部删除
QDataType QueueFront(Queue* pq);  //获取队列头部元素 
QDataType QueueBack(Queue* pq);  // 获取队列队尾元素 
bool QueueEmpty(Queue* pq);  //为空为1,不为空为0
int QueueSize(Queue* pq);  // 获取队列中有效元素个数 

 

 

 Queue.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void QueueInit(Queue* pq)
{
	assert(pq);

	pq->head = NULL;
	pq->tail = NULL;
	pq->size=0;
}
void QueueDestroy(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->head;
	while (cur)
	{
		QNode* del = cur;
		cur = cur->next;

		free(del);
		//del = NULL;  //这个不用置空,因为del是局部变量,出while就销毁了
	}

	pq->head = pq->tail = NULL;  //free之后变为野指针,要置空
	pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)  //尾插
{
	assert(pq);

	QNode* newnode = (QNode*)malloc(sizeof(QNode));  
	//新建一个结点,由于只有这个函数需要开辟内存,所以不用写BuyNode函数
	if (newnode == NULL)  //开辟失败
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = NULL;
	if (pq->tail == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}

	pq->size++;
}
void QueuePop(Queue* pq)  //头部删除
{
	assert(pq);
	assert(!QueueEmpty(pq));
	if (pq->head->next == NULL)  //只有一个结点
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* del = pq->head;  //保存当前节点
		pq->head = pq->head->next;  //后移

		free(del);
	}

	pq->size--;
}
QDataType QueueFront(Queue* pq)  // 获取队列头部元素 
{
	assert(pq);
	assert(!QueueEmpty(pq));  //不能为空

	return pq->head->data;
}
QDataType QueueBack(Queue* pq)  //获取队列队尾元素 
{
	assert(pq);
	assert(!QueueEmpty(pq));  //不能为空

	return pq->tail->data;
}
bool QueueEmpty(Queue* pq)  //为空为1,不为空为0
{
	assert(pq);

	return pq->head == NULL && pq->tail == NULL;
}
int QueueSize(Queue* pq)  //获取队列中有效元素个数 
{
	assert(pq);

	//int size = 0;
	//QNode* cur = pq->head;
	//while (cur)  //size的时间复杂度是o(n),可以优化一下,在结构体中定义一个size
	//{
	//	cur = cur->next;
	//	++size;
	//}
	//return size;

	return pq->size;
}

 

 

 test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"BinaryTree.h"
#include"Queue.h"

int main()
{
	//char a[] = "ABD##E#H##CF##G##";
	char a[] = "ABD##E##CF##G##";
	int i = 0;
	int n = sizeof(a) / sizeof(a[0]);

	BTNode* root1 = BinaryTreeCreate(a, &i);  //传过去两个参数,数组和初始值为0的参数i的地址
	//BTNode* root1 = BinaryTreeCreate(a, n, &i);  //传过去的是遍历的数组,数组元素个数,初始值为0的参数i的地址
	
	BinaryTreePrevOrder(root1);  //前序遍历
	printf("\n");

	BinaryTreeInOrder(root1);  //中序遍历
	printf("\n");

	BinaryTreePostOrder(root1);  //后序遍历
	printf("\n");

	//int ksize = BinaryTreeLevelKSize(root1, 3);
	//printf("%d\n", ksize);

	//BTNode* root2 = BinaryTreeFind(root1,'h');  //寻找存放数据D的结点,找到了返回指针,找不到返回空
	//printf("%c", root2->_data);
	BinaryTreeLevelOrder(root1);  //层序遍历

	int ret = BinaryTreeComplete(root1);
	printf("%d", ret);
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值