- 树的概念
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:每个结点有零个或多 个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结 点可以分为多个不相交的子树 。
就像上图,这就是一个树。
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I…等节点为叶节点
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G…等节点为分支节点
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点 孩
子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
兄弟节点:具有相同父节点的节点互称为兄弟节点;
如上图:B、C是兄弟节点 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的高度或深度:树中节点的最大层次; 如上图:树的高度为4 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
森林:由m(m>=0)棵互不相交的树的集合称为森林;
树的表示
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,实际中树有很多种表示方式,如:双亲表 示法,孩子表示法、孩子兄弟表示法等等。我们这里就简单的了解其中最常用的孩子兄弟表示法
就上图来说,B是A的孩子节点,B的兄弟节点是C,C的兄弟节点是D。B的孩子节点是E,E的兄弟节点是F。当然我们的D的兄弟节点是NULL,以及F的兄弟节点,C的孩子节点E的孩子节点D的孩子节点都是NULL。
- 二叉树的概念
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树 的二叉树组成。
二叉树的特点:
- 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
- 二叉树的子树有左右之分,其子树的次序不能颠倒。
二叉树是和我们现实中的二叉树是类似的。
特殊的二叉树:
- 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是 说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
- 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
在后面我们会继续讨论二叉搜索书和红黑树。这里我们不讨论这两个。
二叉树的存储
链式存储和顺序存储
顺序存储我们只能存储完全二叉树,中间是不应该有NULL节点的,如果含有空节点可能会出现二义性,比如我们的树
当我们存储在数组中应该是abc,但是我们的另一个树
他存储起来同样也是我们的abc,所以我们顺序存储一般只存储我们的完全二叉树。
链式存储就是定义一个结构体存储我们的节点。节点的元素一般情况下包括了我们节点的值和界定啊的左孩子和右孩子。用来指向这个节点的孩子。除了我们的二叉树以外,其他的树结构也可以使用我们的链式存储
// 二叉链
struct BinaryTreeNode {
struct BinTreeNode* _pLeft; // 指向当前节点左孩子
struct BinTreeNode* _pRight; // 指向当前节点右孩子
BTDataType _data; // 当前节点值域
}
// 三叉链
struct BinaryTreeNode {
struct BinTreeNode* _pParent; // 指向当前节点的双亲
struct BinTreeNode* _pLeft; // 指向当前节点左孩子
struct BinTreeNode* _pRight; // 指向当前节点右孩子
BTDataType _data; // 当前节点值域
}
实际上都是定义指针去存储我们的孩子节点。
在一般情况下我们只考虑我们的链式存储(其中一个特例堆我们在文章的末尾进行讲解)
这里我们将讲解二叉树的创建,递归遍历,二叉树的一般OJ的题目,二叉树的层序遍历等等…
了解前序,中序,后序遍历
前序遍历:前序遍历就是在我们第一次遇见我们的节点的时候我们就打印
中序遍历:在我们第二次遇见我们的节点的时候打印
后序遍历:在我们最后一次遇见我们的节点的时候打印
- 二叉树的递归遍历(简单一点,放在前面讲解)
void PreOrder(Node *root){
if (root != NULL){
return;
}
printf("%d", root->value);
PreOrder(root->left);
PreOrder(root->right);
}
这里我们只需要改变我们打印的位置就可以得到中序和后序遍历。
- 节点数量的计算
int count = 0;
void PreorderCount(Node *root) {
if (root != NULL) {
// 根
count++;
PreorderCount(root->left);
PreorderCount(root->right);
}
}
或者使用子问题的思路解决
int NodeSize(Node *root) {
if (root == NULL) {
return 0;
}
else if (root->left == NULL && root->right == NULL) {
// 可选分支
return 1;
}
else {
// 本质是后序遍历
int left = NodeSize(root->left);
int right = NodeSize(root->right);
return left + right + 1;
}
}
- 叶子节点的个数
int LeafSize(Node *root) {
if (root == NULL) {
return 0;
}
else if (root->left == NULL && root->right == NULL) {
// 必须带着
return 1;
}
else {
// 本质是后序遍历
int left = LeafSize(root->left);
int right = LeafSize(root->right);
return left + right;
}
}
- 树的高度
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int Height(Node *root) {
if (root == NULL) {
return 0;
}
int left = Height(root->left);
int right = Height(root->right);
return MAX(left, right) + 1;
}
- 层节点的个数
int KLevelSize(Node *root, int k) {
if (root == NULL) {
return 0;
}
if (k == 1) {
// 隐含着 root 不是空树
return 1;
}
int left = KLevelSize(root->left, k - 1);
int right = KLevelSize(root->right, k - 1);
return left + right;
}
- 元素的查找
Node * Find(Node *root, char v) {
if (root == NULL) {
return NULL; // 一
}
if (root->value == v) {
return root; // 二
}
Node *result = Find(root->left, v);
if (result != NULL) {
return result; // 三
}
result = Find(root->right, v);
if (result != NULL) {
return result; // 四
}
return NULL; // 五
}
- 树的创建
Node * CreateTree(char preorder[], int size, int *pUsed) {
if (size == 0) {
*pUsed = 0;
return NULL;
}
if (preorder[0] == '#') {
*pUsed = 1;
return NULL;
}
// 递推
// 根
Node *root = (Node *)malloc(sizeof(Node));
root->value = preorder[0];
// 左子树
int leftUsed;
root->left = CreateTree(preorder + 1, size - 1, &leftUsed);
// 右子树
int rightUsed;
root->right = CreateTree(preorder + 1 + leftUsed,
size - 1 - leftUsed, &rightUsed);
*pUsed = 1 + leftUsed + rightUsed;
return root;
}
- 根据前序和中序创建树
Node * buildTree(char preorder[], char inorder[], int size) {
if (size == 0) {
return NULL;
}
char rootValue = preorder[0];
int leftSize = find(inorder, size, rootValue);
// 根
Node *root = (Node *)malloc(sizeof(Node));
root->value = rootValue;
// 左子树
root->left = buildTree(
preorder + 1,// 左子树前序
inorder,// 左子树中序
leftSize// 左子树结点个数
);
// 右子树
root->right = buildTree(
preorder + 1 + leftSize,// 右子树前序
inorder + leftSize + 1,// 右子树中序
size - 1 - leftSize// 右子树结点个数
);
return root;
}
- 前中后序非递归遍历
void PreOrderNoR(Node *root) {
std::stack<Node *> s;
Node *cur = root;
while (cur != NULL || !s.empty()) {
while (cur != NULL) {
printf("%c ", cur->value);
s.push(cur);
cur = cur->left;
}
Node *top = s.top();
s.pop();
cur = top->right;
}
}
void InOrderNoR(Node *root) {
std::stack<Node *> s;
Node *cur = root;
while (cur != NULL || !s.empty()) {
while (cur != NULL) {
s.push(cur);
cur = cur->left;
}
Node *top = s.top();
s.pop();
printf("%c ", top->value);
cur = top->right;
}
}
void PostOrderNoR(Node *root) {
std::stack<Node *> s;
Node *cur = root;
Node *last = NULL; // 上一个被完整后序遍历过的树的根结点
while (cur != NULL || !s.empty()) {
while (cur != NULL) {
s.push(cur);
cur = cur->left;
}
Node *top = s.top();
if (top->right == NULL) {
printf("%c ", top->value);
s.pop();
last = top;
}
else if (top->right == last) {
printf("%c ", top->value);
s.pop();
last = top;
}
else {
cur = top->right;
}
}
}
这里我们只讲解我们的这点,在我们学习了c++之后我们再对我们的二叉树进行深入的了解。