二叉树链式结构讲解
由于前面一篇文章《二叉树—堆》已经讲解过二叉树的一些定义了,在这篇文章就不过多的讲解了,我们直接开始链式结构的讲解!
正文开始:
前置说明:在学习二叉树链式结构之前,需要先创建一棵树。由于大家可能对二叉树结构的了解不是那么深,为了降低大家理解的难度,我们就先手搓一棵二叉树方便大家接下来的学习。待讲得差不多了再回来讲二叉树的创建。
一、手搓一棵二叉树
typedef struct BinaryTreeNode
{
BTDataType data; // 当前结点值域
struct BinaryTreeNode* left; // 指向当前结点左孩子
struct BinaryTreeNode* right; // 指向当前结点右孩子
}BTNode;
//给节点申请空间
BTNode* BuyNode(int x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
return NULL;
}
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
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;
}
int main()
{
BTNode* root = CreatBinaryTree();
return 0;
}
注:上述代码并不是创建二叉树的正确方式,仅用于讲解
再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:
- 空树
- 非空:根结点,根结点的左子树、根结点的右子树组成的
接下来的讲解是以递归的形式进行的
二、二叉树的遍历
学习二叉树的结构,最基础的就是遍历二叉树。所谓二叉树遍历是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历方式有:前序、中序和后续遍历(递归结构)
2.1 前序遍历
前序遍历(亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。简单地说就是先访问根节点再访问左右子树的节点:
代码展示:
//前序遍历
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
printf("%d ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
2.2 中序遍历
中序遍历——访问根结点的操作发生在遍历其左右子树之中。也就是说先访问左子树再访问根节点最后访问右子树:
代码展示:
//中序遍历
void MiddleOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
MiddleOrder(root->left);
printf("%d ", root->data);
MiddleOrder(root->right);
}
2.3 后序遍历
后序遍历——访问根结点的操作发生在遍历其左右子树之后。这里就不多解释了,和以上两种相似。
代码展示:
//后序遍历
void BackOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
}
BackOrder(root->left);
BackOrder(root->right);
printf("%d ", root->data);
}
2.4 层序遍历
层序遍历,顾名思义就是按照顺序一层一层地遍历二叉树,而不是按照树的父子关系去遍历。但是不通过父子关系又该如何把二叉树都遍历一遍呢?这时候我们就要摒弃我们遍历的一贯思维,想想还有什么办法去遍历二叉树。这时我们可以引入队列的思想,将二叉树的节点进入一个出去一个(队列数据先进先出)
代码展示:
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);//队列的初始化
if (root != NULL)
QueuePush(&q, root);//将数据插入队列
while (!QueueEmpty(&q))//当队列为空的时候就说明二叉树已经遍历完成了
{
BTNode* front = QueueFront(&q);//取队列的队头数据
QueuePop(&q);//删除队头数据
printf("%d ", front->data);
if (root->left)
QueuePush(&q, root->left);
if (root->right)
QueuePush(&q, root->right);
}
QueueDestroy(&q);//销毁队列
}
注:队列的实现代码我就不在这展示了,有老铁不清楚的就可以去了解并实践一遍
三、二叉树的销毁
这篇文章的主题是链式结构的存储,那么既然是链式结构存储,想必大家对销毁链式结构的方式并不陌生吧?没错,就是遍历二叉树依次销毁节点!那么销毁链表应该使用哪种遍历方式呢?答案是都可以,不过在这里推荐使用后序遍历,因为前序遍历会优先将根节点释放掉导致找不到左右子树,所以想要找到左右子树还要先将根节点储存起来;而中序遍历呢会导致找不到右子树
代码展示:
void BinaryTreeDestory(BTNode* root)//后序遍历销毁
{
if (root == NULL)
return;
BinaryTreeDestory(root->left);
BinaryTreeDestory(root->right);
free(root);
}
四、二叉树的节点个数
求二叉树的节点个数也是一个比较基础的问题,这里我们同样使用遍历法来依次累加得出最终的个数:
代码展示:
// 求二叉树结点的个数
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeSize(root->left) +
BinaryTreeSize(root->right) + 1;//
}
运行结果:
五、二叉树的高度
求二叉树的高度,也就是求二叉树的层数:
代码展示:
//求二叉树的高度
int BinaryTreeHeight(BTNode* root)
{
if (root == NULL)
return 0;
int leftheight = BinaryTreeHeight(root->left);
int rightheight = BinaryTreeHeight(root->right);
return leftheight > rightheight ?
leftheight + 1 : rightheight + 1;
}
六、二叉树叶子节点的个数
求叶子结点的个数,首先要知道什么是叶子节点,叶子节点的判定条件是什么?叶子节点就是当它没有孩子,也就是左右孩子都为空时,那么该节点就是叶子节点。
代码展示:
//求二叉树叶子结点的个数
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)//当
return 1;
return BinaryTreeLeafSize(root->left) +
BinaryTreeLeafSize(root->right);
}
七、查找二叉树的某个节点
查找二叉树中是否有存在某个节点,如果一棵树有多个相同数据的节点,那么只需要找到第一个就可以了,其他的就不用找了:
代码展示:
//查找二叉树值为x的节点
BTNode* BinaryTreeFind(BTNode* root,BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)//如果值相同直接返回上一层
return root;
BTNode* ret1 = BinaryTreeFind(root->left, x);
if (ret1)//返回的值需要一个变量去接收
return ret1;
BTNode* ret2 = BinaryTreeFind(root->right, x);//左子树找到了就不用到右子树找了
if (ret2)
return ret2;
return NULL;//都没找到就要返回空
}
八、将数组"ABD##E#H##CF##G##"构建成二叉树
前面我们为了方便讲解和调试,可以通过手搓创建一棵二叉树,这种方式是比较原始的;现在我们通过前序遍历来将以上数组构建成一棵完整的二叉树。注:这里的“#”表示空。
通过前序遍历将数组转换为如下图解:
代码展示:
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
if (a[*pi] == '#')//如果当前元素为“#”,则返回NULL
{
(*pi)++;//这里的“++”不能放在判断条件上,因为每次递归都会进行判断,即便不是空也会进行“++”,导致创建出的二叉树与数组不一致
return NULL;
}
//不为空则先创建一个节点
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
root->data = a[(*pi)++];//将数组的数据插入到树的节点中
root->left = BinaryTreeCreate(a, pi);
root->right = BinaryTreeCreate(a, pi);
return root;
}