目录
1二叉链表存储结构
typedef char ElemType;
typedef struct node
{
ElemType data; //数据域
struct node *left,*right; //指针域,存储该结点的左右孩子结点的存储地址
}BiTree;
2.二叉链表的创建(递归)
把每一个结点的空指针用字符'#'来标识
BiTree *CreateBiTree()
{
BiTree *t;
char ch; //元素为字符
scanf("%c",&ch);
if(ch!='#')
{
t=(BiTree *)malloc(sizeof(BiTree)); //开辟结点
t->data=ch; //放数据
t->left=CreateBiTree(); //创建左子树
t->right=CreateBiTree(); //创建右子树
}
else
{
t=NULL; //输入“#”不开辟结点,给空即可
}
return t;
}
2.二叉链表的遍历
此篇所有遍历算法的访问顺序均采用先左后右的遍历方式:
先序序列:先访问根结点,后访问左孩子,最后访问右孩子;
中序序列:先访问左孩子,后访问根结点,最后访问右孩子;
后序序列:先访问左孩子,后访问右孩子,最后访问根;
此篇讨论的二叉树的遍历分为先序遍历(递归,非递归)、中序遍历(递归,非递归)、后序遍历(递归,非递归)、层次遍历。
先、中、后序遍历的递归算法,代码一模一样,唯一不同之处是访问结点的时机不同。
先、中、后序遍历非递归算法:
从根结点开始沿左子树深入下去,当深入到最左端,无法再深入下去时,则返回,再逐一进入刚才深入时遇到结点的右子树,再次进行上述的深入和返回,直到最后从根结点的右于树返回到根结点为止。
先序遍历是在深入时遇到结点就访问,中序遍历是在从左子树返回时遇到结点访问,后序遍历是在从右子树返回时遇到结点访问。(访问时机不同)
在这一过程中,返回结点的顺序与深入结点的顺序相反,即后深入先返回,正好符合栈结构后进先出的特点。因此,可以用栈来帮助实现这一遍历路线。其过程如下:
①在沿左子树深入时,深入一个结点入栈一个结点,若为先序遍历,则在入栈之前访问之;沿左分支深入不下去时,从栈中弹出前面压入的结点。
②若为中序遍历,则此时访问该然后,从该结点的右子树继续深入。
③若为后序遍历,则将此结点再次入栈,然后从该右子树继续深入,与前面类同,仍为深入一个结点入栈一个结点,深入不下去再返回第二次从栈里弹出该结点,才访问
- 先序遍历
//二叉树中序遍历递归算法
void PreOrder(BiTree *t)
{
if(t!=NULL)
{
printf("%c ",t->data); //先访问结点再往后走
PreOrder(t->left);
PreOrder(t->right);
}
}
//二叉树先序遍历(非递归)
void PreOrder_Nonrecursive(BiTree *t)
{
BiTree *s[M],*p; //数组模拟栈
int top=0; //栈的初始化
if(t!=NULL) //二叉树非空
{
p=t; //指向根结点
do
{
while(p!=NULL) //非空
{
printf("%c ",p->data); //访问
s[top]=p; //入栈
top++;
p=p->left; //向左走
}
if(top>0) //向左走不下去并栈非空
{
top--;
p=s[top]; //出栈
p=p->right; //向右走
}
}while(p!=NULL||top>0); //遍历指针为空并且栈为空循环结束
}
}
- 中序遍历
中序遍历非递归与先序遍历非递归算法一样,只是访问语句时机不同,中序遍历是在左子树回来(出栈)后 访问,而先序遍历是先访问后入栈。
//二叉树中序遍历递归算法
void InOrder(BiTree *t)
{
if(t!=NULL)
{
InOrder(t->left);
printf("%c ",t->data); //从左子树回来后访问结点
InOrder(t->right);
}
}
//二叉树中序遍历(非递归)
void InOrder_Nonrecursive(BiTree *t)
{
BiTree *s[M],*p;
int top=0;
if(t!=NULL)
{
p=t; //根结点入栈
do
{
while(p!=NULL) //非空,入栈,向左走
{
s[top]=p;
top++;
p=p->left;
}
if(top>0) //出栈,访问,向右走
{
top--;
p=s[top];
printf("%c ",p->data);
p=p->right;
}
}while(p!=NULL||top>0); //遍历指针为空并栈为空结束算法
}
}
- 后序遍历
从右子树回来后访问该结点,为记录该结点的状态,即从左子树回来还是从右子树回来,要对栈进行改造,即加入一个标记域。
typedef struct
{
BiTree *ptr;
char tag; //结点状态标记域
}seqstack;
后序遍历中结点要入栈两次,访问左子树时入栈一次,访问右子树时入栈一次,从右子树回来(出栈时标记域为'R')则访问该结点
在后序遍历算法中,一维数组s[M]用于栈的结构,整型变量top表示栈顶的位置,指针变量p指向当前要处理的结点,q为进出栈的栈元素(由结点指针ptr和标志tag组成)
//二叉数后序遍历递归算法
void PostOrder(BiTree *t)
{
if(t!=NULL)
{
PostOrder(t->left);
PostOrder(t->right);
printf("%c ",t->data); //从右子树回来后访问结点
}
}
//二叉树后序遍历(非递归)
void PostOrder_Nonrecursive(BiTree *t)
{
BiTree *p;
seqstack s[M],q; //改造后的栈
int top=0;
if(t!=NULL) //二叉树非空
{
p=t; //指向头结点
}
do
{
while(p!=NULL) //非空
{
q.ptr=p;
q.tag='L'; //设左标记
s[top]=q; //入栈
top++;
p=p->left; //向左走
}
top--; //向左走不下去,出栈
q=s[top];
p=q.ptr;
while(q.tag=='R') //判断标记是否为“R”,即是否从右子树回来
{
printf("%c ",p->data); //访问
if(p==t) //指向从右子树回来头结点时循环结束,此时二叉树已遍历完,栈也为空,
{
break;
}
if(top>0) //再出栈,循环条件判断标记域
{
top--;
q=s[top];
p=q.ptr;
}
}
if(q.tag=='L') //从左子树回来,修改标记为“R”,继续入栈,向右走
{
q.ptr=p;
q.tag='R';
s[top]=q;
top++;
p=p->right;
}
}while(top>0); //栈为空算法结束
}
- 层次遍历
从根结点开始,逐层按从左到右的顺序访问。对每一层的结点,先从左到右依次访问,后按此顺序访问他们的左右子树,其访问实质与队列(先进先出)的定义相同,故引进队列。根结点先入队,只要循环队列非空(循环条件)就出队一个元素,访问该结点,后判断该结点的左右子树是否为空,若空则下一个结点出队,否则该结点的左右孩子先入对,后下一个结点出队,如此往复,直到循环队列为空 。
//二叉树层次遍历算法
void LevelOrder(BiTree *t)
{
BiTree *p;
BiTree *queue[M]; //数组模拟实现循环队列
int front,rear; //循环队列的前后指针
if(t==NULL) //判断二叉树是否为空二叉树
{
return;
}
front=rear=0; //循环队列初始化
queue[rear]=t; //根结点入队
rear=(rear+1)%M; //rear指针后移
while(front!=rear) //循环队列非空
{
p=queue[front]; //出队
front=(front+1)%M;
printf("%c ",p->data);
if(p->left!=NULL) //其左子树非空则入队
{
queue[rear]=p->left;
rear=(rear+1)%M;
}
if(p->right!=NULL) //其右子树非空则入队
{
queue[rear]=p->right;
rear=(rear+1)%M;
}
}
}
下图为一张运行截图
此文章仅个人学习总结,如有错误,请指正,若转载请注明出处