数据结构·二叉树

目录

续上篇 -- 数据结构·堆·完全二叉树

二叉树的性质

再次认知二叉树

二叉树的遍历

附上队列的实现

Queue.c

Queue.h

Test.c

解析一 

解析二 

解析三 

深度优先与广度优先

关于二叉树的一些oj题目

965. 单值二叉树

100. 相同的树

572. 另一棵树的子树

144. 二叉树的前序遍历

94. 二叉树的中序遍历

145. 二叉树的后序遍历

KY11 二叉树遍历

101. 对称二叉树

结束语 


续上篇 -- 数据结构·堆·完全二叉树

二叉树的性质

几个关于性质的题目

1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )
A 不存在这样的二叉树
B 200
C 198
D 199
2. 下列数据结构中,不适合采用顺序存储结构的是( )
A 非完全二叉树
B
C 队列
D
3. 在具有 2n 个结点的完全二叉树中,叶子结点个数为( )
A n
B n+1
C n-1
D n/2
4. 一棵完全二叉树的节点数位为 531 个,那么这棵树的高度为( )
A 11
B 10
C 8
D 12
5. 一个具有 767 个节点的完全二叉树,其叶子节点个数为()
A 383
B 384
C 385
D 386
答案 -- 选择查看
1.B  -- 根据性质第3条很容易得出 -- 叶子指的是度为0结点
2.A  -- 这里的队列其实也不应该使用顺序存储,但是相比A来说就离谱了 -- 看补图
3.A  -- 2n就一定是非满二叉树了,所以有一个度为1的结点,再利用性质3可以得出 
4.B  -- 对于高度h的计算公式:2^(h-1) <= N <= 2^h-1 
5.B  -- 根据二叉树的性质这个一定是满二叉树,所以没有度为1的结点,再利用性质3可以得出 

对于2的补图

再次认知二叉树

基于上述才可以引入 二叉树的遍历 这一概论

二叉树的遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

按照规则,二叉树的遍历有: 前序 / 中序 / 后序的递归结构遍历
1. 前序遍历 (Preorder Traversal 亦称先序遍历 )—— 访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历 (Postorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之后。

根 左子树 右子树             -- 1概述

左子树 根 右子树             -- 2概述

左子树 右子树 根             -- 3概述

为了后续更好的理解二叉树的实现,下面写一个简单的只遍历的测试用例

附上队列的实现

数据结构·栈与队列介绍与实现

Queue.c

#include "Queue.h"

//初始化  -- 到现在有三种不用二级指针来初始化结构体了 返回值、头节点
void QueueInit(Queue* pq)
{
	assert(pq);	//因为这里是自己创立的节点是不可能为空的,为空就是出问题了
	pq->head = 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);
	}

	pq->head = pq->tail = NULL;	//要改变什么就需要什么的指针,这里置空就可以了
}

//入队列 -- 根据队列的性质 只尾插
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	else
	{
		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->tail = pq->head = NULL;
	}
	else
	{
		QNode* del = pq->head;
		pq->head = pq->head->next;

		free(del);
		del = NULL;
	}

	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)
{
	assert(pq);

	return pq->head == NULL && pq->tail == NULL; //理论上两个都应该为空才为空,一般一个为空就都会为空,不是就出问题了
}

//查看节点长度
int QueueSize(Queue* pq)
{
	assert(pq);

	//这样写就变为O(N)级别了 为此需要稍作修改
	//QNode* cur = pq->head;
	//int n = 0;
	//while (cur)
	//{
	//	++n;
	//	cur = cur->next;
	//}

	//return n;

	return pq->size;
}

Queue.h

#pragma once

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




typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;


//能用单链表解决问题就可以用单链表 -- 没必要使用双向循环链表,这属于有点小题大做了

//用C写的话就得把 -- 之前写的队列用上
typedef BTNode* QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

