1,二叉树的概念
二叉树是一种重要的数据结构,它由节点组成,每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树具有以下特点:
-
**根节点(Root)**:二叉树的顶端节点称为根节点,它没有父节点,是树的起始点。
-
**父节点、子节点**:除了根节点外,每个节点都有一个父节点,以及最多两个子节点。这些子节点分别称为左子节点和右子节点。
-
**叶子节点(Leaf Node)**:没有子节点的节点称为叶子节点,也称为终端节点。
-
**深度(Depth)**:从根节点到某个节点的唯一路径的边数称为该节点的深度。根节点的深度为0。
-
**高度(Height)**:从某个节点到其子树中的最远叶子节点的最长路径的边数称为该节点的高度。叶子节点的高度为0。
-
**子树(Subtree)**:树中的任意节点及其子节点(包括该节点)构成的树称为原树的子树。
-
**二叉搜索树(Binary Search Tree)**:二叉搜索树是一种特殊的二叉树,其中每个节点的值大于其左子树中的所有节点的值,且小于其右子树中的所有节点的值。这个性质使得二叉搜索树具有高效的查找、插入和删除操作。
-
**满二叉树(Full Binary Tree)**:除了叶子节点外,每个节点都有两个子节点的二叉树称为满二叉树。
-
**完全二叉树(Complete Binary Tree)**:对于深度为d的二叉树,除了第d层之外,其他层的节点都必须是满的,并且第d层的节点从左到右填入,称为完全二叉树。
二叉树在计算机科学中有广泛的应用,包括在数据结构、算法、数据库等领域。它们提供了一种灵活且高效的方式来组织和处理数据。
2,二叉树的分类,以及实用的二叉树结构
二叉树可以根据其性质和结构进行多种分类,以下是一些常见的分类方式:
- **按照节点数分类**:
-
完全二叉树:除了最后一层,其他层的节点都是满的,最后一层节点从左到右填充,没有间隙。
-
满二叉树:每一层的节点都是满的,即每个节点都有两个子节点。
-
完美二叉树:所有叶子节点都在同一层,且每个非叶子节点都有两个子节点。
- **按照节点连接方式分类**:
-
完全二叉树:所有节点都是从左向右依次连接的,没有跳过的空缺。
-
斜树:所有节点都只有一侧的子节点,分为左斜树和右斜树。
- **按照平衡性分类**:
-
平衡二叉树(Balanced Binary Tree):任意节点的两个子树的高度差不超过1。
-
不平衡二叉树:存在节点的两个子树的高度差超过1。
- **按照搜索特性分类**:
-
二叉搜索树(Binary Search Tree,BST):左子树上所有节点的值均小于根节点的值,右子树上所有节点的值均大于根节点的值。
-
平衡二叉搜索树(如AVL树、红黑树等):是二叉搜索树的一种,且具有良好的平衡性能。
- **按照存储结构分类**:
-
链式存储结构:每个节点包含数据以及指向左右子节点的指针。
-
数组存储结构:使用数组来表示二叉树,通常用于完全二叉树。
在实践中,常用的二叉树结构包括普通二叉树、二叉搜索树(BST)、AVL树、红黑树等。每种结构都有其特定的应用场景和性能特点,选择合适的二叉树结构取决于具体的问题需求。例如,二叉搜索树适用于需要频繁进行查找、插入和删除操作的场景,而平衡二叉搜索树则保证了更好的性能和稳定性。
3,二叉树前中后序的概念
二叉树的前序、中序和后序遍历是常见的树遍历方式,它们指的是在遍历二叉树时访问节点的顺序不同。这三种遍历方式都是递归地进行的。
假设有一个二叉树如下所示:
前序遍历(Preorder Traversal)
前序遍历的顺序是:根节点 -> 左子树 -> 右子树。
在上面的例子中,前序遍历结果为:A -> B -> D -> E -> C。
中序遍历(Inorder Traversal)
中序遍历的顺序是:左子树 -> 根节点 -> 右子树。
在上面的例子中,中序遍历结果为:B -> D -> E -> A -> C。
后序遍历(Postorder Traversal)
后序遍历的顺序是:左子树 -> 右子树 -> 根节点。
在上面的例子中,后序遍历结果为:B -> D -> E -> C -> A。
这些遍历方式在不同的情况下有不同的应用场景,例如:
-
前序遍历适合用于复制一棵树。
-
中序遍历适合用于表达式树求解。
-
后序遍历适合用于释放一棵树的所有节点。
以上是递归实现的遍历方式,也可以使用迭代的方法实现。
4,实现二叉树的前中后序遍历
结构体
//存储数据的类型
typedef char BTdatatype;
//树的结构
typedef struct BintaryTree
{
struct BintaryTree* left;//左子结点
struct BintaryTree* right;//右子节点
BTdatatype data;//存储数据结构
}BTree;
函数声明
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
//存储数据的类型
typedef char BTdatatype;
//树的结构
typedef struct BintaryTree
{
struct BintaryTree* left;//左子结点
struct BintaryTree* right;//右子节点
BTdatatype data;//存储数据结构
}BTree;
//前序遍历
void prevOrder(BTree* root);
//中序遍历
void InOrder(BTree* root);
//后序遍历
void QueenOrder(BTree* root);
//计算二叉树中节点的数量,包括根节点在内。
int TreeSize(BTree* root);
//叶子节点的数量
int TreeLeafSize(BTree* root);
前序遍历
//前序遍历适合用于复制一棵树
void prevOrder(BTree* root)
{
//遇到根节点为空就返回
if (root == NULL)
{
printf("->NULL ");
return;
}
//打印节点数据,一边更好观察
printf("%c-> ",root->data);
//根节点->左节点->右节点
prevOrder(root->left);
prevOrder(root->right);
}
中序遍历
//中序遍历适合用于表达式树求解。
void InOrder(BTree* root)
{
//遇到根节点为空就返回
if (root == NULL)
{
printf("->NULL ");
return;
}
//左节点->根节点->右节点
prevOrder(root->left);
printf("%c-> ", root->data);
prevOrder(root->right);
}
后序遍历
//后序遍历适合用于释放一棵树的所有节点。
void QueenOrder(BTree* root)
{
//遇到根节点为空就返回
if (root == NULL)
{
printf("->NULL ");
return;
}
//左节点->右节点->根节点
prevOrder(root->left);
prevOrder(root->right);
printf("%c-> ", root->data);
}
中节点的数量
//计算二叉树中节点的数量,包括根节点在内。
int TreeSize(BTree* root)
{
/*
1. 如果输入的根节点 `root` 为空(即指向 `NULL`),说明这棵树是空树,此时返回节点数量为0。
2. 如果根节点不为空,则递归地计算左子树和右子树的节点数量,并将它们相加。
同时,根节点本身也算作一个节点,所以要额外加1。
3. 最终返回的值就是整棵树的节点数量。
这种实现利用了递归的思想,通过不断地向下递归计算子树的节点数量,最终得到整棵树的节点数量。
*/
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
叶子节点的数量
//叶子节点的数量
int TreeLeafSize(BTree* root)
{
if (root == NULL)
{
return 0;
}
//没有子节点的节点称为叶子节点,也称为终端节点。
//所以我们只需要确定有一个节点的左子节点和右子节点都为空就返回1
if (root->left == NULL && root->right == NULL)
{
return 1;
}
//最后将遍历后得到的左右子节点数相加
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
二叉树
//申请根节点
BTree* root = (BTree*)malloc(sizeof(BTree));
root->data = 'A';
root->left = NULL;
root->right = NULL;
//左节点
BTree* root_left_chind_node1 = (BTree*)malloc(sizeof(BTree));
root_left_chind_node1->data = 'B';
root_left_chind_node1->left = NULL;
root_left_chind_node1->right = NULL;
//右节点
BTree* root_right_chind_node2 = (BTree*)malloc(sizeof(BTree));
root_right_chind_node2->data = 'C';
root_right_chind_node2->left = NULL;
root_right_chind_node2->right = NULL;
BTree* root_left_chind_node3 = (BTree*)malloc(sizeof(BTree));
root_left_chind_node3->data = 'D';
root_left_chind_node3->left = NULL;
root_left_chind_node3->right = NULL;
BTree* root_right_chind_node4 = (BTree*)malloc(sizeof(BTree));
root_right_chind_node4->data = 'E';
root_right_chind_node4->left = NULL;
root_right_chind_node4->right = NULL;
root->left = root_left_chind_node1;
root->right = root_right_chind_node2;
root_left_chind_node1->left = root_left_chind_node3;
root_left_chind_node1->right = root_right_chind_node4;
验证接口正确性
//前序遍历,打印
printf("前序遍历:");
prevOrder(root);
printf("\n");
//中序遍历,打印
printf("中序遍历:");
InOrder(root);
printf("\n");
//后序遍历,打印
printf("后序遍历:");
QueenOrder(root);
printf("\n");
printf("节点数:[%d] ", TreeSize(root));
printf("第一个左节点数:[%d] ", TreeSize(root_left_chind_node1));
printf("子节点数:[%d] ", TreeLeafSize(root));
5,每期一问
上期答案
bool hasCycle(struct ListNode *head) {
//链表为空或者只有一节点,必然不成环
if(!head || !head->next)
{
return false;
}
//创建一个快指针和一个慢指针
struct ListNode * fast = head->next;
struct ListNode * slow = head;
//快指针走两步,慢指针走一步,
//当快指针走到空或者快指针的next为空都不成环
//当慢指针和快指针相等时,成环
while(fast && fast->next && fast != slow)
{
fast = fast->next->next;
slow = slow->next;
}
//当快指针走到空或者快指针的next为空都不成环,返回false
if(!fast || !fast->next)
{
return false;
}
//否则返回true
return true;
}
本期问题