数据结构学习笔记-二叉树的前、中、后序遍历,递归、非递归方式

树和二叉树

完全二叉树,满二叉树

性质:1.二叉树的第k层结点个数最多为:2^(k-1)个

           2.二叉树的深度为k则总共的结点数为:2^(k)-1个

           3.二叉树中度为0的结点比度为2的结点多1个

           4.二叉树中的结点数为n,则树的深度至少为log2[n]+1,"[ ]"这里表示向下取整

二叉树的建立

       一般都采用前序遍历来生成二叉树。二叉树有两种存储结构,一种是顺序存储结构,另一种是链式存储结构。这里我们用链式存储结构来生成二叉树。

typedef struct Btnode
{
	char value;
	struct Btnode *lchild;
	struct Btnode *rchild;
}BTNODE;

BTNODE *creat_binary_tree(BTNODE *T)
{
	char x;

	scanf("%c",&x);
	if(x!=' ')
	{
		T = (BTNODE *)malloc(sizeof(BTNODE));
		if(T==NULL)
		{
			printf("内存分配失败\n");
			exit(-1);
		}
		T->value = x;
		T->lchild = creat_binary_tree(T->lchild);
		T->rchild = creat_binary_tree(T->rchild);
		return T;
	}
	else
		return NULL;
}

二叉树的遍历有三种:前序,中序,后序

用递归的方式比较容易实现二叉树的遍历,这里就写下前序遍历的递归形式

void PreOrderTraverse(BTNODE *root)
{
	if(NULL!=root)
	{
		printf("%c",root->data);
		PreOrderTraverse(root->lchild);
		PreOrderTraverse(root->rchild);
	}
}
中序遍历、后序遍历只是调用递归语句时的位置不同。

二叉树遍历的非递归方式:

二叉树遍历的非递归方式需要用到栈的相关操作,下面的代码将在二叉树的前序、中序遍历中用到,包含栈的操作和一些结构体的定义:

typedef struct Btnode
{
	char data;
	struct Btnode *lchild;
	struct Btnode *rchild;
}BTNODE;

typedef struct Stnode
{
	BTNODE *value;
	struct Stnode *pNext;
}STNODE;

typedef struct Stack
{
	STNODE *pTop;
	STNODE *pBottom;
	int slength;
}STACK;

void InitStack(STACK *s)
{
	s->pTop = NULL;
	s->pBottom = NULL;
	s->slength = 0;
}                                   //多加了一个分号!!!!!!!!

void Push(STACK *s,BTNODE *p)
{
	STNODE *pNew;
	pNew = (STNODE *)malloc(sizeof(STNODE));
	if(pNew == NULL)
	{
		printf("memory distribution error\n");
		exit(-1);
	}
	pNew->value = p;	
	if(s->slength == 0)
	{
		pNew->pNext = NULL;
		s->pTop = pNew;
		s->pBottom = s->pTop;
	}
	else
	{
		pNew->pNext = s->pTop;
		s->pTop = pNew;
	}
	++(s->slength);
}

int StackEmpty(STACK *s)
{
	if(s->slength == 0)
		return 1;
	else
		return 0;
}

BTNODE *GetTop(STACK *s)
{
	if(s->slength!=0)
	{
		return s->pTop->value;
	}
	else
		return NULL;
	
}

BTNODE *Pop(STACK *s)
{
	STNODE *p;
	BTNODE *q;	
	if(s->slength!=0)
	{
		p = s->pTop;
		s->pTop = p->pNext;
		q = p->value;
		--(s->slength);
		free(p);
		return q;
	}
	else 
		return NULL;
}
二叉树前序、中序遍历的非递归形式:
void PreOrderTraverse(BTNODE *root)
{
	BTNODE *p;
	STACK s;
	InitStack(&s);
	p = root;
	while(p||!StackEmpty(&s))//
	{
		if(p)
		{
			printf("%c",p->data);
			Push(&s,p);
			p = p->lchild;
		}
		else
		{
			p = Pop(&s);
			p = p->rchild;
		}
	}
}