//再定义一个节点来存放两个指针,方便后面操作
typedef struct Queue
{
	//尾指针
	QNode* tail;
	//头指针
	QNode* head;
	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);

//查看节点长度
int QueueSize(Queue* pq);

Test.c

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include "Queue.h"

//typedef int BTDataType;
//typedef struct BinaryTreeNode
//{
//	BTDataType data;
//	struct BinaryTreeNode* left;
//	struct BinaryTreeNode* right;
//}BTNode;


// 二叉树前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}
// 二叉树中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}
// 二叉树后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	printf("%d ", root->data);
	PostOrder(root->right);
}
//会发现其实遍历的代码很简单,只是理解起来要复杂


//遍历计算的思路
//int count = 0;
//void TreeSize_1(BTNode* root)
//{
//	if (root == NULL)
//		return;
//
//	++count;
//	TreeSize_1(root->left);
//	TreeSize_1(root->right);
//
//	return;
//}

//层序遍历 -- 用C的方法就得用到队列 -- 解析二
void TreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	
	if (root)
		QueuePush(&q, root);	//QueueNode* 里面包含一个BTNode*的指针

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%d ", front->data);

		//下一层入队列
		if(front->left)
			QueuePush(&q, front->left);
		if (front->right)
			QueuePush(&q, front->right);
	}
	printf("\n");

	QueueDestroy(&q);
}



//二叉树的结点数 -- 切割子问题的思路
int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
	//解释起来就是:左子树 + 右子树 + 根
}

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

	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

//二叉树的高度 -- 解析一
int TreeHeight(BTNode* root)
{
	if (root == NULL)
		return 0;

	int lh = TreeHeight(root->left);
	int rh = TreeHeight(root->right);

	return	lh > rh ? lh + 1 : rh + 1;
}

//第k层节点个数
int TreeKlevel(BTNode* root, int k)
{
	assert(k > 0);

	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;

	//转化成求子树第k-1层
	return TreeKlevel(root->left, k-1) + TreeKlevel(root->right, k-1);
}

//返回x所在的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;

	BTNode* lret = TreeFind(root->left, x);
	if (lret)
		return lret;
	BTNode* rret = TreeFind(root->right, x);
	if (rret)
		return rret;

	return NULL;
}


//判断是否为完全二叉树 -- 利用队列的方法
int BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);

	if (root)
		QueuePush(&q, root);	//QueueNode* 里面包含一个BTNode*的指针

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		//遇到空就跳出循环 -- 解析3
		if (front == NULL)
			break;

		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}
	
	//遇到空以后,后面全是空,则是完全二叉树
	//遇到空以后,后面存在非空,则不是完全二叉树
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		//遇到空就跳出循环 -- 解析3
		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}

	}

	QueueDestroy(&q);
	return true;
}


BTNode* CreateTree()
{
	BTNode* n1 = (BTNode*)malloc(sizeof(BTNode));
	assert(n1);
	BTNode* n2 = (BTNode*)malloc(sizeof(BTNode));
	assert(n2);
	BTNode* n3 = (BTNode*)malloc(sizeof(BTNode));
	assert(n3);
	BTNode* n4 = (BTNode*)malloc(sizeof(BTNode));
	assert(n4);
	BTNode* n5 = (BTNode*)malloc(sizeof(BTNode));
	assert(n5);
	BTNode* n6 = (BTNode*)malloc(sizeof(BTNode));
	assert(n6);
	BTNode* n7 = (BTNode*)malloc(sizeof(BTNode));
	assert(n7);

	n1->data = 1;
	n2->data = 2;
	n3->data = 3;
	n4->data = 4;
	n5->data = 5;
	n6->data = 6;

	//为测试高度添加的一个结点
	n7->data = 7;

	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n2->right = NULL;
	n4->left = n5;
	n4->right = n6;
	n3->left = NULL;
	n3->right = NULL;
	n5->left = NULL;
	n5->right = NULL;
	n6->left = NULL;
	n6->right = NULL;

	//为测试高度添加的一个结点
	n3->right = n7;
	n7->left = NULL;
	n7->right = NULL;

	return n1;
}


