基本概念
- 二叉树(Binary Tree)是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根节点和两棵互不相交的、分别称为根节点的左子树和右子树的二叉树组成。
常见二叉树大概长这样👇
根:一棵树有且仅有一个根节点(当然空树是没有的),根节点可以理解为金字塔最顶端的那个结点(图里是A)
孩子:其实就是某结点分出来的子节点,这样的结构放在遗传系谱图里面,该结点的孩子就是子节点,所以会说“孩子结点”。二叉树的子节点区分左右,且一个结点的子结点最多只能有两个(当子节点数为0时我们会称它为叶子结点)
特殊的二叉树
-
斜树:所有结点都只有左结点或者右结点的二叉树叫斜树。其中,只有左结点的树叫左斜树,只有右结点的数叫右斜树。
-
满二叉树:满二叉树的精髓就是“满”。具体来说就是除叶子外所有分支结点都存在左结点和右结点,并且所有叶子都在最下面那层上(叶子就是没有后代孤零零的单个结点。
大概长这样子👇
-
完全二叉树:满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满二叉树。完全二叉树的叶子节点只能出现在最下两层,并且最下层的结点都集中在左部连续位置,倒数第二层的叶子结点都集中在右部位置。
比如这样👇
树的实现
知道了树大概长什么样子,接下来就该“种树”了
我们在这里使用链表来创建二叉树,因为一个结点最多有两个子结点(左、右两个),还要存放自己的数据。
所以我们把结构体设置成这样👇
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
- 在创建二叉树的时候,多采用递归的方式创建。代码如下:
Tree* creat_tree(int data){
int left = 0;
int right = 0;
if (data == -1) {
return NULL;
}
Tree* t = (Tree*)malloc(sizeof(Tree));
t->val = data;
printf("请输入%d的左子树(-1表示没有):", data);
scanf("%d", &left);
t->left = creat_tree(left);
printf("请输入%d的右子树(-1表示没有):", data);
scanf("%d", &right);
t->right = creat_tree(right);
return t;
}
树的遍历
我们常见的遍历二叉树的方式有四种,分别是前序遍历、中序遍历、后序遍历和层序遍历。
前序遍历
- 前序遍历的顺序可以概述为根-左-右,看整个过程的话,就是这样👇
- 遍历出来的结果是 A-B-D-H-E-I-C-F-G
void preorder(Tree* t) {
if (t == NULL) {
return;
}
printf("%d ", t->val);
preorder(t->left);
preorder(t->right);
}
中序遍历
- 中序遍历的顺序可以概述为左-根-右,看整个过程的话,就是这样👇
- 遍历出来结果是 H-D-I-B-E-J-A-F-K-C-G
void inorder(Tree* t) {
if (t == NULL) {
return;
}
inorder(t->left);
printf("%d ", t->val);
inorder(t->right);
}
后序遍历
- 后序遍历的顺序可以概述为左-右-根,看整个过程的话,就是这样👇
- 遍历出来结果是 H-I-D-J-E-B-K-F-G-C-A
void postorder(Tree* t) {
if (t == NULL) {
return;
}
postorder(t->left);
postorder(t->right);
printf("%d ", t->val);
}
看到这里是不是发现上面三个遍历长得特别像!!!就是改变了一下递归中提取当前结点的顺序而已(超好记的嗷)
但是下面的这个层序遍历变化会稍稍大一点,要好好学哦
层序遍历
- 后序遍历的顺序可以概述为从左到右、从上到下,看整个过程的话,就是这样👇
- 遍历出来结果是 A-B-C-D-E-F-G-H-I-J-K
- 关于用代码实现(题目来源 leetcode102)
具体思想是一个对应结构体组成的队列,(先把第一层的结点放进队列)。 然后针对这一层的数据个数(tail - head)开一个一维数组储存这一层结点的数据(存完这个结点,这个结点就出队列)。 在储存数据的过程中顺便把这一层的子节点按从左至右的顺序放进队列里面。 当一行的节点值都记录完之后,把记录这一行的数组的地址放进被返回的二维数组里面。
int** levelOrder(struct TreeNode* root, int* returnSize, int** returnColumnSizes) {
//returnSize:有多少行 returnColumnSizes:每一行应该放多少元素
*returnSize = 0;
int** result = (int**)malloc(sizeof(int*) * 2000); //这个是要被返回的那个二维数组
struct TreeNode* queue[2000]; //一个大小为2000的数组,存放结构体指针
int head = 0, tail = 0;
queue[tail++] = root;
while (head < tail) {
int k = tail; //k是当前层的最后一个元素在队列中往后一个位置的下标
int* row = (int*)malloc(sizeof(int) * (tail - head));
int count = 0; //记录该结点是该层的第几个
while (head < k) { //对每一层结点进行操作
struct TreeNode* p = queue[head++];
if (p->left) {
queue[tail++] = p->left;
}
if (p->right) {
queue[tail++] = p->right;
}
row[count++] = p->val;
}
result[*returnSize] = row;
(*returnColumnSizes)[*returnSize] = count;
*returnSize++;
}
return result;
}