瞅数据结构写代码(28) 线索二叉链表的实现

看数据结构写代码(28) 线索二叉链表的实现

要说 线索二叉链表,不得 不说一说 二叉链表的遍历。二叉链表的 遍历 其实 就是 将 树型结构 转换 成 一种 线性结构。不过 这种 节点 前驱 后继的关系,只能 在 遍历的时候 动态 获得。拿有没有一种方法 可以 保存 这种 前驱 后继的关系呢? 总的来看 有 两种方法:

1. 将二叉链表 加上 节点 加上 前驱 ,后继的 指针

2. 一个n个节点的 二叉树,必定有 n+1 个空链域。(n个 节点 有 2n 个 链域,这些链域 指向了 n-1 个节点(根节点没有被指),所以 空链域 = 2n - (n-1) = n + 1),那么 可以 利用 这些 空 链域 来存放前驱 后继的信息。 这样 我们 需要 增加 两个标志位, leftTag 和 rightTag,当leftTag   为 0 的时候 leftChild 指向的 是 左孩子,当为1 的 时候 表示 指向前驱 ,当 rightTag 为0 的时候 指向 右 孩子,为1 指向 后继。 这些 为1 的 链域,我们称 之为 线索。加了 线索的 二叉链表 叫做 线索二叉链表。

关于 这两种方法,书中 说的 第一种 浪费 存储空间,降低了存储密度,我就有点 不理解了, 两个指针 占用空间 为 2 * 4 = 8, 可 就算 标志位 用 枚举型 也是 占用 2*4 = 8个字节,就算 用 bool型,也没 减少 多少存储空间。为什么 不用 第一种呢? 第一种 使用起来 多方便。 不明白,可能 自己的 境界 不够高,看得不够远吧。

还有 一点 疑惑的 是 树中 说的 头节点 的 leftChild 指向 根节点,rightChild 指向 最后一个 被访问的节点。为什么 不能 把 leftChild指向 第一个被 访问的节点呢? 这样 对于 遍历 算法的 清晰度 更有益处。 下面 图示:

瞅数据结构写代码(28) 线索二叉链表的实现

我下面的 线索花 算法 就更改了 头节点 leftChild 指向 第一个被访问的 节点。 。

至于 这样 是对, 还是错呢? 是不是 对 其他 操作 有不便呢? 等待 自己 回答吧。

在上代码 之前 有必要 说一下 线索二叉链表 根据 遍历 根节点 的 顺序,分为,先序遍历,中序遍历,后序遍历

除了 中序 遍历,其他的两种线索二叉链表 是 不 完善的 。

不完善 指的 是 在 查找 前驱 后继时 需要 知道 父亲 节点的信息,无法 直接 获取。

中序 查找 节点 前驱 为 左子树的 右下角,后继 为 右子树的 左下角

先序 查找 前驱 为 节点 父 节点 或者 父 节点的 左子树,需要 借助 栈,无法直接获取。查找 后继  为 左 子树 或者 右子树(左子树为空)。

后序查找 前驱 为 左子树 或者右子树,查找 后继 为  父亲的 右子树 (最后 被 访问的节点) 或者 父亲节点 ,借助栈,无法直接获取。

所以 中序 线索二叉链表 是 最合适的。 如果 想用 先序 ,后序 线索,请用 线索三叉链表。有空 实现以下吧。

说了这么多,下面 上代码吧,今天 也够累的了。

源代码网盘地址:点击打开链接

// binaryTree.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <cstdlib>

typedef int ElementType;
enum E_State
{
	E_State_Error = 0,
	E_State_Ok = 1
};

enum E_Tag
{
	E_Tag_Link = 0,//指针,指向左右子树
	E_Tag_Threaded = 1,//线索,指向前驱后继
};

typedef  struct ThBiTreeNode
{
	ElementType data;
	ThBiTreeNode * leftChild;
	ThBiTreeNode * rightChild;
	E_Tag leftTag;
	E_Tag rightTag;
} * ThBiTree;

void treeInit(ThBiTree * tree){
	*tree = NULL;
}
//其实 自己 不懂 这个 函数 初始化...
E_State treeCreate(ThBiTree *tree){
	ElementType data;
	scanf("%d",&data);
	if (data == 0)
	{
		*tree = NULL;
	}else
	{
		*tree = (ThBiTree)malloc(sizeof(ThBiTreeNode));
		(*tree)->data = data;
		//设置 默认 E_Tag_Link
		(*tree)->leftTag = E_Tag_Link;
		(*tree)->rightTag = E_Tag_Link;
		treeCreate(&(*tree)->leftChild);
		treeCreate(&(*tree)->rightChild);
	}
	return E_State_Ok;
}