void InOrderTraverse_1(BTNODE *root)
{
	BTNODE *p;
	STACK s;
	p = root;
	InitStack(&s);
	Push(&s,p);
	while(!StackEmpty(&s))
	{
		while(p!=NULL)
		{
			Push(&s,p->lchild);
			p = p->lchild;
		}
		Pop(&s);
		if(!StackEmpty(&s))
		{
			p = Pop(&s);
			printf("%c",p->data);
			Push(&s,p->rchild);
			p = p->rchild;
		}
	}
}

void InOrderTraverse_2(BTNODE *root)
{
	BTNODE *p;
	STACK s;
	p = root;
	InitStack(&s);
	while(p||!StackEmpty(&s))
	{
		if(p)
		{
			Push(&s,p);
			p = p->lchild;
		}
		else
		{
			p = Pop(&s);
			printf("%c",p->data);
			p = p->rchild;
		}
	}
}
二叉树后序遍历的非递归形式需要在入栈的元素中加入标志位,因此在结构体的定义和操作上略有不同,下面给出在结构体和栈操作上的不同点:
typedef struct Snode
{
	BTNODE *value;
	struct Snode *pNext;
	int flag;          //设置标志位
}SNODE;

void Push(STACK *s,BTNODE *p)
{
	SNODE *pNew;
	pNew = (SNODE *)malloc(sizeof(SNODE));
	if(NULL==pNew)
	{
		printf("内存分配失败\n");
		exit(-1);
	}
	pNew->value = p;
	pNew->flag = 0; //相当于在入栈的时候对flag进行初始化
	if(s->slength == 0)
	{
		pNew->pNext = NULL;
		s->pTop = pNew;
		s->pBottom = s->pTop;
	}
	else
	{
		pNew->pNext = s->pTop;
		s->pTop = pNew;
	}
	++(s->slength);
}

BTNODE *Pop(STACK *s)
{
	SNODE *q;
	BTNODE *p;
	if(s->slength!=0)
	{
		q = s->pTop;
		p = q->value;
		s->pTop = q->pNext;
		free(q);
		--(s->slength);
		return p;
	}
	else
		return NULL;
}
二叉树后序遍历的非递归形式:
void PostOrderTraverse(BTree *root)
{
	STACK s;
	BTNODE *p;
	InitStack(&s);
	p = root;
	Push(&s,p);//将根压入栈中
	while(!StackEmpty(&s))
	{
		if(p&&(s.pTop->flag==0))
		{
			s.pTop->flag = 1;
			Push(&s,p->lchild);//
			p = p->lchild;	
		}
		else
		{
			if(p==NULL)
			{
				Pop(&s);           //空指针退栈
			}
			if(s.pTop->flag == 1) //s不是地址,不能用->
			{
				p = GetTop(&s);
				p = p->rchild;
				s.pTop->flag = 2;//先改变此时栈顶的元素状态,顺序很重要
				Push(&s,p);		
			}
			if(s.pTop->flag == 2)
			{
				p = Pop(&s);
				printf("%c",p->data);
				p = GetTop(&s);
			}
		}
	}
}
/* 
1、初始结点初始状态为0
2、当结点压入栈中时,结点状态变为1,入栈的顺序是沿着根结点的左子树一路向下,一直到叶子结点的左孩子,(即空指针入栈)
   随后空指针退栈。(这样一路走下来,入栈的元素均可以认为是遍历了其左子树,因此结点状态置为1)。从栈顶读元素值,改变
   栈顶元素的状态位,将其设置为2,并将栈顶元素的右孩子压入栈
3、状态位为0,用来判断是否要入栈
   状态位为1,说明已经遍历了该结点的左子树,下一步应将该结点的右孩子入栈
   状态位为2,说明已经将该结点的左、右子树均遍历完,此时应该输出此双亲结点
*/
完整的代码就是加上头文件和函数声明,这里就从简了。
看了许多其他人写的代码,很多代码在实现过程中都用到了引用,确实在代码中使用引用的方式能够使代码更清晰也更方便。只是个人觉得c里面没有引用,如果要是在纯c的编译器中可能会出问题,也不知道在c中混用c++中的方法是否是个好习惯。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值