树的创建与遍历
本文记录了我在学习树的过程中习得的基础知识以及算法,特此在博客中以笔记的形式记录下来。
树的定义
树是一个n个节点的有限集,当n = 0
时,我们称之为空树
-
除了树的根节点(没有父节点的节点称为根节点)以外,树中的所有节点都有它的前驱。
-
树的所有节点都可以有0个和多个后缀
-
除了根节点以外,每个子节点可以分为多个不相交的子树。
-
每一个非跟节点有且仅有一个父节点;
-
除了根节点以外,每个子节点可以分为多个不想交的子树。
树的相关定义
**节点的度:**节点拥有的子树的数目。
叶子:度为零的节点。
**层次:**根节点的层次为1,其余节点的层次等于该节点的父节点加1。
**有序树:**如果树中结点的各子树的次序是重要的,不可随意改变其节点位置
**无序数:**如果树中节点的各子树之间的次序是不重要的,可以交换位置。
二叉树
二叉树的定义
二叉树是每个节点最多有两个子树的树结构。它有五种基本形态:空集,有左子树,有右子树,有左右子树,无左右子树。
二叉树的种类
在我们解题过程中二叉树有两种主要的形式:满二叉树和完全二叉树。
满二叉树
如果一个树只有度为0或2的节点,而且为0的节点都在树上的同一层,那么这颗树为满二叉树。设满二叉树的深度为k
,那么该满二叉树就有2^k-1
个节点。
完全二叉树
在完全二叉树之中,除了最底层的节点没有填满,而其它层的节点数都达到了2个。而在对底层的节点中,节点度数为1的节点的子节点全部集合与左边。若完全二叉树的深度为k
,那么该树的节点数应该在1 ~ 2^k-1
的范围中。
二叉搜索树
二叉搜索树(Binary Search Tree),又被称为二叉查找树。前面我们介绍的树其实都是节点上没有数据的树,而二叉搜索树就是节点上有数值的树,是有序树的一种
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
二叉树的存储方式
在c语言中,二叉树是可以用数组和链表的两个方式来进行实现的,在[代码随想录](代码随想录 (programmercarl.com))中,给我们提供了这两种方式实现的思路
链式实现
数组实现
在数组之中我们有一个问题,那就是我们是怎么通过下标来进行树的遍历呢?
规律如下:
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
二叉树的遍历方式
在二叉树的遍历当中存在两种遍历方式:
- 深度优先(
dfs
)—— 不断递归或者迭代往深入遍历,直到遇到树中的叶节点,在进行回溯。- 前序遍历:中左右
- 中序遍历:左中右
- 后序遍历:左右中
- 广度优先(
bfs
) —— 按照树的层数不断进行遍历。- 层次遍历:按照上图的数,遍历的结果为
1 2 3 4 5 6 7
- 层次遍历:按照上图的数,遍历的结果为
二叉树遍历方式的实现
树的c语言实现
#include <stdio.h>
#include <stdlib.h>
typedef struct Tree {
char data;
struct Tree* left;
struct Tree* right;
} tree;
tree* createTree(char data) {
tree* node = (tree*)malloc(sizeof(tree));
node->data = data;
node->left = NULL;
node->right = NULL;
return node;
}
void insertTree(tree* parent, tree* left, tree* right) {
parent->left = left;
parent->right = right;
}
int main() {
tree* a = createTree('1');
tree* b = createTree('2');
tree* c = createTree('3');
tree* d = createTree('4');
tree* e = createTree('5');
tree* f = createTree('6');
tree* g = createTree('7');
insertTree(a, b, c);
insertTree(b, d, e);
insertTree(c, f, g);
return 0;
}
树的结构构建
递归实现
前序遍历
void preOrder(tree* root) {
if(root != NULL) {
printf("%c ", root->data);
preOrder(root->left);
preOrder(root->right);
}
}
中序遍历
void midOrder(tree* root) {
if(root != NULL) {
midOrder(root->left);
printf("%c ", root->data);
midOrder(root->right);
}
}
后序遍历
void postOrder(tree* root) {
if(root != NULL) {
postOrder(root->left);
postOrder(root->right);
printf("%c ", root->data);
}
}
迭代实现
用迭代实现树的前中后序本质为,用栈储存当前节点的上一位节点,在当循环遍历到节点的结尾处,从栈顶弹出上一位的指针进行回溯,根据遍历的顺序的选择,探索另一边的节点或者继续回溯到上一位,并在合适的时候打印出节点相应数值(打印节点数值与进行回溯两者的顺序是根据遍历方式来决定的)。
前序遍历
void preOrderbyStack(tree* root) {
if (root == NULL) {
return;
}
tree* stack[10];
int top = -1;
tree* pmove = root;
while (top != -1 || pmove) {
while (pmove) {
printf("%c ", pmove->data);
stack[++top] = pmove;
pmove = pmove->left;
}
if (top != -1) {
pmove = stack[top--];
pmove = pmove->right;
}
}
}
中序遍历
void midOrderbyStack(tree* root) {
if (root == NULL) {
return;
}
tree* stack[10];
int top = -1;
tree* pmove = root;
while (top != -1 || pmove) {
while (pmove) {
stack[++top] = pmove;
pmove = pmove->left;
}
if (top != -1) {
pmove = stack[top--];
printf("%c ", pmove->data);
pmove = pmove->right;
}
}
}
后序遍历
void postOrderbyStack(tree* root) {
if (root == NULL) {
return;
}
tree* stack[10];
int top = -1;
tree* mark = NULL;
tree* pmove = root;
while (pmove) {
stack[++top] = pmove;
pmove = pmove->left;
}
while (top != -1) {
pmove = stack[top--];
if (pmove->right == NULL || pmove->right == mark) {
printf("%c ",pmove->data);
mark = pmove;
} else {
stack[++top] = pmove;
pmove = pmove->right;
while (pmove) {
stack[++top] = pmove;
pmove = pmove->left;
}
}
}
}
在后序遍历的时候,我们需要一个标记去确认当前节点的右子树节点已经进行遍历,若检测到已经进行标记就将中间节点输出。
层次遍历
由于c语言并没有现成的栈和队列结构,为了方便,我直接使用c++进行层次遍历的编写
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
vector<vector<int>> res;
if (root) {
que.push(root);
}
while (!que.empty()) {
int n = que.size();
vector<int> path;
while (n--) {
TreeNode* temp = que.front();
que.pop();
path.push_back(temp->val);
if (temp->left) {
que.push(temp->left);
}
if (temp->right) {
que.push(temp->right);
}
}
res.push_back(path);
}
return res;
}
};
对于二叉树的基础介绍到这里就结束了,希望能对读者有所帮助。