//后序遍历 销毁树
void treeClear(ThBiTree * tree){
	if (tree != NULL)
	{
		if ((*tree)->leftChild != NULL)
		{
			treeClear(&(*tree)->leftChild);
		}
		else if((*tree)->rightChild != NULL)
		{
			treeClear(&(*tree)->rightChild);
		}
		free(*tree);
		*tree = NULL;
	}
}

void treeDestory(ThBiTree * tree){
	treeClear(tree);
}

//二叉树的 中序 线索化...
void inThreading(ThBiTree tree,ThBiTree &pre){
	if (tree)
	{
		inThreading(tree->leftChild,pre);
		if (tree->leftChild == NULL)//当前节点左孩子为空
		{
			tree->leftChild = pre;
			tree->leftTag = E_Tag_Threaded;
		}
		if (pre->rightChild == NULL)//前驱右孩子为空
		{
			pre->rightChild = tree;
			pre->rightTag = E_Tag_Threaded;
		}
		pre = tree;
		inThreading(tree->rightChild,pre);
	}
}

//根课本上最大的不同,头节点 的leftChild 指向 第一个 被访问的节点.
E_State inOrderThreading(ThBiTree & head,ThBiTree tree){
	head = (ThBiTree)malloc(sizeof(ThBiTreeNode));
	if (head != NULL)
	{
		head->leftTag = E_Tag_Link;
		head->rightTag = E_Tag_Threaded;
		if (tree == NULL)//空树
		{
			head->leftChild = head;//左右孩子都指向自己
			head->rightChild = head;
		}
		else
		{
			ThBiTree pre = head;//保存线索化中的 前驱
			//head->leftChild = tree;//头指针左孩子 指向根节点..
			inThreading(tree,pre);
			pre->rightChild = head;pre->rightTag = E_Tag_Threaded;//最后一个节点 指向头指针
			//查找 第一个 被访问的节点。
			//将头节点的 左孩子 设置 成 第一个 被访问的节点.
			ThBiTree firstNode = tree;
			while (firstNode->leftTag == E_Tag_Link)
			{
				firstNode = firstNode->leftChild;
			}
			head->leftChild = firstNode;
			head->rightChild = pre;//头指针右孩子 指向最后 访问的 节点
		}
		return E_State_Ok;
	}
	return E_State_Error;
}

//获取前驱
ThBiTree treeGetPre(ThBiTree tree){
	ThBiTree pre = NULL;
	if (tree != NULL)
	{
		if (tree->leftTag == E_Tag_Threaded)
		{
			pre = tree->leftChild;
		}
		else//前驱是 左子树的 右下角
		{
			pre = tree->leftChild;
			while (pre->rightTag == E_Tag_Link)
			{
				pre = pre->rightChild;
			}
		}
	}
	return pre;
}

//获取 后继
ThBiTree treeGetNext(ThBiTree tree){
	ThBiTree next = NULL;
	if (tree != NULL)
	{
		if (tree->rightTag == E_Tag_Threaded)//当rightTag是线索是,rightChild 指向后继
		{
			next = tree->rightChild;
		}
		else//是指针时,指向右子树的最左下角
		{
			next = tree->rightChild;//不用 担心节点的 右子树 为空的问题,因为tag 不是线索,必不为空
			while (next->leftTag == E_Tag_Link)
			{
				next = next->leftChild;
			}
		}
	}
	return next;
}

//中序线索遍历..
void inOrderTraverse(ThBiTree head){
	printf("\n------------中序顺序访问--------------\n");
	ThBiTree next = head->leftChild;//指向第一个被访问的节点.
	while (next != head)//当树空时 或者 到达 结尾时
	{
		printf("%d\t",next->data);
		next = treeGetNext(next);
	}
}

//中序 逆序 遍历(查找前驱)
void inOrderTraversePre(ThBiTree head){
	printf("\n------------中序逆序访问--------------\n");
	ThBiTree pre = head->rightChild;
	while (pre != head)
	{
		printf("%d\t",pre->data);
		pre = treeGetPre(pre);
	}
}


int _tmain(int argc, _TCHAR* argv[])
{
	ThBiTree tree;
	printf("---------------创建树(先序,0代表子树为空)-----------------\n");
	treeCreate(&tree);
	ThBiTree head;
	inOrderThreading(head,tree);
	inOrderTraverse(head);
	inOrderTraversePre(head);
	return 0;
}
运行截图:

瞅数据结构写代码(28) 线索二叉链表的实现