[数据结构]二叉树详解

目录

一、二叉树概念及结构

1.1概念

1.2现实中的二叉树:

[1.3 特殊的二叉树:](#1.3 特殊的二叉树:)

[1.4 二叉树的存储结构](#1.4 二叉树的存储结构)

[1. 顺序存储](#1. 顺序存储)

[2. 链式存储](#2. 链式存储)

二、二叉树的顺序结构及实现

[2.1 二叉树的顺序结构](#2.1 二叉树的顺序结构)

三、二叉树链式结构的实现

[3.1 前置说明](#3.1 前置说明)

3.2二叉树的遍历

3.2.1前序、中序以及后序遍历

①前序遍历递归图解:

②中序遍历

③后序遍历

3.2.2二叉树的层序遍历

层序遍历主要代码:

层序遍历全部代码:

3.2.3求一棵树的大小

3.2.4求一棵树的高度

3.2.5二叉树第k层节点个数

[3.2.6二叉树查找值为x的节点 BTNode* BinaryTreeFind(BTNode* root, BTDataType x);](#3.2.6二叉树查找值为x的节点 BTNode* BinaryTreeFind(BTNode* root, BTDataType x);)

递归展开图:

3.2.7代码汇总


一、二叉树概念及结构

1.1概念

一棵二叉树是结点的一个有限集合,该集合 :

  1. 或者为空
  2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成

    从上图可以看出:
  3. 二叉树不存在度大于 2 的结点
  4. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
    注意:对于任意的二叉树都是由以下几种情况复合而成的:

1.2现实中的二叉树:

1.3 特殊的二叉树:

  1. 满二叉树 :一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K ,且结点总数是2^k-1 ,则它就是满二叉树。(通俗来讲就是除了最后一层,每个人都有两个娃)
  2. 完全二叉树 :完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为 K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 至 n 的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。(通俗来讲如果有h层,那么h-1层都是满的,并且最后一层要求从左到右必须是连续的)

    高度为h的完全二叉树,节点数量的范围:[ 2^(h-1) , 2^h-1 ]
    对于任何一颗二叉树,如果度为0其叶节点个数为n0,度为2的分支结点个数为n2,则n0=n2+1(通俗来讲就是度为0的永远比度为2的多一个)。可以仔细观察,当有一个空树,此时n0=1,n2=0,后续可以观察,每增加一个n2就会增加一个n0

1.4 二叉树的存储结构

1. 顺序存储

顺序结构存储就是使用 数组来存储 ,一般使用数组 只适合表示完全二叉树 ,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺 序存储在物理上是一个数组,在逻辑上是一颗二叉树。

2. 链式存储

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面课程学到高阶数据结构如红黑树等会用到三叉链。

二、二叉树的顺序结构及实现

2.1 二叉树的顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆 ( 一种二叉树 )使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

三、二叉树链式结构的实现

3.1 前置说明

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于二
叉树比较难,为了降低学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,反过头再来研究二叉树真正的创建方式。
那么二叉树相比链表存储有什么意义?->普通二叉树单独拎出来是没有任何意义的
普通二叉树加上一个性质就很有意义了->搜索二叉树(左子树的值比父节点要小,右子树要大),当然搜索二叉树也有问题,后面衍生出了红黑树

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
       BTDataType data;
       struct BinaryTreeNode* left;
       struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(BTDataType x)
{
       BTNode* node = (BTNode*)malloc(sizeof(BTNode));
       if (node == NULL)
       {
              perror("malloc fail");
       }
       node->data = x;
       node->left = NULL;
       node->right = NULL;
       return node;
}
BTNode* CreatTree()
{
       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;
}
int main()
{
       BTNode* root = CreatTree();
       return 0;
}

在看二叉树基本操作前,再回顾下二叉树的概念, 二叉树是:

  1. 空树
  2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

    从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

3.2二叉树的遍历

3.2.1前序、中序以及后序遍历

  1. 前序遍历 (Preorder Traversal 亦称先序遍历 )------根、左子树、右子树 。

  2. 中序遍历 (Inorder Traversal)------左子树、根、右子树 。

  3. 后序遍历 (Postorder Traversal)------左子树、右子树、根 。

    // 二叉树前序遍历
    void PreOrder(BTNode* root);
    // 二叉树中序遍历
    void InOrder(BTNode* root);
    // 二叉树后序遍历
    void PostOrder(BTNode* root);

下面主要分析前序递归遍历,中序与后序图解类似,同学们可自己动手绘制。

①前序遍历递归图解:
// 二叉树前序遍历
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;
       }
       PreOrder(root->left);
       printf("%d ", root->data);
       PreOrder(root->right);
}
③后序遍历
// 二叉树后序遍历
void PostOrder(BTNode* root)
{
       if (root == NULL)
       {
              printf("NULL ");
              return;
       }
       PreOrder(root->left);
       PreOrder(root->right);
       printf("%d ", root->data);
}

3.2.2二叉树的层序遍历

核心思想:用队列,出上一层带入下一层

不能把1的值带进队列,这样不好把1的左右孩子带进去,所以得存1的结构体,但是结构体比较大,所以存1的结构体的指针

层序遍历主要代码:

test.c中的主要代码

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

void LevelOrder(BTNode* root)
{
       Queue q;
       QueueInit(&q);
       if (root)
       {
              QueuePush(&q, root);
       }
       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);
              }
       }
}

Queue.h中的主要代码

typedef struct BinaryTreeNode* QDataType;
// 链式结构:表示队列
typedef struct QueueNode
{
    struct QListNode* next;
    QDataType data;
}QNode;
层序遍历全部代码:

Queue.h

#pragma once
#pragma once
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include<stdbool.h>
#include"test.c"
typedef struct BinaryTreeNode* QDataType;
// 链式结构:表示队列
typedef struct QueueNode
{
    struct QListNode* next;
    QDataType data;
}QNode;
// 队列的结构
typedef struct Queue
{
    QNode* head;
    QNode* tail;
    int size;
}Queue;
// 初始化队列
void QueueInit(Queue* pq);
// 队尾入队列
void QueuePush(Queue* pq, QDataType x);
// 队头出队列
void QueuePop(Queue* pq);
// 获取队列头部元素
QDataType QueueFront(Queue* pq);
// 获取队列队尾元素
QDataType QueueBack(Queue* pq);
// 获取队列中有效元素个数
int QueueSize(Queue* pq);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);

Queue.c

#pragma once
#pragma once
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include<stdbool.h>
#include"test.c"
typedef struct BinaryTreeNode* QDataType;
// 链式结构:表示队列
typedef struct QueueNode
{
    struct QListNode* next;
    QDataType data;
}QNode;
// 队列的结构
typedef struct Queue
{
    QNode* head;
    QNode* tail;
    int size;
}Queue;
//front rear为什么不能放在struct QListNode里面
//冗余存储:
//每个 QListNode 节点都会包含 front 和 rear 指针,这会导致大量的冗余存储。实际上,front   和 rear 只需要存储一次,而不是在每个节点中都存储。
//
//不一致性:
//如果每个节点都包含 front 和 rear 指针,那么当队列发生变化时(如入队或出队操作),需要更新所有节点的 front 和 rear 指针,这会导致数据不一致性和额外的复杂性。
//
//逻辑分离:
//QListNode 结构体的目的是表示队列中的一个节点,而 front 和 rear 指针是用于管理整个队列的。将它们放在 QListNode 中会混淆节点的表示和队列的管理逻辑。
// 初始化队列
void QueueInit(Queue* pq);
// 队尾入队列
void QueuePush(Queue* pq, QDataType x);
// 队头出队列
void QueuePop(Queue* pq);
// 获取队列头部元素
QDataType QueueFront(Queue* pq);
// 获取队列队尾元素
QDataType QueueBack(Queue* pq);
// 获取队列中有效元素个数
int QueueSize(Queue* pq);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#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;
BTNode* BuyNode(BTDataType x)
{
       BTNode* node = (BTNode*)malloc(sizeof(BTNode));
       if (node == NULL)
       {
              perror("malloc fail");
       }
       node->data = x;
       node->left = NULL;
       node->right = NULL;
       return node;
}
BTNode* CreatTree()
{
       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;
}
// 二叉树前序遍历
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);
       PostOrder(root->right);
       printf("%d ", root->data);
}
//求一颗树的大小
int TreeSize(BTNode* root)
{
       return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
//求一颗树的高度
int TreeHeight(BTNode* root)
{
       if (root == NULL)
              return 0;
       int leftHeight = TreeHeight(root->left);
       int rightHeight = TreeHeight(root->right);
       return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
// 二叉树第k层节点个数
int TreeKLevel(BTNode* root, int k)
{
       assert(k > 0);
       if (root == 0)
       {
              return 0;
       }
       if (k == 1)
       {
              return 1;
       }
       int leftk = TreeKLevel(root->left, k - 1);
       int rightk=TreeKLevel(root->right, k - 1);
       return leftk + rightk;
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
       if (root == NULL)
       {
              return NULL;
       }
       if (root->data == x)
       {
              return root;
       }
       BTNode* lret=BinaryTreeFind(root->left,x);
       if (lret)//如果左数找到了,那么返回,没找到返回空
       {
              return lret;
       }
       BTNode* rret = BinaryTreeFind(root->right, x);
       if (rret)//如果左数找到了,那么返回,没找到返回空
       {
              return rret;
       }
       
       return NULL;
}
       void LevelOrder(BTNode* root)
       {
              Queue q;
              QueueInit(&q);
              if (root)
              {
                      QueuePush(&q, root);
              }
              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);
                      }
              }
       }
