正式开始打代码前的一些小知识
辅助队列
辅助队列如下
此时判断得知A的左右子树已满,因此将A出队,辅助队列如下
继续插入元素D时,查询辅助队列得知队首为B,检查B得知左子树为NULL
,将D作为B的左子树插入。辅助队列如下
calloc和malloc
calloc同样是申请空间的函数,和malloc的区别在于,calloc申请的空间内容会全部赋0。
对于二叉树的新建,每插入一个结点就需要将左右孩子的值赋为NULL,而使用calloc时就可以省略这一步。
calloc()的函数声明
void *calloc(size_t nitems, size_t size)
//nitems -- 要被分配的元素个数。
//size --*斜体样式* 元素的大小。
代码部分
结构体
typedef char ElemType;
//二叉树的数据结构
typedef struct BiTNode{
ElemType data;
struct BiTNode* lchild;
struct BiTNode* rchild;
}BiTNode, *BiTree;
//用于辅助队列标记
typedef struct tag {
BiTree p;
struct tag* pnext;
}tag_t,*ptag_t;
二叉树建树
我调试了一下,简单的输入没有什么问题(看完王道的课自己试着写的)
//新建树
BiTree tree = NULL;//树根
//新结点
BiTree pnew;
//结点数据
ElemType newdata;
ptag_t phead = NULL, ptail = NULL, pcur;//phead指向队头,ptail指向队尾,pcur指向当前需要操作的结点
ptag_t listpnew;//listpnew指向新入队的结点
//输入一串字符,以回车作为结束
while (scanf("%c", &newdata) != EOF)
{
//回车,跳出循环
if (newdata == '\n')
{
break;
}
//新的结点pnew
pnew = (BiTNode*)calloc(1, sizeof(BiTNode));
pnew->data = newdata;
//新的入队结点listpnew
listpnew = (ptag_t)calloc(1, sizeof(tag_t));
listpnew->p = pnew;
//树为空
if (tree==NULL)
{
phead = listpnew;
ptail = listpnew;
//pcur = listpnew;
tree = pnew;
continue;
}
else
{
ptail->pnext = listpnew;
ptail = ptail->pnext;
//pcur = ptail;
}
//首先确认队头结点的左右孩子是否已满,已满则出队
if (phead->p->lchild != NULL && phead->p->rchild != NULL)
{
phead = phead->pnext;
}
//左孩子为空,则先将新结点放入队头结点的左孩子
if (phead->p->lchild == NULL)
{
phead->p->lchild = pnew;
}
//否则放入右孩子
else if (phead->p->rchild == NULL)
{
phead->p->rchild = pnew;
}
}
与王道的代码做对比
//读取字符串abcdefghij,然后层次建树建立一颗二叉树,然后前序遍历输出abdhiejcfg,注意不要打印前序遍历几个汉字
//思路:层次遍历创建二叉树,用队列记录节点(该节点是否有左右孩子)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<stdlib.h>
//二叉数的结构体,用链表实现
typedef char ElemType;
typedef struct BiTNode
{
ElemType data;
struct BiTNode* lchild, * rchild;//左右孩子指针
}BiTNode, * BiTree;//二叉树的节点,二叉树本树
//记录节点的链队,若节点的左右孩子不为空,则出队
typedef struct label
{
BiTree p;//树的某一个结点的地址值
struct label* pnext;
}label, * Label;//链队节点,链队
int main()
{
BiTree pnew;//树的新节点
BiTree tree = NULL;//树根为空
//层次遍历的辅助队列
Label phead = NULL, ptail = NULL;//phead就是队列头,ptail就是队列尾
Label listpnew;//链队的新节点
Label pcur = NULL;//listpnew是队列的新节点 pcur是树的当前节点
ElemType c;
while (scanf("%c", &c) != EOF)
{
if (c == '\n')
{
break;
}
pnew = (BiTree)calloc(1, sizeof(BiTNode));//calloc申请空间并对空间进行初始化
pnew->data = c;//数据放进去
listpnew = (Label)calloc(1, sizeof(Label));//给队列结点申请空间
listpnew->p = pnew;//赋值
if (NULL == tree)//树根为空
{
tree = pnew;//树的根
phead = listpnew;//队列头
ptail = listpnew;//队列尾
pcur = listpnew;
continue;//跳出本次循环
}
else {
ptail->pnext = listpnew;//新结点放入链表,通过尾插法//尾插法入队
ptail = listpnew;//ptail指向队列尾部
}
//pcur始终指向要插入的结点的位置
if (NULL == pcur->p->lchild)//如何把新结点放入树
{
pcur->p->lchild = pnew;//把新结点放到要插入结点的左边
}
else if (NULL == pcur->p->rchild)
{
pcur->p->rchild = pnew;//把新结点放到要插入结点的右边
pcur = pcur->pnext;//左右都放了结点后,pcur指向队列的下一个
}
}
}
看了一下王道的代码,和我写的区别在于pcur:
可能是听课的时候没太明白pcur具体是什么,我写的时候将队列中队头左右子树已满的元素直接出队,也就是phead指向下一个结点;
王道的代码则是phead一直指向根结点,用pcur来表示当前左右子树未满还可以插入的结点。
看我的代码我是将pcur理解成了新插入的结点,所以没用到pcur。
思考了一下还是有pcur比较合理并且完整,可以保留下完整的插入结点,如果之后还需要用到所有结点的话会更方便。
打代码时发现的关于BiTNode和BiTree一些注意点(其实很简单但好久才想明白
我写了这样一句代码
pnew = (BiTNode*)calloc(1, sizeof(BiTree));
是一句错的代码,正确写法应该是
pnew = (BiTNode*)calloc(1, sizeof(BiTNode));
之前写类似空间申请时一般都会写对,但一直没仔细注意过这个点,在写二叉树层次建树这部分时不小心就写错了,后来运行代码一直不对,还以为是我对calloc()函数理解错了
我在这段纠结的时间完全确认了一下一点(其实是基础但是我一直没放心上):
对于
typedef struct BiTNode{
ElemType data;
struct BiTNode* lchild;
struct BiTNode* rchild;
}BiTNode, *BiTree;
sizeof(BiTNode) = 12
sizeof(BiTNode*) = 4
sizeof(BiTree) = 4
原因在于BiTNode*和BiTree只是指针!!