🥰作者: FlashRider
🌏专栏: 初阶数据结构
🍖知识概要:详解二叉树的概念、二叉树的遍历、以及代码实现。
目录
树的基本概念
我们说的树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成的具有层次关系的集合。
因为外形看起来像是一棵根朝上,叶朝下的倒挂的树,所以我们把它称作树。
注意:子树不能有交集,否则就不是树形结构(即每个节点只能有一个父节点)。
一些术语:
节点的度:一个节点含有的字数个数。
叶节点:度为0的节点。
非叶节点/分支节点:度不为0的节点。
父/双亲节点:有子树的节点。
子节点:有父节点的节点。
兄弟节点:具有相同父节点的节点。
树的度:整个树中节点的度取最大值。
节点的层次:从根节点为第一层开始,依次往下递增。
树的高度:节点的最大层次。
祖先:从根节点到该节点所经过的分支上所有的节点。
森林:由m(m>0)棵互不相交的树组成的集合。
树的图示:
注:树的节点存储什么样的数据根据情况而议,图中使用字母表示节点只是为了方便。
树的存储结构与二叉树的实现
树的存储-左孩子右兄弟表示法
我们可以观察一下树的结构,首先对于每一个父节点来说,它都有至少一个子节点,如果有多个兄弟我们可以都看作兄弟节点。对于没有子节点或兄弟节点的节点,我们可以把它的子节点或兄弟节点看作空。
因此我们可以发现任意节点都可以这么表示:子节点,兄弟节点。
子节点存储它的第一个儿子,兄弟节点存储它的兄弟。这样根据一个儿子的兄弟节点就可以遍历所有的儿子,而根据子节点又可以继续往深处找。
typedef int DataType;//树的数据类型
typedef struct TreeNode
{
struct Node* _child; //第一个孩子
struct Node* _brother; //自己的兄弟
DataType _data; //节点内存的数据
}TNode;
而对于我们目前而言,研究一些特殊的树就可以了,而树中比较典型的一种就是二叉树。
什么是二叉树
二叉树是一种树的结点的有限集合,该集合满足两个性质:
1. 可以为空。 2. 由一个根加上左右两颗子树组成。
对于途中的结点2来说,它的右子树为空,对于3、5、6来说,左右子树都为空,但也满足二叉树的性质。因此这棵树是一颗二叉树。
完全二叉树:
除了最后一层叶子结点可以不用铺满,其他每一层的结点都必须满足最大值。比如上图中的第一层和第二层都满足结点为最多,第三层叶子节点最多为4个,但只有3个,因为叶子节点不用满足铺满,所以上图是一个完全二叉树。
满二叉树:
每一层的结点都为最大值,即每一层结点都铺满。满二叉树是一种特殊的完全二叉树。
二叉树的一些性质:
1. 若根节点层次为1,则一颗非空二叉树第i层最多有2的i-1次方。
2. 若根节点层次为1,则深度为h的二叉树,最大结点数为2的h次方-1。
3. 对于任何一颗二叉树,如果度为0的叶节点个数为n0, 则度为2的分支结点个数为n2,则满足
n0 = n2 + 1。
4. 若根节点层次为1,则有n个结点的满二叉树深度为h = log2(n+1) 以2为底 n+1为对数。
5. 如果从上到下从左到右,根结点为0开始编号,对于结点 i 则:
5.1 若 i 不为0,则(i-1)/2为父节点.
5.2 若2i+1<n, 则2i+1为左孩子。否则无左孩子。
5.3 若2i+2<n, 则2I+2为右孩子。否则无右孩子。
二叉链存储二叉树
我们可以使用链式结构存储二叉树。
typedef int DataType;//数据类型
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* _left;//左孩子
struct BinaryTreeNode* _right;//右孩子
DataType _data;//数据
}BTNode;
三叉链
如果使用三叉链存储二叉树,那么就多一个指向父亲的指针,可以快速找到父节点,在某些需要频繁找父节点的情况下可以使用。
typedef int DataType;//数据类型
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* _parent;//父节点
struct BinaryTreeNode* _left;//左孩子
struct BinaryTreeNode* _right;//右孩子
DataType _data;//数据
}BTNode;
二叉树的代码实现
有了前面的知识,我们已经明白二叉树的结构是怎样的了,那么我们可以考虑如何创建二叉树的节点,其实也很简单,和链表节点类似,malloc一个新节点出来,并存储数据就行了,而二叉树节点的创建与链接我们一般用递归实现,这里就不再赘述,我们就直接手动连接。
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
//二叉树结构
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
//创建一个节点
BTNode* BuyNode(BTDataType x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->left = newnode->right = NULL;
return newnode;
}
//手动链接一颗二叉树
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
二叉树的遍历:
二叉树的遍历分为前序中序后序三种,而这三种的区别就是根节点在前还是在后。
遍历顺序为:左子树,根节点,右子树。 左根右,因此是中序遍历。以此类推。
而我们可以使用递归非常简单的去实现这三种遍历,因为树本身就是一种递归结构。
如果当前节点为空,代表不需要再递下去了,就return,如果不为空代表可以继续递左右子树,因此看遍历需求考虑先递再打印还是先打印再递。
//前序遍历: 根 左 右
void PrevOrder(BTNode* bt)
{
if (bt == NULL)
{
printf("N ");
return;
}
printf("%d ", bt->data);
PrevOrder(bt->left);
PrevOrder(bt->right);
}
//中序遍历 左 根 右
void InOrder(BTNode* bt)
{
if (bt == NULL)
{
printf("N ");
return;
}
PrevOrder(bt->left);
printf("%d ", bt->data);
PrevOrder(bt->right);
}
//后序遍历 左 右 根
void PostOrder(BTNode* bt)
{
if (bt == NULL)
{
printf("N ");
return;
}
PrevOrder(bt->left);
PrevOrder(bt->right);
printf("%d ", bt->data);
}
一些简单常见的操作:
对于一颗二叉树,我们也许会求它的高度或结点个数,这些该怎么做呢?
对于求结点个数来说,我们也可以使用递归思想(分治思想),一棵树的结点个数等于左子树节点数+右子树节点数+根结点,因此sum = left + right + 1;对于这棵树的每一颗子树我们都可以这么想,因此代码就显而易见了。
求叶子结点个数也很简单,只要一个树左右子树都为空,则就是叶结点了,在求结点个数的基础上改一改就行。
对于求高度来说,我们也可以递归,一棵树的高度为左右子树高度的最大值,加上本身这一层。即h = max(左子树高度,右子树高度) + 1。
//求结点个数
int BTreeSize(BTNode* bt)
{
if (bt == NULL) return 0;
return BTreeSize(bt->left) + BTreeSize(bt->right) + 1;
}
//求叶结点个数
int BLeafSize(BTNode* bt)
{
if (bt == NULL)
return 0;
if (bt->left == NULL && bt->right == NULL)
return 1;
return BLeafSize(bt->left) + BLeafSize(bt->right);
}
//求树的高度
int BTreeHeight(BTNode* bt)
{
if (bt == NULL)
return 0;
int left = BTreeHeight(bt->left);
int right = BTreeHeight(bt->right);
return left > right ? left + 1 : right + 1;
}
一些常见操作:
求树的第k层的结点个数:因为这个问题指定了求哪一层,所以我们拿一个变量k来存储剩余层数,当k减到1时代表就是第k层了。
查找值为x的结点:也是用一个变量存储x,如果当前结点的data(前提是结点不为空)为x,就可以返回当前结点,否则返回左子树右子树中递归查找结果不为空的那一个。
//求树第k层的结点个数
int BinaryTreeLevelKSize(BTNode* bt, int k)
{
assert(k > 0);
if (k == 1)
return 1;
if (bt == NULL)
return 0;
return BinaryTreeLevelKSize(bt->left, k - 1) + BinaryTreeLevelKSize(bt->right, k - 1);
}
//二叉树查找值为x的结点
BTNode* BTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* r1 = BTreeFind(root->left, x);
if (r1) return r1;
BTNode* r2 = BTreeFind(root->right, x);
if (r2) return r2;
return NULL;
//if (r1 == NULL && r2 == NULL)
// return NULL;
//if (r1 == NULL) return r2;
//else return r1;
}
二叉树的基本知识点就差不多到这了,接下来我们就可以调试代码了。
//求前中后序遍历,并打印二叉树的结点/叶结点个数以及高度
int main()
{
BTNode* root = CreatBinaryTree();
PrevOrder(root);
printf("\n");
InOrder(root);
printf("\n");
PostOrder(root);
printf("\n");
printf("%d\n", BTreeSize(root));
printf("%d\n", BLeafSize(root));
printf("%d\n", BTreeHeight(root));
return 0;
}
当然,为了避免内存泄漏,记得要销毁二叉树噢。
//二叉树的结点空间释放
void BTreeDestroy(BTNode* bt)
{
if(bt == NULL) return;
BTreeDestroy(bt->left);
BTreeDestroy(bt->right);
free(bt);
}