非递归,不用栈实现二叉树中序遍历(一)

2014-11-24 02:49:13 · 作者: · 浏览: 3

  最近总有人问这个问题:“如何不用栈,也不用递归来实现二叉树的中序遍历”。这个问题的实现就是迭代器问题,无论是Java还是C++,利用迭代器遍历树节点(Java中是TreeMap类,C++中是map类)都使用了中序遍历,且无法使用递归和栈,算法效率近似为O(1),不可能每个节点只访问一次。

  纯C实现的办法很简单,先定义类型。

// 定义byte_t类型
typedef unsigned char byte_t;

// 定义bool类型
typedef unsigned char bool;
#define true  1
#define false 0

// 定义用于比较的函数指针类型
typedef int(*treeset_compare_t)(const void*, const void*);

  下面结构体中的data[0]是C语言一种特殊用法,data[0]本身不表示任何大小,可以看作是一个指针,表明该结构体在内存中占据的实际大小会超过结构体本身的字节数,这样,data指针就自动指向结构体中多余的空间,该空间可以用来存储节点值。并不是所有的C编译器都能这么做,但我测试过好像就VC++6不行,这段代码在GCC下编译通过。

\

\

/**
 * 定义二叉树节点类型
 */
typedef struct _btree_node
{
	struct _btree_node* parent;	// 指向父节点的指针
	struct _btree_node* lchild;	// 指向左孩子的指针
	struct _btree_node* rchild;	// 指向右孩子的指针
	struct _treeset* owner;	// 节点所属的树
	byte_t data[0];
} btree_node;
/**
 * 定义二叉树结构体
 */
typedef struct _treeset
{
	struct _btree_node* root;	// 二叉树根节点
	size_t count;	// 二叉树节点数量
	size_t elemsize;
	treeset_compare_t compare;
} treeset;

  

  定义了以上类型,就可以进一步完成二叉树初始化以及节点添加,删除,查找等函数了,函数声明如下。

/**
 * 初始化二叉树结构体
 * @param tree 指向二叉树结构体的指针
 * @param elemsize 每个元素的大小
 * @param comp 用于比较的函数指针
 */
void treeset_init(treeset* tree, size_t elemsize, treeset_compare_t comp);

/**
 * 释放二叉树占据的空间
 * @param tree 指向二叉树结构体的指针
 */
 void treeset_free(treeset* tree);

/**
 * 向二叉树中添加元素
 * @param tree 指向二叉树结构体的指针
 * @param value 指向要添加元素的指针
 * @return 是否实际添加了节点
 */
bool treeset_add(treeset* tree, const void* value);

/**
 * 从二叉树中删除一个元素
 * @param tree 指向二叉树结构体的指针
 * @param value 要删除的节点内容
 * @return 是否删除了节点
 */
bool treeset_remove(treeset* tree, const void* value);

/**
 * 在二叉树中查找一个元素
 * @param tree 指向二叉树结构体的指针
 * @param value 指向要查找内容的指针
 * @return 是否包含要查询的值
 */
bool treeset_contain(const treeset* tree, const void* value);

  这部分函数实现如下:

  下面这个函数用于初始化二叉树,其中参数elemsize表示每个树节点要存放的值所占内存大小,例如每个节点要保存一个整数,则elemsize的值应该为sizeof(int),这个大小将直接反映在每个树节点上,由节点结构体分量data来表示。comp参数是一个函数指针,前面定义过,用来表示比较两个值的大小。

/**
 * 初始化二叉树结构体
 * @param tree 指向二叉树结构体的指针
 * @param elemsize 每个元素的大小
 * @param comp 用于比较的函数指针
 */
void treeset_init(treeset* tree, size_t elemsize, treeset_compare_t comp)
{
	tree->root = NULL;
	tree->count = 0;
	// 设置节点存储的元素大小
	tree->elemsize = elemsize;
	// 设置用于元素大小比较的函数指针
	tree->compare = comp;
}


  下面这个函数用于释放二叉树所占据的内存空间,其中调用了一个remove_all_node函数,后面介绍
/**
 * 释放二叉树占据的空间
 * @param tree 指向二叉树结构体的指针
 */
 void treeset_free(treeset* tree)
 {
 	// 从头节点开始移除二叉树中所有的节点
	remove_all_node(tree->root);
	// 将二叉树所有内容还原为空
	memset(tree, 0, sizeof(*tree));
 }


  下面这个函数用于向二叉树中存放内容,存放的原则就是从二叉树头节点开始,依次进行比较,比节点值大的放一边,小的放另一边,相当于二分查找和插入,具体方式如图:

\

/**
 * 向二叉树中添加元素
 * @param tree 指向二叉树结构体的指针
 * @param value 指向要添加元素的指针
 * @return 是否实际添加了节点
 */
int treeset_add(treeset* tree, const void* value)
{
	int result = 0;

	// 判断二叉树中是否有头节点
	if (tree->count == 0)
	{
		// 创建头节点
		tree->root = create_new_node(tree, value);
		result = 1;
	}
	else
	{
		/*
			对于添加节点,具体操作如下:
			1. 通过要添加节点的值和现有某个节点(从头节点开始)进行比较(通过指定的比较函数进行)
			2. 如果要添加的节点值和现有某个节点值相同,则无需添加节点
			3. 如果要添加的节点值和现有某个节点值不同,则根据比较结果继续访问该节点的左支或者右支
			添加流程示意图参看[图1]
		 */
		// 表示比较结果
		int comp;
		// node变量指向要比较的节点,从头结点开始;parent变量指向其父节点
		btree_node* node = tree->root, *parent;

		// 遍历所有节点,直到没有节点为止
		while (node)
		{
			// 保存父节点指针
			parent = node;
			// 比较要添加的值和当前节点值
			comp = tree->compare(value, node + 1);
			// 判断比较结果
			if (comp == 0)
				break;