目录
续上篇 -- 数据结构·堆·完全二叉树
二叉树的性质
![](https://img-blog.csdnimg.cn/dc5972573c244d69bfeed1f187167ed0.png)
几个关于性质的题目
1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )A 不存在这样的二叉树B 200C 198D 1992. 下列数据结构中,不适合采用顺序存储结构的是( )A 非完全二叉树B 堆C 队列D 栈3. 在具有 2n 个结点的完全二叉树中,叶子结点个数为( )A nB n+1C n-1D n/24. 一棵完全二叉树的节点数位为 531 个,那么这棵树的高度为( )A 11B 10C 8D 125. 一个具有 767 个节点的完全二叉树,其叶子节点个数为()A 383B 384C 385D 386
1.B -- 根据性质第3条很容易得出 -- 叶子指的是度为0结点2.A -- 这里的队列其实也不应该使用顺序存储,但是相比A来说就离谱了 -- 看补图3.A -- 2n就一定是非满二叉树了,所以有一个度为1的结点,再利用性质3可以得出4.B -- 对于高度h的计算公式:2^(h-1) <= N <= 2^h-15.B -- 根据二叉树的性质这个一定是满二叉树,所以没有度为1的结点,再利用性质3可以得出
对于2的补图
再次认知二叉树
基于上述才可以引入 二叉树的遍历 这一概论
二叉树的遍历
学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(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);
}
根据另一棵树的子树改编过来
结束语
兔子不吃窝边草,窝边有草何必满山跑
——俗语