瞅数据结构写代码(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指向 第一个被 访问的节点呢? 这样 对于 遍历 算法的 清晰度 更有益处。 下面 图示:
我下面的 线索花 算法 就更改了 头节点 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; }运行截图: