一、树的概述
1、树的定义
树是一种非线性的数据结构,它由节点(Node)和边(Edge)组成。树的定义如下:
(1) 节点(Node):树中的每个元素称为节点,每个节点可以有零个或多个子节点。节点可以包含数据(payload)以及指向其子节点的指针。
(2) 根节点(Root):树中的顶层节点称为根节点,它是树的起点,没有父节点。
(3) 子节点(Children):树中每个节点可以有零个或多个子节点。父节点指向其子节点,子节点指向其父节点。
(4) 叶节点(Leaf):没有子节点的节点称为叶节点,也称为终端节点。
(5) 父节点(Parent):每个节点除了根节点外,都有一个父节点,父节点指向其子节点。
(6) 兄弟节点(Siblings):具有相同父节点的节点被称为兄弟节点。
(7) 子树(Subtree):树中的任意节点和它的后代节点构成一个子树。
(8) 深度(Depth):节点到根节点的边的数量称为节点的深度,根节点的深度为0。
(9) 高度(Height):节点到其最远叶节点的边的数量称为节点的高度。
树可以具有不同的特性和类型,例如二叉树、平衡树、二叉搜索树等。树的定义提供了一种组织数据的方式,使得数据的检索和插入操作更高效。树在许多应用中都有广泛的应用,包括文件系统、数据库索引、图算法等。
2、树的存储结构
数据结构中的树可以使用不同的存储结构来表示,其中最常见的有以下几种:
(1) 链式存储结构(Linked structure):每个节点使用一个数据结构来表示,节点包含数据以及指向其子节点的指针。每个节点通常还包含一个指向其父节点的指针(除了根节点)。通过这种方式,树的结构可以通过节点之间的指针链接来表示。链式存储结构的优点是易于实现和维护,适用于动态变化的树结构。
(2) 数组存储结构(Array structure):使用数组来表示树的结构。在数组中,每个元素存储了节点的数据,通过数组的索引关系来表示节点之间的层次关系。一般来说,数组中索引为i的元素的左子节点在索引2i处,右子节点在索引2i+1处。数组存储结构的优点是节省空间,适用于静态或已知大小的树结构。
选择适当的存储结构取决于树的特性、应用需求以及对空间和时间复杂度的要求。不同的存储结构在空间和时间复杂度上可能有不同的优劣,并且对于特定问题可能会有更高效的解决方案。因此,在实际应用中,需要根据具体情况选择最适合的存储结构。
二、二叉树
1、二叉树介绍
(1)二叉树及其性质
二叉树是一种特殊的树状数据结构,每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树可以为空树,或者由若干个节点组成,其中每个节点最多有一个父节点。
二叉树的性质包括以下几点:
【1】每个节点最多有两个子节点:二叉树中的每个节点最多有两个子节点,分别为左子节点和右子节点。如果节点没有子节点,则称为叶节点。
【2】左子树和右子树是二叉树:二叉树的子树也是二叉树,即左子树和右子树都是二叉树。这意味着可以通过递归方式定义和操作二叉树的结构。
【3】二叉树可以是空树:二叉树可以为空树,即没有任何节点。空树是一种特殊的二叉树,也是合法的二叉树。
【4】二叉树的高度:二叉树的高度是指从根节点到最远叶节点的路径上经过的节点数。空树的高度为0,只有根节点的二叉树的高度为1。
【5】二叉树的深度:二叉树的深度是指从根节点到当前节点的路径上经过的节点数。根节点的深度为0,深度为d的节点的子节点的深度为d+1。
【6】二叉树的遍历:二叉树的遍历是指按照一定的顺序访问二叉树的所有节点。常见的遍历方式包括前序遍历、中序遍历和后序遍历。前序遍历先访问根节点,然后按照左子树、右子树的顺序遍历;中序遍历先访问左子树,然后访问根节点,最后访问右子树;后序遍历先访问左子树,然后访问右子树,最后访问根节点。
【7】二叉搜索树:二叉搜索树是一种特殊的二叉树,其中每个节点的值大于其左子树的所有节点的值,小于其右子树的所有节点的值。通过这种有序性质,可以在二叉搜索树中高效地进行搜索、插入和删除操作。
【8】完全二叉树:完全二叉树是一种特殊的二叉树,除了最后一层的叶节点可能不满外,其他层的节点数都达到最大值,并且最后一层的叶节点从左到右连续排列。
【9】满二叉树:满二叉树是一种特殊的完全二叉树,每个节点都有两个子节点。满二叉树的叶节点只能出现在最后一层,且最后一层的叶节点数量为2的n次方。
以上是二叉树的一些基本概念及其性质。二叉树由于其简单的结构和丰富的性质,在计算机科学中被广泛应用,包括搜索算法、排序算法、图算法等领域。
(2) 二叉树的存储结构
二叉树的存储结构有多种方式,主要包括链式存储和顺序存储两种。
【1】链式存储:链式存储是最常见的二叉树存储方式,每个节点通过指针连接其左右子节点。每个节点通常包含三个字段:一个数据域用于存储节点的数据,一个指向左子节点的指针,一个指向右子节点的指针。使用链式存储结构的优点是易于插入和删除节点,但是访问节点需要通过指针遍历。
【2】顺序存储:顺序存储是将二叉树的节点按照某种方式存储在一个线性数组中。具体存储方式有两种:
- 完全二叉树顺序存储:对于完全二叉树,可以使用数组来存储。将根节点存储在数组的第一个位置,然后按照从上到下、从左到右的顺序依次存储其他节点。如果一个节点的索引是i,它的左子节点的索引是2i,右子节点的索引是2i+1。通过这种方式,可以利用数组的随机访问特性快速访问节点,但是插入和删除节点需要移动其他节点的位置。
- 二叉搜索树顺序存储:对于二叉搜索树,可以使用有序数组来存储。将节点按照中序遍历的顺序存储在数组中,这样可以保持节点的有序性。通过二分查找的方式可以快速定位节点,但是插入和删除节点需要移动其他节点的位置。
(3) 二叉树的创建
#include <stdio.h>
#include <stdlib.h>
// 定义二叉树节点结构
struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};
// 创建二叉树
struct TreeNode* createBinaryTree() {
int data;
struct TreeNode* newNode;
printf("输入节点的值(输入-1表示空节点):");
scanf("%d", &data);
if (data == -1) {
return NULL;
}
// 创建新节点
newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
newNode->data = data;
printf("输入节点 %d 的左子节点:\n", data);
newNode->left = createBinaryTree();
printf("输入节点 %d 的右子节点:\n", data);
newNode->right = createBinaryTree();
return newNode;
}
// 前序遍历二叉树
void preOrderTraversal(struct TreeNode* node) {
if (node != NULL) {
printf("%d ", node->data);
preOrderTraversal(node->left);
preOrderTraversal(node->right);
}
}
int main() {
struct TreeNode* root;
printf("创建二叉树:\n");
root = createBinaryTree();
printf("\n前序遍历结果:");
preOrderTraversal(root);
return 0;
}
运行结果示例:
创建二叉树:
输入节点的值(输入-1表示空节点):1
输入节点 1 的左子节点:
输入节点的值(输入-1表示空节点):2
输入节点 2 的左子节点:
输入节点的值(输入-1表示空节点):-1
输入节点 2 的右子节点:
输入节点的值(输入-1表示空节点):-1
输入节点 1 的右子节点:
输入节点的值(输入-1表示空节点):3
输入节点 3 的左子节点:
输入节点的值(输入-1表示空节点):-1
输入节点 3 的右子节点:
输入节点的值(输入-1表示空节点):4
输入节点 4 的左子节点:
输入节点的值(输入-1表示空节点):-1
输入节点 4 的右子节点:
输入节点的值(输入-1表示空节点):-1
前序遍历结果:1 2 3 4
在示例程序中,我们通过递归方式创建了一个二叉树,并实现了前序遍历。用户输入节点的值时,-1表示空节点。程序根据输入的值递归创建二叉树,然后输出前序遍历结果。
2、二叉树的遍历
(1)前序遍历
二叉树的前序遍历是一种遍历二叉树的方法,它的访问顺序是先访问根节点,然后按照先左后右的顺序访问左子树和右子树。具体步骤如下:
【1】 如果当前节点为空,则返回。
【2】 访问当前节点。
【3】 递归地前序遍历左子树。
【4】 递归地前序遍历右子树。
以下是一个使用C语言创建二叉树并进行前序遍历的示例程序:
#include <stdio.h>
#include <stdlib.h>
// 定义二叉树节点结构
struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};
// 创建二叉树
struct TreeNode* createBinaryTree() {
int data;
struct TreeNode* newNode;
printf("输入节点的值(输入-1表示空节点):");
scanf("%d", &data);
if (data == -1) {
return NULL;
}
// 创建新节点
newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
newNode->data = data;
printf("输入节点 %d 的左子节点:\n", data);
newNode->left = createBinaryTree();
printf("输入节点 %d 的右子节点:\n", data);
newNode->right = createBinaryTree();
return newNode;
}
// 前序遍历二叉树
void preOrderTraversal(struct TreeNode* node) {
if (node != NULL) {
// 访问当前节点
printf("%d ", node->data);
// 递归前序遍历左子树
preOrderTraversal(node->left);
// 递归前序遍历右子树
preOrderTraversal(node->right);
}
}
int main() {
struct TreeNode* root;
printf("创建二叉树:\n");
root = createBinaryTree();
printf("\n前序遍历结果:");
preOrderTraversal(root);
return 0;
}
运行结果:
创建二叉树:
输入节点的值(输入-1表示空节点):1
输入节点 1 的左子节点:
输入节点的值(输入-1表示空节点):2
输入节点 2 的左子节点:
输入节点的值(输入-1表示空节点):-1
输入节点 2 的右子节点:
输入节点的值(输入-1表示空节点):-1
输入节点 1 的右子节点:
输入节点的值(输入-1表示空节点):3
输入节点 3 的左子节点:
输入节点的值(输入-1表示空节点):-1
输入节点 3 的右子节点:
输入节点的值(输入-1表示空节点):4
输入节点 4 的左子节点:
输入节点的值(输入-1表示空节点):-1
输入节点 4 的右子节点:
输入节点的值(输入-1表示空节点):-1
前序遍历结果:1 2 3 4
在示例程序中,我们通过递归方式创建了一个二叉树,并实现了前序遍历。用户输入节点的值时,-1表示空节点。程序根据输入的值递归创建二叉树,然后输出前序遍历结果。
(2)中序遍历
二叉树的中序遍历是一种遍历二叉树的方法,它的访问顺序是按照先左后根再右的顺序遍历二叉树的节点。具体步骤如下:
【1】 如果当前节点为空,则返回。
【2】 递归地中序遍历左子树。
【3】 访问当前节点。
【4】 递归地中序遍历右子树。
以下是一个使用C语言创建二叉树并进行中序遍历的示例程序:
#include <stdio.h>
#include <stdlib.h>
// 定义二叉树节点结构
struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;