//二叉树的销毁 -- 后序销毁
void BinaryTreeDestroy(BTNode* root)
{
	//当左右子树都为空时就释放
	if (root == NULL)
		return;

	BinaryTreeDestroy(root->left);
	BinaryTreeDestroy(root->right);
	free(root);
}

int main()
{
	BTNode* root = CreateTree();
	//前序遍历
	PreOrder(root);
	printf("\n");

	//中序遍历
	InOrder(root);
	printf("\n");

	//后序遍历
	PostOrder(root);
	printf("\n");

	//遍历计算
	//先归零再计算,因为是全局变量
	//count = 0;	
	//TreeSize_1(root);
	//printf("Tree size:%d\n", count);

	//结点数
	printf("Tree size:%d\n", TreeSize(root));

	//叶子数
	printf("Tree Leaf size:%d\n", TreeLeafSize(root));

	//高度
	printf("Tree Height size:%d\n", TreeHeight(root));

	//第k层节点个数
	printf("Tree K Level:%d\n", TreeKlevel(root, 4));

	//返回x所在的节点
	printf("Tree Find:%p\n", TreeFind(root, 7));
	//有了节点的地址可以改该节点的值
	BTNode* p = TreeFind(root, 7);
	p->data *= 10;
	//前序遍历
	PreOrder(root);
	printf("\n");

	//层序遍历
	TreeLevelOrder(root);

	//判断是否为完全二叉树 -- true 与 false 本质上就是1与0
	printf("Tree BinaryTreeComplete:%d\n", BinaryTreeComplete(root));

	//因为里面没有实现置空,所以在外部需要置空 -- 要实现置空得使用二级指针
	BinaryTreeDestroy(root);
	root = NULL;

	return 0;
}

 

结果与前面分析是一致的 

解析一 

分析高度的递归路线

其他的递归路线大同小异,画图便自然理解

解析二 

分析层序遍历 -- 利用队列的方法去实现

 

解析三 

完全二叉树与非完全二叉树的解析 

深度优先与广度优先

DFS -- 深度优先遍历

前序遍历 -- 遍历顺序和访问数据都符合

中序遍历 -- 遍历符合

后序遍历 -- 遍历符合

递归先往深走,遇到不满足条件再往回退,走其他分支路径 -- 往一个方向走

BFS -- 广度优先遍历

层序遍历 

关于二叉树的一些oj题目

965. 单值二叉树

965. 单值二叉树 -- leetcode链接

递归解决 -- 让子树去比较

bool isUnivalTree(struct TreeNode* root){
    if(root == NULL)
        return true;
    if(root->left && root->val != root->left->val)
        return false;
    if(root->right && root->val != root->right->val)
        return false;

    return isUnivalTree(root->left) && isUnivalTree(root->right);
}

100. 相同的树

100. 相同的树  -- -- leetcode链接

bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    
    if(p == NULL && q == NULL)
        return true;
    if(p == NULL || q == NULL)
        return false;
    if(p->val != q->val)
        return false;
    return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
}

572. 另一棵树的子树

572. 另一棵树的子树  -- leetcode链接

上面一题刚好可以为下面一题服务 

bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    
    if(p == NULL && q == NULL)
        return true;
    if(p == NULL || q == NULL)
        return false;
    if(p->val != q->val)
        return false;
    return isSameTree(p->left,q->left) && isSameTree(p->right,q->right);
}


bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    if(root == NULL)
        return false;

    if(isSameTree(root,subRoot))
        return true;

    return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);

}

144. 二叉树的前序遍历

144. 二叉树的前序遍历  -- -- leetcode链接

//查看节点数
int TreeSize(struct TreeNode* root)
{
    if(root == NULL)
        return 0;
    return TreeSize(root->left) + TreeSize(root->right) + 1;
}

