数据结构——树和二叉树
文章目录
树结构是一类重要的客观世界中广泛存在,直观来看,树是以分支关系定义的层次结构。
1.树的基本概念
1.1树的定义
树(Tree)是一种抽象数据类型(ADT),它是由节点组成的有限集合。在树中,有一个特殊的节点被称为根节点(Root),它没有父节点。除了根节点外,每个节点有零个或多个子节点,并且每个节点有一个父节点。节点的子节点集合称为该节点的子树。
1.2树的基本语
(1) 结点:树中的一个独立单元。包含一个数据元素及若于指向其子树的分支
(2)结点的度:结点拥有的子树数称为结点的度。
(3)树的度: 树的度是树内各结点度的最大值。
(4) 叶子:度为0 的结点称为叶子或终端结点。
(5) 非终端结点:度不为0 的结点称为非终端结点或分支结点。除根结点之外,非终端结点也称为内部结点。
(6)双亲和孩子:结点的子树的根称为该结点的孩子,相应地,该结点称为孩子的双亲。
(7) 兄弟:同一个双亲的孩子之间互称兄弟。
(8) 祖先:从根到该结点所经分支上的所有结点。
(9) 子孙:以某结点为根的子树中的任一结点都称为该结点的子孙。
(10) 层次:结点的层次从根开始定义起,根为第一层,根的孩子为第二层。树中任一结点的层次等千其双亲结点的层次加l。
(11)堂兄弟:双亲在同一层的结点互为堂兄弟。
(12)树的深度:树中结点的最大层次称为树的深度或高度。
(I3 )有序树和无序树:如果将树中结点的各子树看成从左至右是有次序的(即不能互换),则称该树为有序树,否则称为无序树。
(14)森林:是m (mO)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。由此,也可以用森林和树相互递归的定义来描述树。
1.3树的分类
- 二叉树:每个节点最多有两个子节点的树。
- 平衡树:一种特殊的二叉树,其中任何两个叶子节点的深度大致相同。
- B树:一种自平衡的树,用于数据库和文件系统。
- 红黑树:一种自平衡的二叉搜索树。
- AVL树:一种高度平衡的二叉搜索树。
- 堆:一种特殊的完全二叉树,其中每个节点的值都大于或等于其子节点的值(最大堆)或小或等于其子节点的值(最小堆)。
2.二叉树的基础知识
二叉树(binary tree)是一种非线性数据结构,代表“祖先”与“后代”之间的派生关系,体现了“一分为二”的分治逻辑。与链表类似,二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子点引用。
2.1二叉树的结构体定义
//二叉树结构体定义
typedef int Elemtype;
typedef struct TreeNode {
Elemtype val; //结点值
int height; //结点高度
struct TreeNode *left; //左结点指针
struct TreeNode *right; //右结点指针
}TreeNode;
每个节点都有两个引用(指针),分别指向左子节点(left‑child node)和右子节点(right‑child node),该节点被称为这两个子节点的父节点(parent node)。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的左子树(left subtree),同理可得右子树(right subtree)。
2.2二叉树的初始化
二叉树的初始化函数
//二叉树的初始化
TreeNode* CreatTree(int val) {
//为二叉树动态分配内存
TreeNode *node = (TreeNode*)malloc(sizeof(TreeNode));
node->val = val; //默认根节点的值
node->height = 0; //二叉树的深度为0
node->left = NULL; //左子树为空
node->right = NULL; //右子树为空
//返回初始化的二叉树
return node;
}
实际应用:
//二叉树的初始化
TreeNode* CreatTree(int val);
int main() {
//初始化二叉树
TreeNode *tree = CreatTree(1);
TreeNode *n1 = CreatTree(2);
TreeNode *n2 = CreatTree(3);
TreeNode *n3 = CreatTree(4);
TreeNode *n4 = CreatTree(5);
//各节点之间的引用
tree->left = n1;
tree->right = n2;
n2->left = n3;
n4->right = n4;
}
2.3二叉树插入与删除节点
与链表类似,在二叉树中插入与删除节点可以通过修改指针来实现。
//二叉树的插入与删除
//在n2与n3之间插入p
TreeNode *p = CreatTree(0);
n2->left = p;
p->left = n2;
//删除p
n2->left = n3;
注意:
插入节点可能会改变二叉树的原有逻辑结构,而删除节点通常意味着删除该节点及其所有子树。因此,在二叉树中,插入与删除通常是由一套操作配合完成的,以实现有实际意义的操作。
3.常见的二叉树类型
3.1完美二叉树
完美二叉树(perfect binary tree)所有层的节点都被完全填满。在完美二叉树中,叶节点的度为0 ,其余所有节点的度都为2 ;若树的高度为ℎ ,则节点总数为2ℎ+1 − 1 ,呈现标准的指数级关系,反映了自然界中常见的细胞分裂现象。
3.2完全二叉树
完全二叉树(complete binary tree)只有最底层的节点未被填满,且最底层节点尽量靠左填充。
3.3完满二叉树
完满二叉树(full binary tree)除了叶节点之外,其余所有节点都有两个子节点。
3.4平衡二叉树
平衡二叉树(balanced binary tree)中任意节点的左子树和右子树的高度之差的绝对值不超过1
4.二叉树的退化
当二叉树的每层节点都被填满时,达到“完美二叉树”;而当所有节点都偏向一侧时,二叉树退化为“链表”。
- 完美二叉树是理想情况,可以充分发挥二叉树“分治”的优势。
- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至𝑂(𝑛) 。
5.二叉树的遍历
5.1层序遍历
层序遍历(level‑order traversal) 从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点。
层序遍历本质上属于广度优先遍历(breadth‑first traversal),也称广度优先搜索(breadth‑first search, BFS),它体现了一种“一圈一圈向外扩展”的逐层遍历方式。
//二叉树的遍历
Elemtype* levelOrder(TreeNode* root,Elemtype *size) {
//构造辅助队列
int front,rear;
int index;
Elemtype *array; //遍历后的元素存储到数组中
TreeNode *node;
TreeNode **queue; //辅助队列
//辅助队列分配内存
queue = (TreeNode**)malloc(sizeof(TreeNode*)*Max_Size);
//结点分配内存
node = (TreeNode*)malloc(sizeof(TreeNode));
//辅助数组分配内存
array = (Elemtype*)malloc(sizeof(Elemtype) * Max_Size);
//队列索引初始化
front = 0, rear = 0;
//根节点入队
queue[rear++] = root;
//数组指针
index = 0;
while (front < rear) {
//出队
node = queue[front++];
//保存节点值
array[index++] = node->val;
//左节点入队
if(node->left != NULL) {
queue[rear++] = node->left;
}
//右节点入队
if(node->right != NULL) {
queue[rear++] = node->right;
}
}
//更新数组大小
*size = index;
array = realloc(array,sizeof(Elemtype) * (*size));
//释放队列空间
free(queue);
return array;
}
复杂度分析:
- 时间复杂度为𝑂(𝑛) :所有节点被访问一次,使用𝑂(𝑛) 时间,其中𝑛 为节点数量。
- 空间复杂度为𝑂(𝑛) :在最差情况下,即满二叉树时,遍历到最底层之前,队列中最多同时存在(𝑛 + 1)/2 个节点,占用𝑂(𝑛) 空间。
5.2前序、中序、后序遍历
前序、中序和后序遍历都属于深度优先遍历(depth‑first traversal),也称深度优先搜索(depth‑firstsearch, DFS),它体现了一种“先走到尽头,再回溯继续”的遍历方式。
//二叉树的前序遍历
void preOrder(TreeNode* root,int *size,int *arr) {
//如果节点为空,停止递归
if(root == NULL) {
return;
}
//将节点的值存储到数组中
arr[(*size)++] = root->val;
//开始递归
preOrder(root->left,size,arr);
preOrder(root->right,size,arr);
}
//二叉树的中序遍历
void inOrder(TreeNode* root,int *size,int *arr) {
//如果节点为空,停止递归
if(root == NULL) {
return;
}
//开始递归
inOrder(root->left,size,arr);
//将节点的值存储到数组中
arr[(*size)++] = root->val;
inOrder(root->right,size,arr);
}
//二叉树的后序遍历
void postOrder(TreeNode* root,int *size,int *arr) {
//如果节点为空停止递归
if(root == NULL) {
return;
}
//开始递归
postOrder(root->left,size,arr);
//将节点值存储到数组中
arr[(*size++)] = root->val;
postOrder(root->right,size,arr);
}
复杂度分析
- 时间复杂度为𝑂(𝑛) :所有节点被访问一次,使用𝑂(𝑛) 时间。
- 空间复杂度为𝑂(𝑛) :在最差情况下,即树退化为链表时,递归深度达到𝑛 ,系统占用𝑂(𝑛) 栈帧空间。