非递归学习树结构(二)--前中后遍历



树是数据结构里面比较复杂的一种结构,很多科班出身的程序员都不一定搞得清楚其具体的结构与原理,本人本科使用的是严的《数据结构》,大二时开始学习的,当时编程能力尚处在起步阶段,对于书中一些数据结构和算法没有能力切切实实的实现。然而,作为一名合格的程序员,有些知识是我们应该具备的。有人说,我不会栈,队列,更不会任何一种树,不也是好好的当我的程序员吗?道理很简单,书到用时方恨少,未雨绸缪。如果你理解了一些树的原理和用途,说不定会给你遇到的问题带来另外一种更好的解决方式。


栈结构,队列结构,树结构,栈和队列比较简单一些,树结构比较复杂,而且树的类型也很多,变种更多,如BST(二叉搜索树),AVL(自平衡二叉搜索)树,RB-Tree(红黑树),B-Tree(多路搜索树),R-Tree(索引树)等,要想弄明白这些树结构的实现原理和操作方法,我们首先需要掌握理解一些树的基本知识,如建树过程,树的各种遍历,树的遍历包括先根遍历(DLR),中根遍历(LDR),后根遍历(LRD)(有的叫前序、中序、后序遍历,但是不如先根,中根,后根更能表达出遍历的规则),广度优先搜索,深度优先搜索。


下面本文就以分析代码的形式来理解掌握一棵树的建立过程,以及用先根、中根、后根方式遍历一颗树。提到树的遍历,也许很多人都会想到递归,递归使得代码非常的简洁,然而对于我们理解学习原理来说不太适合,非递归方式虽然代码繁多,但是能给人更加直观的感受。本文将会给出递归实现和非递归实现的两种版本。非递归实现要借助栈结构,相比大多数人都知道栈就是LIFO(后进先出),对于理解本文代码就足够了,本文代码中使用的栈结构是自己实现的,而且尝试用纯C模仿一下C++的有函数的结构体,具体请参考“纯C模仿C++有函数的结构体”一文。


/*此头文件的引用以及头文件中函数的实现请查阅“从零学习Tree0—栈和队列”一文*/
/*BST.h*/
#ifndef __BST__H
#define __BST__H

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int	RESULT;
#ifndef SUCCESS
#define SUCCESS 0
#endif
#ifndef FAILURE
#define FAILURE -1
#endif

typedef struct _Node
{
	struct _Node* plc; /*左字结点指针*/
	struct _Node* prc; /*右子结点指针*/
	int key;  /*key字段*/
	int val;  /*val字段*/
	int cnt;  /*计数*/
}Node;

#define ELEM_TYPE Node /* 堆栈所存储的值的数据类型 */

/*建树 插入节点*/
RESULT insert(Node** root, int key, int val);

/*递归遍历*/
void DLR(const Node* root);/*先根*/
void LDR(const Node* root);/*中根*/
void LRD(const Node* root);/*后根*/

/*非递归遍历*/
void _DLR(Node* root);/*先根遍历*/
void _LDR(Node* root);/*中根遍历*/
void _LRD(Node* root);/*后根遍历*/

/*非递归广度优先搜索*/
void BFS(Node* root);

/*非递归深度优先遍历*/
void DFS(Node* root);

#endif//__BST__H


/*BST.c*/
#include “BST.h”
#include "stack.h"

/*创建节点*/
Node* createnode(int key,int val)
{
	Node* node = (Node*)malloc(sizeof(Node));
	node->key = key;
	node->val = val;
	node->cnt = 1;
	node->plc = NULL;
	node->prc = NULL;
	return node;
}
/*插入节点*/
RESULT insert(Node** root, int key, int val)
{
	Node* node = createnode(key, val);
	if (NULL == *root)
	{
		if (NULL == node)
		{
			perror("createnode error!!!\n");
			return FAILURE;
		}
		*root = node;
	}
	else
	{
		Node* tmp = *root;
		while (1)
		{
			if (key == tmp->key)
			{
				tmp->cnt++;
				return SUCCESS;
			}
			else if (key < tmp->key)
			{
				if (tmp->plc == NULL)
				{
					tmp->plc = node;
					return SUCCESS;
				}
				tmp = tmp->plc;
				continue;
			}
			else
			{
				if (tmp->prc == NULL)
				{
					tmp->prc = node;
					return SUCCESS;
				}
				tmp = tmp->prc;
				continue;
			}
		}

	}
	return SUCCESS;
}
/*递归遍历*/
void DLR(const Node* root)
{
	if (root != NULL)
	{
		printf("%d\t", root->val);
		DLR(root->plc);
		DLR(root->prc);
	}
}