int main()
{
       BTNode* root = CreatTree();
       BTNode* ret = BinaryTreeFind(root, 5);
       int k=TreeKLevel(root,3);
       printf("%d", k);
       return 0;
}

3.2.3求一棵树的大小

方法一:

//求一颗树的大小
int TreeSize(BTNode* root,int *psize)
{
       if (root == NULL)
              return;
       ++(*psize);
       TreeSize(root->left,psize);
       TreeSize(root->right,psize);
}

方法二:

//求一颗树的大小
int TreeSize(BTNode* root)
{
       return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

3.2.4求一棵树的高度

//求一颗树的高度
int TreeHeight(BTNode* root)
{
       if (root == NULL)
              return 0;
       int leftHeight = TreeHeight(root->left);
       int rightHeight = TreeHeight(root->right);
       return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

3.2.5二叉树第k层节点个数

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

3.2.6二叉树查找值为x的节点 BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
       if (root == NULL)
       {
              return NULL;
       }
       if (root->data == x)
       {
              return root;
       }
       BTNode* lret=BinaryTreeFind(root->left,x);
       if (lret)//如果左数找到了,那么返回,没找到返回空
       {
              return lret;
       }
       BTNode* rret = BinaryTreeFind(root->right, x);
       if (rret)//如果左数找到了,那么返回,没找到返回空
       {
              return rret;
       }
       
       return NULL;
}
递归展开图:

3.2.7代码汇总

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
       BTDataType data;
       struct BinaryTreeNode* left;
       struct BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(BTDataType x)
{
       BTNode* node = (BTNode*)malloc(sizeof(BTNode));
       if (node == NULL)
       {
              perror("malloc fail");
       }
       node->data = x;
       node->left = NULL;
       node->right = NULL;
       return node;
}
BTNode* CreatTree()
{
       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;
}
// 二叉树前序遍历
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);
       PostOrder(root->right);
       printf("%d ", root->data);
}
//求一颗树的大小
int TreeSize(BTNode* root)
{
       return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
//求一颗树的高度
int TreeHeight(BTNode* root)
{
       if (root == NULL)
              return 0;
       int leftHeight = TreeHeight(root->left);
       int rightHeight = TreeHeight(root->right);
       return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
// 二叉树第k层节点个数
int TreeKLevel(BTNode* root, int k)
{
       assert(k > 0);
       if (root == 0)
       {
              return 0;
       }
       if (k == 1)
       {
              return 1;
       }
       int leftk = TreeKLevel(root->left, k - 1);
       int rightk=TreeKLevel(root->right, k - 1);
       return leftk + rightk;
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
       if (root == NULL)
       {
              return NULL;
       }
       if (root->data == x)
       {
              return root;
       }
       BTNode* lret=BinaryTreeFind(root->left,x);
       if (lret)//如果左数找到了,那么返回,没找到返回空
       {
              return lret;
       }
       BTNode* rret = BinaryTreeFind(root->right, x);
       if (rret)//如果左数找到了,那么返回,没找到返回空
       {
              return rret;
       }
       
       return NULL;
}
int main()
{
       BTNode* root = CreatTree();
       BTNode* ret = BinaryTreeFind(root, 5);
       int k=TreeKLevel(root,3);
       printf("%d", k);
       return 0;
}
相关推荐
win水7 分钟前
数据结构(初阶)(八)----排序
数据结构·算法·排序算法
wen__xvn8 分钟前
每日一题蓝桥杯P8598 [蓝桥杯 2013 省 AB] 错误票据c++
开发语言·数据结构·c++·算法
小王C语言15 分钟前
【数据结构初阶】---时间复杂度和空间复杂度了解及几道相关OJ题
java·数据结构·算法
灰灰学姐25 分钟前
Floyd算法——有向图
数据结构·算法·图论
三水气象台1 小时前
对ArrayList中存储的TreeNode的排序回顾
java·数据结构·算法·huffman tree
疯狂的代M夫5 小时前
数据结构【AVL树(平衡二叉树)】
数据结构
张二娃同学5 小时前
数据结构篇—队列(queue)
数据结构·c++
猫头鹰~5 小时前
Redis数据结构——list
数据结构·数据库·redis
梅茜Mercy6 小时前
数据结构:八大排序(冒泡,堆,插入,选择,希尔,快排,归并,计数)详解
数据结构
神秘的t6 小时前
优选算法合集————双指针(专题三)
java·数据结构·算法·二分查找