//前序遍历
void preorder(struct TreeNode* root, int* a, int* pi)
{
    if(root == NULL)
        return;
    
    a[*pi] = root->val;
    (*pi)++;

    preorder(root->left, a, pi);
    preorder(root->right, a, pi);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize){
    
    int n = TreeSize(root);
    int* a = (int*)malloc(sizeof(int)*n);

    int i = 0;
    preorder(root, a, &i);
    //这里是返回数组的大小,因为C不支持一次返回两个值,利用输出型参数可以解决
    *returnSize = n;

    return a;
}

94. 二叉树的中序遍历

94. 二叉树的中序遍历  -- leetcode链接

//查看节点数
int TreeSize(struct TreeNode* root)
{
    if(root == NULL)
        return 0;
    return TreeSize(root->left) + TreeSize(root->right) + 1;
}

//中序遍历
void preorder(struct TreeNode* root, int* a, int* pi)
{
    if(root == NULL)
        return;
    
    preorder(root->left, a, pi);
    a[*pi] = root->val;
    (*pi)++;
    preorder(root->right, a, pi);
}

int* inorderTraversal(struct TreeNode* root, int* returnSize){
    
    int n = TreeSize(root);
    int* a = (int*)malloc(sizeof(int)*n);

    int i = 0;
    preorder(root, a, &i);
    //这里是返回数组的大小,因为C不支持一次返回两个值,利用输出型参数可以解决
    *returnSize = n;

    return a;
}

145. 二叉树的后序遍历

145. 二叉树的后序遍历  -- leetcode链接

//查看节点数
int TreeSize(struct TreeNode* root)
{
    if(root == NULL)
        return 0;
    return TreeSize(root->left) + TreeSize(root->right) + 1;
}

//后序遍历
void preorder(struct TreeNode* root, int* a, int* pi)
{
    if(root == NULL)
        return;
    
    preorder(root->left, a, pi);
    preorder(root->right, a, pi);
    a[*pi] = root->val;
    (*pi)++;
}

int* postorderTraversal(struct TreeNode* root, int* returnSize){
    
    int n = TreeSize(root);
    int* a = (int*)malloc(sizeof(int)*n);

    int i = 0;
    preorder(root, a, &i);
    //这里是返回数组的大小,因为C不支持一次返回两个值,利用输出型参数可以解决
    *returnSize = n;

    return a;
}

KY11 二叉树遍历

二叉树遍历_牛客题霸_牛客网 (nowcoder.com) -- 题目链接

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

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

// 通过前序遍历的数组"abc##de#g##f###"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
    if(a[*pi] == '#')
    {
         (*pi)++;
         return NULL;
    }
       
    
    BTNode* root = (BTNode*)malloc(sizeof(BTNode));
    
    root->data = a[*pi];
    (*pi)++;
    
    root->left = BinaryTreeCreate(a, pi);
    root->right = BinaryTreeCreate(a, pi);
    
    return root;
}


int main()
{
    char str[100] = {0};
    scanf("%s",str);
    
    int i = 0;
    BTNode* root =  BinaryTreeCreate(str, &i);
    assert(root);
    
    InOrder(root);
    
    return 0;
}

101. 对称二叉树

101. 对称二叉树  -- leetcode链接

bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    
    if(p == NULL && q == NULL)
        return true;
    if(p == NULL || q == NULL)
        return false;
    if(p->val != q->val)
        return false;
    return isSameTree(p->left,q->right) && isSameTree(p->right,q->left);
}


bool isSymmetric(struct TreeNode* root){
    if(root == NULL)
        return true;

    return isSameTree(root->left,root->right);
    
}

根据另一棵树的子树改编过来

结束语 

兔子不吃窝边草,窝边有草何必满山跑
                                                                ——俗语 

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清风玉骨

爱了!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值