二叉树的线索化(C++实现)
2017-02-05 22:54
309 查看
对于一棵二叉树,我们可以通过前序,中序,后序,层序四种遍历方式得到关于二叉树的序列,这样从每个结点出发就很容易找到它在某种次序下前序和后继。
我们观察各种二叉树的结构,发现不管儿叉树的形态如何,空链域的个数总是多过非空链域的个数。准确的说,n个结点的指针域共有2n个,非空指针域为n-1个,但其中的空指针域却有n+1个。
这是因为,对于二叉树的每个结点都有左右孩子,但是每个结点(除根节点)只有一个父亲,因此n个结点空指针域为**2n - (n - 1) = n + 1个。
比如下图,其空指针域个数为 2 * 6 - 5 = 7个
因此我们可以利用这些空指针域来标记结点的前驱或者后继,规则如下:
若结点有左子树,则其左指针域指向其左孩子,否则令左指针指向其前驱
若结点有右子树,则其右指针域指向其右孩子,否则令右指针指向其后继
为了区分指针域是指向它的孩子还是前驱(后继),我们分别加一个标志位(如下图)
我们给出树的结点定义如下:
为了实现线索化,我们需要先构建一棵树,为了简单,我们采用前序方式构建树,对于给定的一个数组,如果元素为 ‘#’ 则表明当前结点为空,不然为左子树。
例如:
所构建的树如下图:
C++代码如下:
我们以上面的二叉树为例建立线索化:
前序线索化的树如上图,下面我们看看如何建立线索化。
实际上线索化的代码和遍历代码结构类似,因为线索化需要在遍历的时候完成指针之间的连接,也是遵循着前序遍历的顺序,先是根节点然后分别是左子树右子树。
以上图为例,对于1,2来说左右不为空按照前序遍历的顺序来即可,但是遍历到了3时,由于3的左右为空,我们需要完成3的前驱是2,3的后继是4这样的指针连接操作。
为了完成这样的连接操作,我们在实现代码时候需要记录一个指向前一个结点的指针prev,比如访问到3时候,cur指向3,prev指向2,同时将标志位改成THREAD,比如下面的代码
就完成了3的前驱是4的操作,然而对于3的后继是4,我们不能记录个next指针指向下一个结点,因为我们无法预知下一个结点指向哪儿,那该怎么办?
我们知道了前一个结点的指向,因此我们只需要访问下一个结点时候完成后继这一步连接:当访问到4时候,cur指向4,prev指向3,这时候只要
这样就可以玩成3的后继是4这一操作了,最后完成当前结点的线索化一定要改变prev为cur。
因此完整的前序线索化代码如下:
建立前序线索化,我们可以通过前序遍历看看我们写的是否是对的?下面分别是递归和非递归两种方法的遍历:
中序线索化和前序基本没什么区别。。。只是需要改变一下顺序而已,代码如下:
而他的遍历方式如下:
PS:关于四种遍历方式的递归和非递归实现可点击 我的github 里面的BInaryTree.h查看。
但是很多时候,我们希望很快找到某的结点的前序或者后继,又不想遍历一遍,这就需要我们记录下每个结点的前驱和后继,因此为了做到这一点,我们引入了二叉树的线索化。我们观察各种二叉树的结构,发现不管儿叉树的形态如何,空链域的个数总是多过非空链域的个数。准确的说,n个结点的指针域共有2n个,非空指针域为n-1个,但其中的空指针域却有n+1个。
这是因为,对于二叉树的每个结点都有左右孩子,但是每个结点(除根节点)只有一个父亲,因此n个结点空指针域为**2n - (n - 1) = n + 1个。
比如下图,其空指针域个数为 2 * 6 - 5 = 7个
因此我们可以利用这些空指针域来标记结点的前驱或者后继,规则如下:
若结点有左子树,则其左指针域指向其左孩子,否则令左指针指向其前驱
若结点有右子树,则其右指针域指向其右孩子,否则令右指针指向其后继
为了区分指针域是指向它的孩子还是前驱(后继),我们分别加一个标志位(如下图)
我们给出树的结点定义如下:
enum PointerTag { LINK,THREAD };//LINK表示指向的是左右孩子,THREAD则表示指向前驱或者后继 template<class T> struct BinaryTreeThreadNode { T _data;//节点数据 BinaryTreeThreadNode<T>* _left;//左孩子 BinaryTreeThreadNode<T>* _right;//右孩子 PointerTag _leftTag;//左孩子线索标志 PointerTag _rightTag;//右孩子线索标志 BinaryTreeThreadNode(const T& x = T()) : _data(x) , _left(nullptr) , _right(nullptr) , _leftTag(LINK) , _rightTag(LINK) {} };
为了实现线索化,我们需要先构建一棵树,为了简单,我们采用前序方式构建树,对于给定的一个数组,如果元素为 ‘#’ 则表明当前结点为空,不然为左子树。
例如:
int array[10] = { 1, 2, 3, '#', '#', 4, '#', '#', 5, 6 };
所构建的树如下图:
C++代码如下:
template<class T> class BinaryTreeThread { typedef BinaryTreeThreadNode<T> Node; public: BinaryTreeThread() : _root(nullptr) {} //前序方式构建二叉树 BinaryTreeThread(T *a, size_t n, const T& invalid = T()) { assert(a); size_t index = 0; _root = _createTree(a, n, index, invalid); } private: //递归建树 //四个参数含义依次为 数组名,数组大小,数组元素下标,无效值比如‘#’ Node *_createTree(T *a, size_t n, size_t& index, const T& invalid = T()) { Node *root = nullptr; if (index < n && a[index] != invalid) { root = new Node(a[index]); root->_left = _createTree(a, n, ++index, invalid); root->_right = _createTree(a, n, ++index, invalid); } return root; } private: Node *_root; };
我们以上面的二叉树为例建立线索化:
前序线索化
前序线索化的树如上图,下面我们看看如何建立线索化。
实际上线索化的代码和遍历代码结构类似,因为线索化需要在遍历的时候完成指针之间的连接,也是遵循着前序遍历的顺序,先是根节点然后分别是左子树右子树。
以上图为例,对于1,2来说左右不为空按照前序遍历的顺序来即可,但是遍历到了3时,由于3的左右为空,我们需要完成3的前驱是2,3的后继是4这样的指针连接操作。
为了完成这样的连接操作,我们在实现代码时候需要记录一个指向前一个结点的指针prev,比如访问到3时候,cur指向3,prev指向2,同时将标志位改成THREAD,比如下面的代码
cur->_left = prev; cur->_leftTag = THREAD;
就完成了3的前驱是4的操作,然而对于3的后继是4,我们不能记录个next指针指向下一个结点,因为我们无法预知下一个结点指向哪儿,那该怎么办?
我们知道了前一个结点的指向,因此我们只需要访问下一个结点时候完成后继这一步连接:当访问到4时候,cur指向4,prev指向3,这时候只要
prev->_right = cur; prev->_rightTag = THREAD;
这样就可以玩成3的后继是4这一操作了,最后完成当前结点的线索化一定要改变prev为cur。
因此完整的前序线索化代码如下:
void PreOrderThreading()
{
Node *prev = nullptr;
_preOrderThreading(_root, prev);//_root为根节点
}
void _preOrderThreading(Node *cur, Node *& prev)
{
if (cur == nullptr)
return;
//当前节点的线索化
if (cur->_left == nullptr)
{
cur->_left = prev; cur->_leftTag = THREAD;
}
if (prev && prev->_right == nullptr)
{
prev->_right = cur; prev->_rightTag = THREAD;
}
prev = cur;
//左子树的线索化
if (cur->_leftTag == LINK)
_preOrderThreading(cur->_left, prev);
//右子树的线索化
if (cur->_rightTag == LINK)
_preOrderThreading(cur->_right, prev);
}
建立前序线索化,我们可以通过前序遍历看看我们写的是否是对的?下面分别是递归和非递归两种方法的遍历:
//递归遍历 void PreOrder() { _preOrder(_root); cout << endl; } void _preOrder(Node *root) { if (root == NULL) return; cout << root->_data << " "; if (root->_leftTag == LINK) _preOrder(root->_left); if (root->_rightTag == LINK) _preOrder(root->_right); }
//非递归遍历 void PreOrderNonR() { Node *cur = _root; while (cur) { while (cur->_leftTag == LINK) { cout << cur->_data << " "; cur = cur->_left; } cout << cur->_data << " "; cur = cur->_right; //下面这种写法也可以 //while (cur && cur->_rightTag == THREAD) //{ // cur = cur->_right; // if (cur) // cout << cur->_data << " "; //} //if (cur->_rightTag == LINK) // cur = cur->_right; } cout << endl; }
中序线索化
中序线索化和前序基本没什么区别。。。只是需要改变一下顺序而已,代码如下:
void InOrderThreading()
{
Node *prev = nullptr;
_inOrderThreading(_root, prev);
}
void _inOrderThreading(Node *cur, Node *& prev)
{
if (cur == nullptr)
return;
//左子树的线索化
if (cur->_leftTag == LINK)
_inOrderThreading(cur->_left, prev);
//当前结点的线索化
if (cur->_left == nullptr)
{
cur->_left = prev; cur->_leftTag = THREAD;
}
if (prev && prev->_right == nullptr)
{
prev->_right = cur; prev->_rightTag = THREAD;
}
prev = cur;
//右左子树的线索化
if (cur->_rightTag == LINK)
_inOrderThreading(cur->_right, prev);
}
而他的遍历方式如下:
//递归遍历 void InOrder() { _inOrder(_root); cout << endl; } void _inOrder(Node* root) { if (root == NULL) return; if (root->_leftTag == LINK) _inOrder(root->_left); cout << root->_data << " "; if (root->_rightTag == LINK) _inOrder(root->_right); }
//非递归遍历 void InOrderNonR() { Node *cur = _root; while (cur) { while (cur->_leftTag == LINK) cur = cur->_left; cout << cur->_data << " "; while (cur && cur->_rightTag == THREAD) { cur = cur->_right; if (cur) cout << cur->_data << " "; } if (cur->_rightTag == LINK) cur = cur->_right; } cout << endl; }
完整代码如下可点击我的github查看
相关文章推荐
- C++实现线索化二叉树
- C++实现二叉树及其线索化和遍历
- c++模板实现二叉树,线索化,线索化遍历,非递归遍历及一些基本操作
- 数据结构_树_二叉树的线索化_C++实现
- 二叉树的遍历(C++非递归实现)
- 简单链式二叉树(C++模版技术实现)
- [C/C++] 先序建立二叉树| 先序、中序、后序遍历二叉树| 求二叉树深度、节点数、叶节点数 算法实现
- 二叉树用顺序表实现 C++代码实现
- 二叉树的后序遍历非递归算法之C++实现 选择自 xuyongfeng 的 Blog
- C++ 二叉树的构建,先序/中序/后序的递归/非递归实现
- C++实现二叉树 前序遍历, 后序遍历, 中序遍历, 层序遍历(不用递归)
- 二叉树(前中后序递归非递归遍历,层次遍历,C++实现)
- [置顶] 二叉树的建立、节点查找以及节点删除C和C++实现
- 【头文件】c++实现二叉树
- C/C++面试题(三) 推断二叉树、快速排序递归实现、递归判断数组递增
- ACM 重构二叉树 C++实现
- 求二叉树中节点间的最大距离(c++代码完整实现)
- 用C++实现二叉树(转)
- 数据结构复习:几种排序算法的C++实现和二叉树的相关算法实现
- C++对二叉树的简单实现。