void LDR(const Node* root)
{
	if ( root != NULL )
	{
		LDR( root->plc );
		printf("%d\t",root->val);
		LDR( root->prc );
	}
}

void LRD(const Node* root)
{
	if (root != NULL)
	{
		LRD(root->plc);
		LRD(root->prc);
		printf("%d\t", root->val);
	}
}
/*非递归遍历*/
void _DLR(Node* root)
{
	Node* tmp = root;
	Stack stack;
	registstack(&stack);
	stack.init_size(&stack,1000);

	if (NULL == root)
	{
		printf(" empty tree!!!\n ");
		return;
	}
	//
	while (tmp || !stack.is_empty(&stack))
	{
		while (tmp)/*将根节点,根节点的左子节点,左子节点的左子节点,一直到最左子节点,全部入栈*/
		{
			stack.push(&stack, tmp); 
			printf( "%d\t",tmp->val );/*入栈是输出当前结点的val*/
			tmp = tmp->plc;
		}
		tmp = stack.top(&stack);/*获取栈顶的结点,栈顶结点是没有左子树(最左子节点)或者左子树已经遍历完毕的结点*/
		stack.pop(&stack);/*清除栈顶元素*/
		tmp = tmp->prc;/*遍历当前栈顶元素的右子树*/
	}

	stack.destroy(&stack);
}
/*中序遍历和前序遍历的过程是一样的,只是访问结点val的时机不一样
最左子节点:访问时一直向左访问,知道不能向左访问时,此时的结点就是最左子节点*/
void _LDR( Node* root )
{
	Node* tmp = root;
	Stack stack;
	registstack(&stack);
	stack.init_size(&stack, 1000);

	if (NULL == root)
	{
		printf(" empty tree!!!\n ");
		return;
	}

	while (tmp || !stack.is_empty(&stack))
	{
		while (tmp)
		{
			stack.push(&stack, tmp);
			tmp = tmp->plc;
		}
		tmp = stack.top(&stack);
		stack.pop(&stack);
		printf("%d\t", tmp->val);/*第一次输出的肯定是最左子节点,此时最左子节点要么是叶子节点,要么是只有右子树的结点*/
		tmp = tmp->prc;
	}

	stack.destroy(&stack);
}

void _LRD( Node* root )
{
	int flag[1000];/*标记结点是否已经被访问过*/
	Stack stack;
	registstack(&stack);
	stack.init_size(&stack, 1000);

	if (NULL == root)
	{
		printf(" empty tree!!!\n ");
		return;
	}
	Node* tmp = root;
	/*将所有的左子节点入栈*/
	while (tmp)
	{
		stack.push(&stack,tmp);
		flag[stack.size] = 0;
		tmp = tmp->plc;
	}
	/*循环处理栈内结点*/
	while (!stack.is_empty(&stack))
	{
		tmp = stack.top(&stack);/*取栈顶结点,第一次被取出来的肯定是最左子节点*/
		while ( tmp->prc && flag[stack.size] == 0) //如果最左子节点有右子节点并且当前节点的右子节点尚未被访问*/
		{
			flag[stack.size] = 1;/*标记为1说明当前结点的右子树开始被访问*/
			tmp = tmp->prc;
			while (tmp)/*将当前结点的右子树的全部左子节点入栈,*/
			{
				stack.push(&stack,tmp);
				flag[stack.size] = 0;
				tmp = tmp->plc;
			}
			tmp = stack.top(&stack);
		}
		tmp = stack.top(&stack);
		printf("%d\t",tmp->val);
		stack.pop(&stack);
	}
	stack.destroy(&stack);
}


/*test.c*/
#include “BST.h”
#include "stack.h"

int main()
{
	Node* root = NULL;
	int array[9] = {20,15,13,14,16,28,30,22,25};
	int index = 0;

	do{
		insert(&root, array[index], array[index]); 
	} while (++index < 9);

	printf(" \n前序遍历(DLR)\n ");
	DLR(root);
	printf(" \n非递归前序遍历(DLR)\n ");
	_DLR(root);
	printf(" \n中序遍历(LDR)\n ");
	LDR(root);
	printf(" \n非递归中序遍历(LDR)\n ");
	_LDR(root);
	printf(" \n后序遍历(LRD)\n ");
	LRD(root);
	printf(" \n非递归后序遍历(LRD)\n ");
	_LRD(root);
	printf("\n");

	return 0;
}

打印出来结果,我们发现,中根遍历输出结果是从小到大的顺序,造成这种结果的原因就是我们一开始建的是一颗BST(二叉搜索树),所以中根遍历才是有序序列。对于BST,后续的篇章我们将介绍其特点和一些其他操作,比如删除节点操作。


 代码只是为了更好的说明先根、中根、后根遍历的过程所用,错误之处请不吝指正。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值