在树中通常将数据元素称为结点
树是n个结点的有限集合;当n=0时,称为空树。
任意一颗非空树满足以下条件:
- 有且仅有一个特定的根节点
- 当n>1时,除根结点之外的其余结点被分成m个互不相交的有限集合,其中每个集合又是一棵树,并成为这个根节点的子树。
- 某结点所拥有的子树的个数成为该结点的度;数中各结点的最大值称为该树的度
- 度为0的结点称为叶子结点,也成为终端结点;度不为0的结点称为分支结点,也成为分终端结点
- 某结点的子树的根结点称为该结点的孩子结点(例如b是a的孩子结点);反之,该节点称为其孩子结点的双亲结点(a是b的双亲结点);具有同一个双亲的孩子结点互称为兄弟结点(例如b与c)。
- 路径上经过的边数称为路径长度
- 如果从结点x到结点y有一条路径,那么x就称为y的祖先,y称为x的子孙
- 规定根节点的层数为1;对其余结点,若某结点在第 k 层,则其孩子结点在第 k+1 层;树中所有结点的最大层数称为树的深度,也称为树的高度;树中每一层结点个数的最大值称为树的宽度。
ADT Tree
DataModel
树由一个根结点和若干棵子树构成,树中结点具有层次关系
Operation
InitTree:初始化一棵树
DestroyTree:销毁一棵树
PreOrder:前序遍历树
PostOrder:后序遍历树
LeverOrder:层序遍历树
endADT
树的遍历是指从根结点出发,按照某种次序风闻树中所有结点,使得每个结点被访问一次且仅访问一次。
1.树的前序遍历操作:
A B D E H I F C G
从根节点开始,递归以下操作
若树为空,则空返回;
否则执行:(1)访问根节点(2)按照从左到右的顺序前序遍历根结点的每一棵子树
2.树的后序遍历操作:
D H I E F B G C A
从根节点开始,递归以下操作
若树为空,则空操作返回
否则:(1)按照从左到右的顺序后序遍历根结点的每一棵子树(2)访问根节点
3.层序遍历:A B C D E F G H I
从根结点开始,自上而下依次遍历
输入:9(节点的个数)
9个结点:ABCDEFG (数组)
8个边数:AB AC BD BE BF CG CH EI
树的双亲表示法:用一维数组存储树中各个结点(一般按层序存储)的数据信息以及该结点的双亲在数组中的下标
数组中一个元素对应树中的一个结点,数组元素包括树中结点的数据信息以及该结点的双亲在数组中的下标。
parent域的值为-1表示该结点无双亲,即该结点是根节点
template
struct PNode
{
DataType data;
int parent;
};
for (i = 0; i < n; i++) {
pnode[i].data = a[i];
pnode[i].parent = -1;
}//初始化parent
find(x)//寻找x的角标
for (i = 0; i < n; i++) {
cin >> par >> child;//输入表示边数
k = find(child);//child的角标
pnode[k].parent = find(par);//child的双亲为par的数组坐标
}
树的孩子表示法是一种基于链表的存储方法,把每个结点的的孩子排列起来,看成一个线性表,以单链表存储,成为该结点的孩子链表,则n个结点共有n个孩子链表。
struct CTNode //孩子结点
{
int child;
CTNode *next;
};
template
struct CBNode //表头结点
{
DataType data;
CTNode *firstChild; //指向孩子链表的头指针
};
child:本身所在的数组下标
data:双亲结点的的数值
firstchild:孩子节点的数组下标
找孩子结点:
i = find(B);//B在list中的位置 p = list[i].firstchild;//p指向B的孩子节点 while (p != nullptr) { cout << list[p.child].data;//依次输出B的孩子节点的数据 p = p->next; }
找双亲结点:
k = find(D);//D的双亲结点,返回D的数组坐标 for (i = 0; i < n; i++) { p = list[i].firstchild;//p指向i的孩子结点 flag = 0; while (p != nullptr) { if (p->data == k) {//判断孩子结点是否与D一样 flag = 1; break; } p = p->next; } if (flag == 1) { cout << list[i].data; break; } }
树的孩子兄弟表示法(二叉链表):链表中的每个结点包括数据域和分别指向该结点的第一个孩子和右兄弟的指针
data:存储该节点的数据信息
firstchild:存储该结点第一个孩子结点的存储地址
rightsib:存储该结点的右兄弟结点的存储地址
二叉树: n(n≥0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
- 每个结点最多有两颗子树,度仅为0、1、2
- 二叉树的左右子树不能任意颠倒,若只有一个结点,一定要指明左右子树。
- 左右不同,二叉树不同
左斜树:所有结点都只有左子树的二叉树
右斜树:所有结点都只有右子树的二叉树
斜树:左斜树和右斜树的统称
斜树特点:
- 每一层仅有一个节点
- 节点个数与层数相同
满二叉树:所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上的二叉树
特点:
- 叶子只能出现在最下一层
- 只有度为0和度为2的结点
- 在同样深度的二叉树中结点个数最多
- 在同样深度的二叉树中叶子结点个数最多
完全二叉树:在满二叉树中,从最后一个结点开始,连续去掉任意个结点得到的二叉树
特点:
- 叶子结点只能出现在最下两层且最下层的叶子结点都集中在二叉树的左面
- 完全二叉树中如果有度为 1 的结点,只可能有一个,且该结点只有左孩子
- 深度为 k 的完全二叉树在 k-1 层上一定是满二叉树
- 在同样结点个数的二叉树中,完全二叉树的深度最小
性质一:
对于多叉树:
对于完全二叉树,度为1的结点的个数只能为1、0
当度为1的结点个数为1时,由性质一可得n2=(N-2)/2
当度为1的结点个数为0时,由性质一可得n2=(N-1)/2
性质二:
性质三:
性质四:
性质五:
性质六:
ADT BiTree
DataModel
二叉树由一个根结点和两棵互不相交的左右子树构成,结点具有层次关系
Operation
InitBiTree:初始化一棵空的二叉树
CreatBiTree:建立一棵二叉树
DestroyBiTree:销毁一棵二叉树
PreOrder:前序遍历二叉树
InOrder:中序遍历二叉树
PostOrder:后序遍历二叉树
LeverOrder:层序遍历二叉树
endADT
二叉树的遍历:递归操作,一条路访问到底,在返回
前序遍历:从根节点开始,递归以下操作
若二叉树为空,则空操作返回;否则:
(1)访问根结点
(2)前序遍历根结点的左子树
(3)前序遍历根结点的右子树
中序遍历: 从根节点开始,递归以下操作
若二叉树为空,则空操作返回;否则:
(1)中序遍历根结点的左子树
(2)访问根结点
(3)中序遍历根结点的右子树
后序遍历:从根节点开始,递归以下操作
若二叉树为空,则空操作返回;否则:
(1)后序遍历根结点的左子树
(2)后序遍历根结点的右子树
(3)访问根结点
层序遍历:
从二叉树的根结点开始,从上至下逐层遍历,在同一层中,则按从左到右的顺序对结点逐个访问
前序:ABDGCEF
中序:DGBAECF
后序:GDBEFCA
前序:ABDECFG
中序:BEDAFGC
后序:EDBGFCA
前序遍历和中序遍历能唯一确定这颗二叉树
- 前序遍历从左往右确定根节点出现的顺序
- 中序遍历结点左右两侧是左右子树
后序遍历和中序遍历能唯一确定这颗二叉树
- 后序遍历从右往左确定根节点出现的顺序
- 中序遍历结点左右两侧是左右子树
前序:A B C D E F G H I
中序:B C A E D G H F I
前序: D E F G H I
中序: E D G H F I
前序:F G H I
中序:G H F I
二叉树的顺序存储结构是用一维数组存储二叉树的结点,用结点的存储位置(下标)表示结点的之间的逻辑关系。
1.将二叉树按完全二叉树进行编号,根节点的编号为1,若某结点i有左孩子,则其左孩子的编号为2i;若某结点i有右孩子,则其右孩子的编号为2i+1
2.将二叉树的结点按照编号顺序存储到一维数组中
顺序存储结构结点的定义:
const MaxSize = 100;
template
struct SeqBiTree
{
DataType data[MaxSize];
int biTreeNum;
};
二叉树一般采用二叉链表存储:每个结点对应一个链表结点,链表结点主要存放数据信息和指示左右孩子的指针。
data存放该结点的数据信息;lchild存放左孩子的指针;rchilc存放右孩子的指针。
构造链表结点:
template struct BiNode
{
DataType data;
BiNode< DataType > *lchild, *rchild;
};
n 个结点的二叉链表有2n-(n-1) = n+1 个空指针个空指针
InitBiTree:初始化一棵空的二叉树
CreatBiTree:建立一棵二叉树
DestroyBiTree:销毁一棵二叉树
PreOrder:前序遍历二叉树
InOrder:中序遍历二叉树
PostOrder:后序遍历二叉树
LeverOrder:层序遍历二叉树
template
class BiTree
{
public:
BiTree( ){root = Creat(root);}
~BiTree( ){Release(root);}
void PreOrder( ){PreOrder(root);}
void InOrder( ){InOrder(root);}
void PostOrder( ){PostOrder(root);}
void LeverOrder( );
private:
BiNode *Creat(BiNode *bt);
void Release(BiNode *bt);
void PreOrder(BiNode *bt);
void InOrder(BiNode *bt);
void PostOrder(BiNode *bt);
BiNode *root;
};
(1)前序遍历
递归操作,根节点-左子树-右子树
template
void BiTree :: PreOrder(BiNode *bt)
{
if (bt == nullptr) return; //递归调用的结束条件
else {
cout << bt->data; //访问根结点bt的数据域
PreOrder(bt->lchild); //前序递归遍历bt的左子树
PreOrder(bt->rchild); //前序递归遍历bt的右子树
}
}
(2)中序遍历
递归操作,左子树-根节点-右子树
template
void BiTree :: PreOrder(BiNode *bt)
{
if (bt == nullptr) return; //递归调用的结束条件
else {
PreOrder(bt->lchild); //前序递归遍历bt的左子树
cout << bt->data; //访问根结点bt的数据域
PreOrder(bt->rchild); //前序递归遍历bt的右子树
}
}
(3)后序遍历
递归操作,左子树-右子树-根节点
template
void BiTree :: PreOrder(BiNode *bt)
{
if (bt == nullptr) return; //递归调用的结束条件
else {
PreOrder(bt->lchild); //前序递归遍历bt的左子树
PreOrder(bt->rchild); //前序递归遍历bt的右子树
cout << bt->data; //访问根结点bt的数据域
}
}
(4)层序遍历
在进行层序遍历时,访问每一层的结点后,在对各个结点的左孩子和右孩子顺序访问,这样一层一层进行,先访问的结点其左右孩子也要先访问
1.队列 Q 初始化;
2. 如果二叉树非空,将根指针入队;
3. 循环直到队列 Q 为空
3.1 q = 队列 Q 的队头元素出队;
3.2 访问结点 q 的数据域;
3.3 若结点 q 存在左孩子,则将左孩子指针入队;
3.4 若结点 q 存在右孩子,则将右孩子指针入队;
template
void BiTree :: LeverOrder( )//root为二叉树的根指针
{
BiNode *Q[100], *q = nullptr; //Q用来存储队列
//q用来存储根指针的位置,根指针不能移动
int front = -1, rear = -1; //队列初始化
if (root == nullptr) return; //二叉树判空操作,若为空,则结束
Q[++rear] = root; //根指针入队操作
while (front != rear)//当队列非空时
{
q = Q[++front];//q接收出队指针,目前根指针
cout << q->data;
if (q->lchild != nullptr) Q[++rear] = q->lchild;//左子树入队
if (q->rchild != nullptr) Q[++rear] = q->rchild;//右子树入队
}
}
(5)求二叉树的宽度
构造新的数组结构体
struct QNode {
Benode* ptr;
int lno;
};
代码实现部分:
int front = rear = -1;//初始化队列
QNode Q[maxsize];
if (root) {//判空操作
Q[++rear].ptr = root;//根指针入队,并且赋值
Q[rear].lno = 1;//赋值层数
}
Benode* p = nullptr;//接收ptr指针,以防ptr发生移动
int LNO;//用来接收lno
while (front != rear) {//队列非空时操作
q = Q[++front].ptr;//A出队操作,出队指针传给q
LNO = Q[front].lno;//A所在层数传给LNO
if (q->lchild != nullptr) { Q[++rear].ptr = q->lchild; Q[rear].lno = LNO + 1 };
if (q->rchild != nullptr) { Q[++rear].ptr = q->rchild; Q[rear].lno = LNO + 1 };
//while循环结束后,所有二叉树均已遍历完毕,此时LNO为二叉树的深度
}
int cnt = 0, width = 0;
for (int i = 1; i <= LNO; i++) {//遍历每一层
for (int k = 0; k < n; k++) {//遍历每一个节点所在的层数
if (Q[k].lno == i) {//计算每一层有几个结点
cnt++;
}
}
if (width < cnt) {
width = cnt;//宽度为最大结点数,每一层遍历完成后,都进行比较,然后更新width
}
}
深度:
核心:左右子树递归找层数,找最大
int BiTree::Depth(BiNode* bt) {
int i = 0, j = 0;
if (bt == NULL)return 0;
else {
i = Depth(bt->Lchild);
j = Depth(bt->Rchild);
}
if (i >= j) {
return i + 1;
}
else
{
return j + 1;
}
}
#include
using namespace std;
struct BiNode {
char data;
BiNode* Lchild;
BiNode* Rchild;
};
class BiTree {
public:
BiTree() {
root = Creat();
}
~BiTree() {
Release(root);
}
int CountDepth() {
return Depth(root);
}
private:
BiNode* Creat();
void Release(BiNode* bt);
int Depth(BiNode* bt);
BiNode* root;
};
BiNode* BiTree::Creat() {
BiNode* bt;
char a = getchar();
if (a == '#')bt = NULL;
else {
bt = new BiNode;
bt->data = a;
bt->Lchild = Creat();
bt->Rchild = Creat();
}
return bt;
}
void BiTree::Release(BiNode* bt) {
if (bt == NULL)return;
else {
Release(bt->Lchild);
Release(bt->Rchild);
delete bt;
}
}
int BiTree::Depth(BiNode* bt) {
int i = 0, j = 0;
if (bt == NULL)return 0;
else {
i = Depth(bt->Lchild);
j = Depth(bt->Rchild);
}
if (i >= j) {
return i + 1;
}
else
{
return j + 1;
}
}
int main() {
BiTree t;
cout << t.CountDepth();
return 0;
}
(6)构造函数——建立二叉树
将二叉树中每个结点的空指针引出一个虚结点,其值为一特定的值(#),以标识其为空,处理过后的二叉树称为拓展二叉树。
拓展二叉树的一个遍历序列就能唯一确定一颗二叉树。
设二叉树中的结点均为一个字符,拓展二叉树的前序遍历序列由键盘输入,root为指向根结点的指针。
首先输入根结点,若输入的是#字符,则表明该二叉树为空树,即root=null;否则输入的字符赋给bt->data,之后依次递归建立他的左子树和右子树。
template
BiNode *BiTree :: Creat(BiNode *bt)//bt为根节点(理想的)
{
char ch;
cin >> ch; //输入结点的数据信息,假设为字符
if (ch == ‘#’) bt = nullptr; //建立一棵空树
else {
bt = new BiNode; bt->data = ch;
bt->lchild = Creat(bt->lchild); //递归建立左子树
bt->rchild = Creat(bt->rchild); //递归建立右子树
}
return bt;
}
(7)析构函数——销毁二叉树
二叉链表是动态存储分配,二叉链表的结点是在程序运行过程中动态申请的,在二叉链表变量退出作用域前,要释放二叉链表的存储空间
template
void BiTree :: Release(BiNode *bt)
{
if (bt == nullptr) return;
else{
Release(bt->lchild); //递归释放左子树
Release(bt->rchild); //递归释放右子树
delete bt; //释放根结点
}
}
三叉链表:
森林是m颗互不相交的树的集合
森林是由树构成的,任何一个树,删去根结点就变成了森林,反之,若增加一个根结点,将森林中的每一棵树作为这个根节点的子树,则森林就变成了一棵树
森林的遍历:依次遍历这m棵树就可以遍历整个森林,仅有前序遍历和后序遍历两种遍历方式
前序遍历:ABCDEFGHIJ
后序遍历:BADEFCHJIG
树的兄弟结点变为二叉树的父子关系
方法:
- 加线——树中所有相邻兄弟结点之间加一条线
- 去线——对树中每个结点只保留他和第一个孩子结点的连线,删除他与其他孩子的连线
- 层次调整——按照二叉树结点的关系进行调整
- 树的前序遍历等于对应二叉树的前序遍历序列
- 树的后序遍历等于对应二叉树的中序遍历序列
树的前序遍历:ABEFCDG
树的后序遍历:EFBCGDA
二叉树的前序遍历:ABEFCDG
二叉树的后序遍历:EFBCGDA
设森林中有4颗树,树中结点的个数依次是n1、n2、n3、n4 ,则把森林转换成二叉树后,根结点的左子树上有n1-1个结点,右子树有n2+n3+n4个结点
即第一棵树的根结点作为二叉树根结点,除了根节点外,第一棵树作为左子树,其余各树作为右子树
森林的根结点转换为二叉树的根结点、右孩子、右孩子的右孩子、、、、
方法:
- 将森林中的每一颗树转换为二叉树
- 将每颗树的根结点视为兄弟,在所有根结点之间加上连线‘
- 按照二叉树结点关系进行调整
- 森林的前序遍历等于对应二叉树的前序遍历
- 森林的后序遍历等于对应二叉树的中序遍历
森林的前序遍历:ABCDEFGHIJ
森林的后序遍历:BADEFCHJIG
二叉树的前序遍历:ABCDEFGHIJ
二叉树的中序遍历:BADEFCHJIG
树转换成的二叉树,其根结点无右子树,而森林转换后得到二叉树,其根结点有右子树
二叉树的根结点、右孩子、右孩子的右孩子、右孩子的右孩子的右孩子
方法:
- 加线+若某结点x是其双亲y的左孩子,则把结点x的右孩子、有孩子的右孩子....,都与结点y连接起来
- 去线——删去源二叉树中所有的双亲结点与右孩子结点的连线
- 层次调整
5*1+4*3+2*3+3*3=32
3*1+2*2+4*3+5*3=34
5*1+4*2+3*3+2*3=28
最优二叉树:
- 二叉树带权路径长度最小,使权值越大的叶子结点越靠近根节点,权值越小的叶子结点远离根节点
- 不存在度为1的结点
1. 初始化:由 n 个权值构造 n 棵只有一个根结点的二叉树,得到一个二叉树集合 F={T1,T2,…,Tn};
2. 重复下述操作,直到集合 F 中只剩下一棵二叉树
2.1 选取与合并:在 F 中选取根结点的权值最小的两棵二叉树分别作为左右子树构造一棵新的二叉树,这棵新二叉树的根结点的权值为其左右子树根结点的权值之和
2.2 删除与加入:在 F 中删除作为左右子树的两棵二叉树,并将新建立的二叉树加入到F中;
例 给定权值集合{2,4,5 ,3}
采用顺序存储结构存储哈夫曼树,具有n个叶子结点的哈夫曼树中有n-1个分支结点,他们是在n-1次合并过程中生成的,因此哈夫曼树有2*n-1个结点,设置一个数组hufftree[2n-1]来存储各节点信息。
struct ElemType { int weight;//权值 int parent, lchild, rchild;//双亲结点、左孩子、右孩子的下标 };
哈夫曼树的构造:
- 数组hafftree初始化,所有数组元素的双亲,左右孩子都置为-1;
- 数组的前n个元素的权值置给定权值;
- 循环变量k从n~n-2进行n-1次合并:
- 选取两个权值最小的根结点,其下标为i1,i2
- 将二叉树i1和i2合并为一颗新的二叉树
void HuffmanTree(ElemType huffTree[], int w[], int n) { int i, k, i1, i2; for (i = 0; i < 2 * n - 1; i++) /*初始化,所有结点均没有双亲和孩子*/ { huffTree[i].parent = -1; huffTree[i].lchild = -1; huffTree[i].rchild = -1; } for (i = 0; i < n; i++) //赋权值 huffTree[i].weight = w[i]; for (k = n; k < 2 * n - 1; k++) //n-1次合并,最多有2n-1个结点,从最初结点满的时候开始 { Select(huffTree, i1, i2); /*权值最小的两个根结点下标为i1和i2*/ huffTree[k].weight = huffTree[i1].weight + huffTree[i2].weight;//权值相加 huffTree[i1].parent = k; huffTree[i2].parent = k;//创造双亲结点 huffTree[k].lchild = i1; huffTree[k].rchild = i2;//创造左右孩子 } }
ps:找最小与次小的函数
void HuffmanTree::Select(HTNode* HT, int len, int& i1, int& i2) { int min1, min2; min1 = 9999; min2 = 9999; i1 = -1;//节点中最小权值的下标 i2 = -1;//节点中倒数第二权值小的下标 for (int i = 0; i < len; i++) { if (HT[i].parent != -1) continue;//该节点已经使用过 if (HT[i].weight < min1) { min1 = HT[i].weight; i1 = i; } } for (int i = 0; i < len; i++) { if (HT[i].parent != -1) continue;//该节点已经使用过 if (HT[i].weight < min2 && HT[i].weight > min1) {//保证比min1大 min2 = HT[i].weight; i2 = i; } } }
- 一般来说,题目中出现的哈夫曼树狗仔结构都是左子树权值小于右子树,两子树权值相同时,较矮的子树在左边
- 判断哈夫曼编码:
- 前缀编码
- 二叉树权值路径最小(权大近根)
- 度为0或2
- 定理:若度为m的哈夫曼树中,叶子结点的个数为n,则非叶子结点的个数为(n-1)/(m-1)取下限。
- 类比m进制编码,则需要将m个小值合并成一个,子树依次编为0、1、....
若为三进制,叶子结点有6个,根据以上定理,(6-1)/(3-1)取下限为1,因此补一个叶子结点且权值为0。三个子树依次编码0、1、2。
一个具有n个结点的二叉链表,在2n个指针域中只有n-1个指针域用来存放孩子节点的地址,存在n+1个空指针域,可以利用这些空指针指向该结点的在某种遍历序列的前驱和后继结点
这些指向前驱和后继结点的指针称为线索,加上线索的二叉链表称为线索链表,二叉树称为线索二叉树
为了区分某结点的指针域存放的是指向孩子的指针还是指向前驱或后继的线索,每个结点再增设两个标志位Ltag和Rtag
template
struct node
{
DataType data;
int ltag, rtag;
node* lchild, rchild;
};
中序遍历为例:DGBAECF
将二叉链表中的空指针改为指向前去或后继的线索,而前驱或后继的信息只有在遍历二叉树时才能使用,具体地对访问的结点bt执行以下操作:
- 检查bt的左右指针域,如果为空,则将相应标志置为1
- 由于结点bt的前驱刚被访问过,所以若左指针为空,则可令其指向它的前驱,但由于bt的后续尚未访问到,所以他的右指针不能建立线索,要等到访问结点bt的后继指针时才能进行。为实现这个过程,设指针pre始终指向刚刚访问过的结点,即若结点bt指向当前结点,则pre指向它的前驱。
- 令pre指向刚刚访问过的结点root
1.若二叉链表为空,则空操作返回 2.对bt的左子树建立线索 3.访问根结点bt执行下述操作 3.1如果bt没有左孩子,则为bt加上前驱线索 3.2如果bt没有右孩子,则将bt的右标志置为1 3.3如果结点pre的右标志为1,则为其加上后继线索 3.4令pre指向刚访问的结点bt 4.对bt的右子树建立线索
template
void Bitree ::Bitree(node * bt, node * pre) { if (bt == null) { return; } Bitree(bt -> child, pre); if (bt->lchild == NULL) { bt->Ltag = 1; bt->lchild = pre; } if (bt->rchild == null) { bt->Rtag = 1; } if (pre->Rtag == 1) { pre->rchild = bt; } pre = bt; Bitree(bt->rchild, pre); }
对于中序线索链表的任意结点,其后继结点有以下两种情况:
template
Node*Bitree::*next(Node* p) {
Node* q = nullptr;
if (p->rtag == 1) {
q = p->rchild;//直接得到后继结点
}
else
{
q = p->rchild;//工作指针q指向结点q的右孩子
while (q->ltag == 0) {//查找最左下节点
q = q->lchild;
}
}
return q;
}
在中序线索链表进行遍历,只需找到中序遍历序列中的第一个结点,然后依次找每一个结点的后继结点,直至某结点无后继位置
template
void Bitree::Inorder(){
if (root == NULL) { return; }//如果线索链表为空,则空操作返回
Node* p = root;
while (p->ltag == 0) {//查找遍历序列的第一个节点
p = p->lchild;
cout << p->data;
while (p->rchild!=nullptr)//当结点p存在后继,依次访问其后继结点
{
p = next(p);
cout << p->data;
}
}
}