二叉树实现(1)

一、相关头文件

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<errno.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);
// 二叉树销毁
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);

和完全二叉树不同,一般得二叉树不能通过下标得算数关系来找到子节点或者父节点,所以利用链表来存储更好;left指向根节点的左子树,right指向根节点的右子树;

二叉树的实现设计到递归,并且一般都是左子树和右子树两个递归;

二、函数定义

为了方便理解,首先从二叉树的前、中、后三种遍历说起;

首先,手动创建一个简单的二叉树作为测试用例;

typedef int BTDataType;
typedef struct BinaryTreeNode
{
 BTDataType _data;
 struct BinaryTreeNode* _left;
 struct BinaryTreeNode* _right;
}BTNode;
BTNode* CreatBinaryTree()
{
 BTNode* node1 = BuyNode(1);
 BTNode* node2 = BuyNode(2);
 BTNode* node3 = BuyNode(3);
 BTNode* node4 = BuyNode(4);
 BTNode* node5 = BuyNode(5);
 BTNode* node6 = BuyNode(6);
 
 node1->_left = node2;
 node1->_right = node4;
 node2->_left = node3;
 node4->_left = node5;
 node4->_right = node6;
 return node1;
}

申请节点:

BTNode* BuyNode(BTDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(1);
	}
	newnode->data = x;
	newnode->right = newnode->left = NULL;

	return newnode;
}

那么此时该二叉树就是这样的:

叶子节点(3、5、6所在的节点)的左右子树都是NULL;

1、前序遍历

// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	else
	{
		printf("%c ", root->data);
		BinaryTreePrevOrder(root->left);
		BinaryTreePrevOrder(root->right);
	}
}

什么是前序遍历?

前序遍历:先访问根节点,再访问左子树,再访问右子树;

看到上面的测试用例,那么就是先是 根节点1 再是1的左子树,在1的左子树里面又有根节点2,那么就先是2 再是2的左子树;2的左子树是3,3作为新的根节点,先访问3,再访问3的左子树和右子树都是NULL,就返回,此时2的左子树访问完了,接着访问2的右子树,是NULL,返回,此时1的左子树访问完了,去访问1的右子树;

右边也是同样的道理:先将4作为新的根节点,访问4,再去访问4的左子树,4的左子树是5,将5作为新的根节点,访问5之后去访问5的左子树,是NULL,返回,再去访问5的右子树,也是NULL,也返回;那么4的左子树就访问完了;再去访问4的右子树,4的右子树的根节点是6,先访问6,之后访问6的左子树是NULL,返回,再去访问6的右子树,是NULL,也返回,那么4的右子树也访问完了。此时,整棵树的前序遍历就完成了.

打印出来就是 1 2 3 N N N 4 5 N N 6 N N (N代表是NULL);

代码:这里递归的思想很明显:递归是将大的问题拆分成子问题,再去一个一个解决;在使用递归的时候要找到结束条件和每个子问题的基本解决思路,这些子问题的解决思路都一样,只不过子问题的差别在于大小,而每一个大一点的子问题都是由小的子问题组成的;

##代码思路:通过观察,可以发现每个子问题都是先访问根节点,再去访问左子树和右子树;结束条件就是访问到空节点时就结束访问,因为此时没有节点去访问;

走一遍代码:先判断根节点是不是NULL,是的就直接结束函数;不是就访问根节点,打印出根节点root的data值,此时打印的是 1;接着去访问左子树,左子树里面2又是根节点,和之前同样的操作,打印2,再去访问2的左子树,打印3,访问3的左子树,为空,返回,那么此时3的左子树调用的函数就结束了,去访问3的右子树,为NULL,同样地,也结束;都打印N;那么此时2的遍历左子树的函数就结束了,再去看2的右子树,为空,打印N,结束2遍历右子树的函数,那么1的遍历左子树就完了,再去看1的右子树。同样的思路。

红线代表递,绿线代表归;

2、中序遍历

先访问左子树,再访问根节点,再访问右子树;思路和前序遍历一样;

// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	else
	{
		BinaryTreeInOrder(root->left);
		printf("%c ", root->data);
		BinaryTreeInOrder(root->right);
	}
}

打印出来就是 N 3 N 2 N 1 N 5 N 4 N 6 N ;

3、后序遍历

// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	else
	{
		BinaryTreePostOrder(root->left);
		BinaryTreePostOrder(root->right);
		printf("%c ", root->data);
	}
}

先访问左子树,再访问右子树,最后访问根节点;

打印出来就是:N N 3 N 2 N N 5 N N 6 4 1 ;

4、二叉树的节点个数

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

找到结束条件是节点为NULL,就结束,返回0,代表没有节点;

不为空就返回左子树的节点个数加上右子树的节点个数再加上1(就是此时的根节点);这是每一个子问题的解决思路,看到只有一个节点的情况:根节点不是NULL,返回左子树的节点个数,w为NULL,返回0,右子树也是返回0,最后加上1,是根节点自己的个数,那么这颗树的节点个数就是1;推广到用例的二叉树也是这样的;

5、二叉树叶子节点个数

// 二叉树叶子节点个数
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);
}

叶子节点的特点就是左右子树都是NULL,将这个作为结束条件,也就是当一个节点的左右子树都是NULL的时候,就判定这个节点是叶子节点,此时返回1(个数);当根节点是NULL的时候就返回0,此时代表没有叶子节点;

子问题的思路:根节点不是NULL,就在左子树和右子树里面找,找到左右子树都是NULL的节点就返回1;叶子节点的个数应该是左右子树的叶子节点相加,所以返回的时候要返回左子树加上右子树的叶子节点个数。

6、k层节点个数

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

结束条件:寻找第k层的节点个数,将k利用起来,每次递的时候就将k-1,因为k是从第一层开始减起的,所以当k减到1的时候恰好就是第k层,那么就将k=1,作为结束条件,k=1就返回1,代表第k层的一个节点;当然,跟节点不能为NULL;

思路:返回左子树和右子树各自第k层的节点个数;

7、寻找二叉树中值为x的节点

// 二叉树查找值为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;
}

当递归调用到的函数立面的根节点的data值为x的时候就返回;当递归到根节点为NULL的时候就返回NULL,代表没有找到;

先判断根节点是不是,若是就直接返回根节点;若不是就先从左子树开始找起,若找到了那么ret1就是这个节点,并且每次递归回来的ret1收到的都是找到的节点,此时也不用遍历右子树;若左子树里面没有再去右子树里面去找,没有就返回NULL;

在二叉树里面利用到递归的时候,可以先从只含有一个节点的二叉树入手去分析问题;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值