一、树形结构:
1、树的基本概念:一种表示层次关系(一对多)的数据结构,有且仅有一个特定的节点,
该节点没有前驱,称为根节点,剩余的n个不相交的子集,其中每个子集也都是一棵树
称为根节点的子树
注意:树形结构有递归性(树中有树)
2、树的表示方式:倒悬树、嵌套法、凹凸法
3、树的专业术语:
1.节点:组成树的基本元素,同时它也是一棵树
2.节点的度:该节点的子树数量
3.树的度:树中节点的数量
4.树的层次:从根节点层次为1出发计算
5.树的深度:树的最大层次数
6.叶子节点:节点的度为0的节点
***7.双亲节点和孩子节点:节点的子树称为该节点的孩子节点,该节点就是它们孩子节点的双亲节点
8.兄弟节点:具有同一个双亲节点,互为兄弟节点
9.堂兄弟节点:双亲节点在同一层次的节点互为堂兄弟
10.祖先节点:从根节点出发到该节点,经过的所有节点都称为该节点的祖先节点
11.子孙节点:一个节点的子树中任意一个节点都是它的子孙结点
12.森林:n个不相交的树组成的集合称为森林
4、树的存储:
树可以顺序存储,也可以链式存储,还可以混合存储,由于存储的信息不同,有以下几种存储表示方式
1.双亲表示法:
顺序存储:
位置 data 双亲位置
0 A -1
1 B 0
2 C 0
3 D 0
4 E 1
5 F 1
6 G 3
优点:方便找到双亲
缺点:不方便查找孩子节点
2.孩子表示法:
顺序存储:浪费空间
位置 data 存储孩子位置的数组sub_arr[n-1]
0 A 1、2、3
1 B 4、5
2 C
3 D 6、7
4 E
5 F
6 G
7 H 8
8 I
链式存储:节约空间
位置 data listhead
0 A 1->2->3->N
1 B 4->5->N
2 C
3 D 6->7->N
4 E
5 F
6 G
7 H 8->N
8 I
优点:方便找孩子
缺点:不方便找双亲
3.兄弟表示法:
双亲只存储第一个子节点,然后链式指向所有的兄弟节点
优点:方便查找到所有的兄弟节点、孩子节点
缺点:不方便找双亲
注意:普通树不常用,一般会把普通树转换成二叉树使用
二、二叉树
是一种常用的数据结构,处理起来比较简单方便,并且普通树可以很方便的转换成二叉树
普通树转二叉树:孩子变左节点,兄弟变右节点
1、定义:节点的度最多为2
二叉树是n个有限元素的集合,该集合或者为空。或者由一个称为根的元素及两个不相交
、被分别称为左子树和右子树的二叉树组成,是有序树,当集合为空时,该二叉树为空二叉树
在二叉树中,一个元素也称为一个节点
2、二叉树的性质:
性质1:二叉树的第i层上至多有2i-1(i≥1)个节点
性质2:深度为h的二叉树中至多含有2h-1个节点
性质3:若在任意一棵二叉树中,有n0个叶子节点,有n2个度为2的节点,则必有n0=n2+1
性质4:具有n个节点的满二叉树深为log2n+1。
性质5:若对一棵有n个节点的完全二叉树进行顺序编号(1≤i≤n),那么,对.于编号为i(i≥1)的节点:
当i=1时,该节点为根,它无双亲节点
当i>1时,该节点的双亲节点的编号为i/2
若2i≤n,则有编号为2i的左节点,否则没有左节点
若2i+1≤n,则有编号为2i+1的右节点,否则没有右节点
3、特殊的二叉树:
1.满二叉树:二叉树每层节点数都满足2^(i-1)个
2.完全二叉树:满二叉树最后一层从右开始少为完全二叉树
三、二叉树的常用操作:构建、销毁、遍历、高度、密度、添加、删除、查询、求左、求右、求根
四、二叉树的存储:
顺序存储:必须按照完全二叉树的格式存储,空位置使用特殊数据代替
数据项:存储节点的内存首地址、容量
链式存储:由一个个节点组成,每个节点也都是一棵树
节点数据项:
数据项:
左子树指针:
右子树指针:
五、二叉树的遍历
前序遍历:根、左、右
中序遍历:左、根、右
后序遍历:左、右、根
注意:前中后由根节点的遍历次序决定,并且左右子树的次序不会改变
注意:根据中序+前序 或者 中序+后序 可以还原一棵树,无法通过前序+后序还原
层序遍历:从上到下、从左到右依次遍历,必须与队列配合
六、有序二叉树(二叉排序树、二叉查找树、二叉搜索树)
左子树的数据小于根节点,根节点的数据小于等于右子树,并且左右子树都符合以上定义
特殊:平衡二叉树
注意:由于这种树的中序遍历是从小到大,所以有序二叉树也是一种排序算法,并且有序二叉树中查找某个值天然是二分查找
所以经常考
注意:由于有序二叉树需要频繁地插入删除,因此不适合使用链式存储
七、线索二叉树
也是有序二叉树
规律:在一棵有n个节点的链式二叉树中必定存在n+1个空指针
因此链式二叉树中有很多的空指针,可以在有序二叉树中,让这些空指针指向下一个或前一个节点,这样有序遍历树时可以
不使用递归而是使用循环进行,以此提高树的遍历效率
中序线索二叉树
节点数据项:
左子树指针:
右子树指针:
右子树线索标志(假:右子树指针指向真的右子树;真:右子树指针指向下一个节点):
实现过程:
1、创建一棵有序二叉树
2、通过中序遍历创建线索(只需一次)
3、根据线索遍历(循环)
简答题:线索二叉树就是为了提高树的遍历速度、效率
八、选择树
是一种完全二叉树,把要存储的数据存放在最后一层,根节点是左右子树中的其中一个,可以是最大的(胜者树)也可以是最小的(败者树)
功能:快速的找出其中最大或者最小值
九、堆
是完全二叉树,不适合链式存储的树,不好找双亲
大顶堆(大根堆):根节点比左右子树大
小顶堆(小根堆):根节点比左右子树小
数据项:
存储数据的内存首地址:
容量:
数量:
运算:创建、销毁、添加、删除、判断是否空堆、满堆、堆顶
作业:
1、把一棵二叉树转换成镜像树
typedef struct TreeNode
{
int val;
struct TreeNode *left;
struct TreeNode *right;
}TreeNode;
TreeNode* mirror_tree(TreeNode* root)
{
if (root == NULL)
{
return NULL;
}
// 交换当前节点的左右子树
TreeNode* temp = root->left;
root->left = root->right;
root->right = temp;
// 递归地处理左子树和右子树
mirror_tree(root->left);
mirror_tree(root->right);
return root;
}
int main()
{
// 创建一个简单的二叉树进行测试
TreeNode* root = (TreeNode*) malloc(sizeof(TreeNode));
root->val = 1;
TreeNode* left_child = (TreeNode*) malloc(sizeof(TreeNode));
left_child->val = 2;
left_child->left = NULL;
left_child->right = NULL;
TreeNode* right_child = (TreeNode*) malloc(sizeof(TreeNode));
right_child->val = 3;
right_child->left = NULL;
right_child->right = NULL;
root->left = left_child;
root->right = right_child;
// 转换为镜像树
TreeNode* mirrored_root = mirror_tree(root);
// 释放内存
free(left_child);
free(right_child);
free(root);
return 0;
}
2、输入两棵二叉树A、B,判断B是否是A的子结构,空树不是任何树的子结构
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
bool is_sub_tree(TreeNode* A, TreeNode* B) {
if (B == NULL) {
return true;
}
if (A == NULL) {
return false;
}
if (A->val != B->val) {
return false;
}
return is_sub_tree(A->left, B->left) && is_sub_tree(A->right, B->right);
}
bool has_sub_structure(TreeNode* A, TreeNode* B) {
if (A == NULL || B == NULL) {
return false;
}
return is_sub_tree(A, B) || has_sub_structure(A->left, B) || has_sub_structure(A->right, B);
}
int main() {
// 创建两个简单的二叉树 A 和 B 进行测试
TreeNode* A = (TreeNode*) malloc(sizeof(TreeNode));
A->val = 1;
TreeNode* A_left_child = (TreeNode*) malloc(sizeof(TreeNode));
A_left_child->val = 2;
A_left_child->left = NULL;
A_left_child->right = NULL;
TreeNode* A_right_child = (TreeNode*) malloc(sizeof(TreeNode));
A_right_child->val = 3;
A_right_child->left = NULL;
A_right_child->right = NULL;
A->left = A_left_child;
A->right = A_right_child;
TreeNode* B = (TreeNode*) malloc(sizeof(TreeNode));
B->val = 2;
B->left = NULL;
B->right = NULL;
bool result = has_sub_structure(A, B);
printf("B is a substructure of A: %s\n", result ? "true" : "false");
// 释放内存
free(A_left_child);
free(A_right_child);
free(A);
free(B);
return 0;
}
3、把一个有序二叉树转换成有序的双向链表
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
TreeNode* prev = NULL; // 全局变量,用于记录遍历过程中的前一个节点
void inorder_traversal(TreeNode* node) {
if (node == NULL) {
return;
}
inorder_traversal(node->left);
node->left = prev;
if (prev != NULL) {
prev->right = node;
}
prev = node;
inorder_traversal(node->right);
}
TreeNode* convert_to_doubly_linked_list(TreeNode* root) {
if (root == NULL) {
return NULL;
}
prev = NULL;
inorder_traversal(root);
TreeNode* head = root;
while (head->left != NULL) {
head = head->left;
}
return head;
}
int main() {
// 创建一个简单的二叉搜索树进行测试
TreeNode* root = (TreeNode*) malloc(sizeof(TreeNode));
root->val = 4;
TreeNode* left_child = (TreeNode*) malloc(sizeof(TreeNode));
left_child->val = 2;
left_child->left = NULL;
left_child->right = NULL;
TreeNode* right_child = (TreeNode*) malloc(sizeof(TreeNode));
right_child->val = 6;
right_child->left = NULL;
right_child->right = NULL;
root->left = left_child;
root->right = right_child;
// 转换为双向链表
TreeNode* head = convert_to_doubly_linked_list(root);
// 打印双向链表
TreeNode* temp = head;
while (temp != NULL) {
printf("%d ", temp->val);
temp = temp->right;
}
printf("\n");
// 释放内存
free(left_child);
free(right_child);
free(root);
return 0;
}
4、计算一棵有序二叉树的倒数第k大的数
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
int kth_largest_value = 0;
int count = 0;
void reverse_inorder_traversal(TreeNode* node, int k) {
if (node == NULL || count >= k) {
return;
}
reverse_inorder_traversal(node->right, k);
count++;
if (count == k) {
kth_largest_value = node->val;
}
reverse_inorder_traversal(node->left, k);
}
int find_kth_largest(TreeNode* root, int k) {
count = 0;
reverse_inorder_traversal(root, k);
return kth_largest_value;
}
int main() {
// 创建一个简单的二叉搜索树进行测试
TreeNode* root = (TreeNode*) malloc(sizeof(TreeNode));
root->val = 4;
TreeNode* left_child = (TreeNode*) malloc(sizeof(TreeNode));
left_child->val = 2;
left_child->left = NULL;
left_child->right = NULL;
TreeNode* right_child = (TreeNode*) malloc(sizeof(TreeNode));
right_child->val = 6;
right_child->left = NULL;
right_child->right = NULL;
root->left = left_child;
root->right = right_child;
// 查找倒数第 2 大的数
int kth_largest = find_kth_largest(root, 2);
printf("The 2nd largest value in the BST is: %d\n", kth_largest);
// 释放内存
free(left_child);
free(right_child);
free(root);
return 0;
}
5、判断一个二叉树是否对称(镜像)
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
bool is_mirror(TreeNode* left_node, TreeNode* right_node) {
if (left_node == NULL && right_node == NULL) {
return true;
}
if (left_node == NULL || right_node == NULL) {
return false;
}
if (left_node->val != right_node->val) {
return false;
}
return is_mirror(left_node->left, right_node->right) && is_mirror(left_node->right, right_node->left);
}
bool is_symmetric(TreeNode* root) {
if (root == NULL) {
return true;
}
return is_mirror(root->left, root->right);
}
int main() {
// 创建一个对称的二叉树进行测试
TreeNode* root = (TreeNode*) malloc(sizeof(TreeNode));
root->val = 1;
TreeNode* left_child = (TreeNode*) malloc(sizeof(TreeNode));
left_child->val = 2;
left_child->left = NULL;
left_child->right = NULL;
TreeNode* right_child = (TreeNode*) malloc(sizeof(TreeNode));
right_child->val = 2;
right_child->left = NULL;
right_child->right = NULL;
root->left = left_child;
root->right = right_child;
bool symmetric = is_symmetric(root);
printf("The binary tree is symmetric: %s\n", symmetric ? "true" : "false");
// 释放内存
free(left_child);
free(right_child);
free(root);
return 0;
}
6、实现之字形遍历二叉树,第一层从左到右,第二层从右到左,第三层从左到右
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
typedef struct StackNode {
TreeNode *data;
struct StackNode *next;
} StackNode;
void push(StackNode **stack, TreeNode *data) {
StackNode *new_node = (StackNode *) malloc(sizeof(StackNode));
new_node->data = data;
new_node->next = *stack;
*stack = new_node;
}
TreeNode *pop(StackNode **stack) {
if (*stack == NULL) return NULL;
TreeNode *data = (*stack)->data;
StackNode *temp = *stack;
*stack = (*stack)->next;
free(temp);
return data;
}
bool is_empty(StackNode *stack) {
return stack == NULL;
}
void zigzag_traversal(TreeNode *root) {
if (root == NULL) return;
StackNode *stack1 = NULL;
StackNode *stack2 = NULL;
push(&stack1, root);
while (!is_empty(stack1) || !is_empty(stack2)) {
while (!is_empty(stack1)) {
TreeNode *node = pop(&stack1);
printf("%d ", node->val);
if (node->left) push(&stack2, node->left);
if (node->right) push(&stack2, node->right);
}
while (!is_empty(stack2)) {
TreeNode *node = pop(&stack2);
printf("%d ", node->val);
if (node->right) push(&stack1, node->right);
if (node->left) push(&stack1, node->left);
}
}
}
int main() {
// 创建一个简单的二叉树进行测试
TreeNode* root = (TreeNode*) malloc(sizeof(TreeNode));
root->val = 1;
TreeNode* left_child = (TreeNode*) malloc(sizeof(TreeNode));
left_child->val = 2;
left_child->left = NULL;
left_child->right = NULL;
TreeNode* right_child = (TreeNode*) malloc(sizeof(TreeNode));
right_child->val = 3;
right_child->left = NULL;
right_child->right = NULL;
root->left = left_child;
root->right = right_child;
printf("Zigzag traversal: ");
zigzag_traversal(root);
printf("\n");
// 释放内存
free(left_child);
free(right_child);
free(root);
return 0;
}