文章目录
二叉树
性质
二叉树(Binary Tree)是一种树型结构,特点是每个节点至多只有两个子树,并且,二叉树的子树有左右之分,次序不能任意颠倒。
性质:
//--二叉树的性质--//
/*
1.每一结点最大度为2,则在第n层,最大结点数为:2^(n-1)。
2.从1到n层的总结点数最大为2^n-1。
3.度为2的结点数n2,与末端结点数n0的关系为:n0=n2+1;
a. n=n0+n1+n2;
b. n=B(连接结点的分支的总数)+1=n1+2n2+1;
//--满二叉树:每一层的结点数都为最大结点数。
//--完全二叉树:在末端层从已有的最右端的叶子结点像右补齐缺省的叶子节点就变为满二叉树。
4.具有n个结点的完全二叉树的深度为[log2n]+1;
2^(k-1)<=n<2^k;
则可以得到k=[log2n]+1。其中k为整数,需要取整,深度即是层数。
*/
创建
构成二叉树的基本结构
结点结构:
//链式储存结构
typedef struct BiTNode
{
int data;
struct BiTNode* lchild, * rchild;
//左右孩子指针。
}BiTNode,*BTree;
其中,typedef 关键字的作用:
BiTNode 结构的别名: BiTNode。
BiTNode* 结构指针的别名:BTree。
二叉树通过多个链式结构链接成树,即,通过指向结点结构的指针构建二叉树。
故, 使用结构体指针的引用 (BTree & T)作为根结点,即创建函数的形参
,向下创建二叉树。
创建函数
通过类似于先序遍历的方法,将字符串里的字符挨个输入到叶子结点中。
//还是得要一个参数
bool CreateBT(BTree&T)
{
char ch;
ch = getchar();
if (ch == ' ') T = NULL;
else
{
//如果无法分配内存,则报错返回
//if(!(T=new BiTnode))
if (!(T = (BiTNode*)malloc(sizeof(BiTNode))))
exit(OVERFLOW);
//在当前结点储存当前值,然后向下递归
//将char转化为相应的int
T->data = ch;
//向左边递归调用直至遇到空字符
CreateBT(T->lchild);
CreateBT(T->rchild);
}
return true;
}
遍历方法
遍历结果的输出函数:(代码较短,使用内联函数)
//输出函数
inline bool BT_visit(char e)
{
std::cout << e << std::endl;
return true;
};
递归型
使用函数指针
函数指针是指向函数的指针,将函数指针作为另一个函数的参数。
与直接调用另一函数相比,稍微笨拙,但可以做到在不同时间使用不同的函数。
函数指针指向的这些函数的返回值以及特征标相同。
函数原型(函数指针为输出函数):
//先序遍历二叉树
bool PreOrderTraverse(const BTree T, bool (*pf)(char));
//中序遍历二叉树
bool InOrderTraverse(const BTree T, bool (*pf)(char));
//后序遍历二叉树
bool PostOrderTraverse(const BTree T, bool (*pf)(char));
可以注意到的是:
递归型的先序遍历,中序遍历,后序遍历的程序代码类型相似,不同点就是遍历的 输出函数所在的位置 。
先序 | 中序 | 后序 | |
---|---|---|---|
输出函数位置 | 前 | 中 | 后 |
先序遍历:
bool PreOrderTraverse(const BTree T, bool (*pf)(char))
{
//根结点以及末端叶子结点的非NULL判断
if (T != NULL)
{
if (BT_visit(T->data))
//递归调用,展开为不断向下的循环
if (PreOrderTraverse(T->lchild, BT_visit))
if (PreOrderTraverse(T->rchild, BT_visit))
return true;
//无法输出则返回false。
return false;
}
//还是返回true,其实就是NULL时提前结束程序返回上一级
else
{
//std::cout << "#";
return true;
}
}
中序遍历:
bool InOrderTraverse(const BTree T, bool(*pf)(char))
{
if (T != NULL)
{
if (InOrderTraverse(T->lchild, BT_visit))
if (BT_visit(T->data))
if (InOrderTraverse(T->rchild, BT_visit))
return true;
return false;
}
else
return true;
}
后序遍历:
//后序遍历二叉树
bool PostOrderTraverse(const BTree T, bool (*pf)(char))
{
if (T != NULL)
{
if (PostOrderTraverse(T->lchild, BT_visit))
if (PostOrderTraverse(T->rchild, BT_visit))
if (BT_visit(T->data))
return true;
return false;
}
else
return true;
}
不使用函数指针
相比于使用函数指针,不是用函数指针明显更简便。
前序遍历:
void PreOrderTraverse(const BTree T)
{
if (T != NULL)
{
//输出当前结点储存值
BT_visit(T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
else
{
//std::cout << "#";
return;
}
}
中序遍历:
void InOrderTraverse(const BTree T)
{
if (T != NULL)
{
//到达左结点的末端叶子结点后再向上返回。
InOrderTraverse(T->lchild);
//输出当前结点的值
BT_visit(T->data);
//进入右结点 然后重复 到最左结点输出,再向上。
InOrderTraverse(T->rchild);
}
//到达末尾结点NULL时结束
else
return;
}
后序遍历:
void PostOrderTraverse(const BTree T)
{
if (T != NULL)
{
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
BT_visit(T->data);
}
}
非递归型
二叉树不采用上述三种递归方法遍历,而采用循环遍历二叉树,就为二叉树的非递归遍历
。
但,递归法通过不断向下创建下级函数以及末端结束后返回上一级函数,变相的记录了每一个结点的遍历顺序。
如果使用非递归方法,则需要一个能记录并更新结点的工具
。
因此,需要使用到栈。
创建栈
注意点:栈里的BTree* 是指向结构指针的指针((BiTNode)*)*。
typedef struct stack
{
//储存指针
BTree* Base;
BTree* Top;
int stacksize;
}Sq;
创建类
这里为可能用到的类方法。(由于博主使用类,析构类时会出现内存泄漏)
但非递归方法是正确的,所以建议不用类方法,直接创造函数即可。
//对储存树节点的栈实行的操作
class Sqstack
{
private:
static const int MAXSIZE = 20;
Sq _stack;
public:
Sqstack();
~Sqstack();
//结点入栈
void Push(const BTree T);
//判断栈是否为空
bool Is_empty();
//栈顶元素脱出
BTree Pop();
//得到栈顶元素
BTree GetTop();
//
};
实现方法:
Sqstack::Sqstack()
{
//栈底
_stack.Base = new BTree;
_stack.Top = _stack.Base;
_stack.stacksize = 0;
}
Sqstack::~Sqstack()
{
while (_stack.stacksize != 0)
{
delete (_stack.Top--);
_stack.stacksize--;
}
// delete _stack.Base;
BTree* pf = _stack.Base;
_stack.Top=_stack.Base = NULL;
delete (*(pf))->lchild;
delete (*(pf))->rchild;
delete pf;
};
void Sqstack::Push(const BTree T)
{
if (_stack.stacksize >= MAXSIZE)
{
_stack.Base = (BTree*)realloc(_stack.Base, (_stack.stacksize + MAXSIZE) * sizeof(BTree));
if (!_stack.Base)
exit(OVERFLOW);
}
*_stack.Top++ = T;
_stack.stacksize++;
}
bool Sqstack::Is_empty()
{
//return _stack.Top==_stack.Base;
return _stack.stacksize == 0;
}
BTree Sqstack::Pop()
{
if (Is_empty())
{
return *_stack.Base;
}
_stack.stacksize--;
return *(--_stack.Top);
}
BTree Sqstack::GetTop()
{
//如果栈为空栈,则返回值为
if (Is_empty())
{
return *_stack.Base;
}
return *(_stack.Top - 1);
}
前序遍历
注:前序遍历和中序遍历都是左子树到结点到右子树的顺序,只是输出函数所在位置不同。
用栈储存从根到叶的各个结点后,对栈顶进行操作。
栈储存值规律为: 储存根走到最左的所有结点,回退到上一级结点,结点向右子树一步,不存在就回退上一级结点,继续上述操作。存在就将该节点进入栈顶端,然后向左到最左末端。
简便说就是:到达左末,回退一级,找右一步,到达左末,回退一级。如果右不存在,回退,直到右子树存在。
最后时刻栈底元素是最底层最右端的叶子结点
。
void PreOrderTraverse_FD(const BTree T)
{
using namespace std;
if (T == NULL)
return;
//移动指针
BTree pt = T;
//创建栈储存树结点
Sqstack temp;
while (!temp.Is_empty() || pt != NULL)
{
//当走到末端,返回上一结点,开启右子树,右子树为NULL则继续退回
while (pt != NULL)
{
//输出值
//cout << pt->data;
BT_visit(pt->data);
//用栈储存一个值(从根到叶子的所有结点,方便回退)
temp.Push(pt);
//左子树
pt = pt->lchild;
}
//到达这个判断式时,左子树到达了末端
if (!temp.Is_empty())
{
//得到栈顶储存的指针(该指针为树结点)
//此结点的左子树为NULL,借由此节点进入它的右子树
pt = temp.GetTop();
temp.Pop();
pt = pt->rchild;
}
}
delete pt;
}
中序遍历
void InOrderTraverse_FD(const BTree T)
{
using namespace std;
if (T == NULL)
return;
BTree pt = T;
Sqstack temp;
while (!temp.Is_empty() || pt!=NULL)
{
while (pt != NULL)
{
temp.Push(pt);
//左子树递归
pt=pt->lchild;
}
//逐渐返回根结点
if (!temp.Is_empty())
{
//
pt = temp.GetTop();
temp.Pop();
//可替换为pt=temp.Pop();
//输出
//cout << pt->data;
BT_visit(pt->data);
//右
pt = pt->rchild;
}
}
delete pt;
}
后序遍历
后序遍历: 由于后序遍历的输出结果为左 右 根。
因此,最后,要由右端结点返回根,即最后时刻栈底元素为根结点储存元素。
在第一个while循环里,访问到了最左末端叶子的左扩充子树NULL。
在第二个while循环的while循环中,如果能进入右子树就按照右一左尽的方式,把所有左子树输出。
最后根据访问过的判断逐渐从栈中回退并输出父亲结点
。这一步理解很关键,试想当结点到达某一最右末端时,左右子树为空,因此输出结点值后,用PRef记录当前结点
,在第二个while(外部循环)中,它会返回上一次结点,然后由于PRef为上一结点(此时的pf)
的右子树,因此判断继续,如此重复步骤,直至返回到跟结点。
//后序遍历二叉树,非递归方法
void PostOrderTraverse_FD(const BTree T)
{
if (T == NULL)
return;
BTree pf, Prepf;
pf = T;
Prepf = NULL;
Sqstack temp;
//先直接到达左子树末端
while (pf != NULL)
{
temp.Push(pf);
pf = pf->lchild;
}
while (!temp.Is_empty())
{
//返回上一级进行判断,因为已经进入了末端的扩展NULL
pf=temp.Pop();
//结点右子树为0或者右子树已经访问过,第一次为空才能进入else
//右子数访问过的触发条件是返回时,末端所有的右子树全部遍历过了
//依靠上一个Prepf储存的右子树进行判断,返回输出所有结点。
//达到左,右,结点的遍历结果
if (pf->rchild == NULL || pf->rchild == Prepf)
{
BT_visit(pf->data);
//修改最近访问过的结点
Prepf = pf;
}
else
{
//结点再次进入栈顶
temp.Push(pf);
//
pf = pf->rchild;
while (pf != NULL)
{
temp.Push(pf);
pf = pf->lchild;
}
}
}
pf = Prepf = NULL;
delete pf;
delete Prepf;
}
层次遍历
层次遍历是从根到叶的每一层从左向右遍历的方法。
例如:
1
2 3
4 5 6 7
8
按照层次遍历就是1 2 3 4 5 6 7 8
因此遍历从1,2,3…直到最深层,故而要求出二叉树的深度。
二叉树的深度函数
1.该方法设立了两个变量,利用类似后序遍历的方法
,以最深层深度为1,(扩展NULL级叶子深度为0),逐级的同级比较叠加,同级比较中深度更深的会成为该级直到末端的结点最大深度
。以此不断返回最大深度,最终得到到达根结点时的最大深度。
int Depth(BTree T)
{
//初始化两者为0
//每一层递归的LD,RD其实是同名不同作用域变量,并不相关。
int LD=0, RD=0;
//如果根结点为
if (T == NULL)
return 0;
else
{
//根据每一个结点的左右子结点进行递归比较
LD = Depth(T->lchild);
RD = Depth(T->rchild);
//从末端开始递归,类似于后序遍历
//(附带上了同级左右结点深度的比较)
if (LD > RD)
return (LD + 1);
else
return (RD + 1);
}
}
简便讲解:
/*
1
2 3
4 5 6 7
8
右NULL回到8时,m=0,n=0;n++;
m=n++=1;
8回到4时,m=1,n未赋值。
RNULL回到4时,m=1,n=0; m++;
m=m++=2;
4回到2时,m=2,n未赋值。
5回到2时,m=2,5向下直到末尾,n=n++=1;m++;
2回到1时,m=3,n未赋值。
3回到1时,m=3,3向下直到末尾,n=n++(n++)=2;m++;
递归结束。
深度返回为4。
*/
2.该方法是上述方法的简便表现形式,不再使用遍历储存值,而是直接用同级函数递归比较
。同级指同一结点层级。
int Depth_E(BTree T)
{
if (T == NULL)
return 0;
else
return Depth_E(T->lchild) > Depth_E(T->rchild) ? (Depth_E(T->lchild) + 1) : (Depth_E(T->rchild) + 1);
}
层次遍历
函数原型:
//层次遍历
void LevelOrderTraverse(const BTree T);
void Level_show(const BTree, int leveldep);
函数实现:
//层级遍历函数,根据层级遍历输出目标层级的所有值
void Level_show(const BTree T, int leveldep)
{
using std::cout;
//保障正确性,如果该结点指向为NULL,则结束程序(不输出)
if (!T)
{
std::cout << "#";
return;
}
if (leveldep == 1)//当层级为1时,达到目标层数,并且这个叶子结点存在
BT_visit(T->data);
else
{
//最终遍历到目标高度并按照左右左右方式输出
Level_show(T->lchild, leveldep - 1);
Level_show(T->rchild, leveldep - 1);
}
}
void LevelOrderTraverse(const BTree T)
{
//得到树的最大深度
int Dep_T = Depth_E(T);
//最大深度时也要遍历到 从1层(根结点)到达最大深度
for (int i = 1; i <=Dep_T; i++)
Level_show(T, i);
}
遍历结果示例
例子
使用的二叉树示例:
1
2 3
4 5 6 7
8 9 0 1 2 3 4 5
结果
代码仅供参考,如有错误,敬请指出
- - - - 分割线:关于非递归使用到的类方法的修正 - - - -
博主混淆了栈和队列的使用方法
,导致内存越界,经过修改后,程序不再出现月结与泄露问题,下面是代码,仅供参考,当作博主学习路上的记录。
Sqstack::Sqstack()
{
// new 和delete ;malloc,ralloc和free要配套使用。
//_stack.Base = new BTree[MAXSIZE];
_stack.Base = (BTree*)malloc(MAXSIZE * sizeof(BTree));
_stack.stacksize = MAXSIZE;
_stack.Top = _stack.Base;
//储存元素个数为0
_stack.stacknum = 0;
}
Sqstack::~Sqstack()
{
free(_stack.Base);
_stack.Top=_stack.Base = NULL;
_stack.stacksize = _stack.stacknum = 0;
};
void Sqstack::Push(const BTree T)
{
if (_stack.stacknum >= MAXSIZE)
{
_stack.Base = (BTree*)realloc(_stack.Base, (_stack.stacksize + MAXSIZE) * sizeof(BTree));
if (!_stack.Base)
exit(OVERFLOW);
_stack.Top = _stack.Base + _stack.stacksize;
_stack.stacksize += MAXSIZE;
}
*_stack.Top++ = T;
_stack.stacknum++;
}
bool Sqstack::Is_empty()
{
return _stack.stacknum == 0;
}
BTree Sqstack::Pop()
{
if (Is_empty())
return ERROR;
_stack.stacknum--;
return *(--_stack.Top);
}
BTree Sqstack::GetTop()
{
//如果栈为空栈,则返回值为ERROR
if (Is_empty())
{
return ERROR;
}
return *(_stack.Top - 1);
}