编程基础 - 线索二叉树 (Threaded Binary Tree)
本文将介绍线索二叉树的基础知识,并用C++实现它。
在查看本文之前,需要一些数据结构和程序语言的基础,要对“树”和“二叉树 (Binary Tree)”的概念有一些了解。
其中的方法还需要熟悉“栈(stack)”、“队列(queue)”和“递归”。
1 线索二叉树简述 (Introduction)
线索二叉树:假设有 n 个结点的二叉树,其中存在 n + 1 个空指针,利用这些指针存放某种遍历下的前驱和后继,这样的指针叫做线索,这样形成的二叉树叫线索二叉树。
线索二叉树分为三种:
- 中序线索二叉树
- 前序线索二叉树
- 后续线索二叉树
之后的代码,我们用中序线索二叉树来举例,另外两种原理相同,不再阐述。
2 线索二叉树的结构 (Structure)
在结构中,我们左子树与右子树需要一个标签来标注存放的是左右孩子还是前驱后继。
且对于每个结点的空指针域:
- 左子树指针空:存放前驱指针
- 右子树指针空:存放后继指针
// Author: https://blog.youkuaiyun.com/DarkRabbit
// Threaded Binary Tree
// 线索标签,标记指针存放的是左右孩子还是前驱后继
enum ThreadTag
{
LeftOrRightChild = 0, // 左孩子或右孩子
PredecessorOrSuccessor = 1 // 前驱或后继
};
// 线索二叉树节点
template<typename T>
class ThreadNode
{
public:
T element; // 数据
ThreadTag leftTag; // 左标签
ThreadTag rightTag; // 右标签
ThreadNode<T>* leftChild; // 左指针
ThreadNode<T>* rightChild; // 右指针
ThreadNode(const T& e)
{
element = e;
leftTag = ThreadTag::LeftOrRightChild;
rightTag = ThreadTag::LeftOrRightChild;
leftChild = 0;
rightChild = 0;
}
~ThreadNode()
{
leftChild = 0;
rightChild = 0;
}
};
3 为结点加入线索 (Add Thread)
初始化二叉树不再介绍,这里我们为每个结点加入线索。
由于我们需要保存前驱和后继,所以需要一个变量predNode
来存储上一个结点。
而加入线索,我们需要对所有节点遍历。即,只是将二叉树的中序遍历中将访问节点(Visit)换成了加入线索。
对于每个结点的空指针域:
- 左子树指针空:存放前驱指针
- 右子树指针空:存放后继指针
C++代码:
-
递归方式:
// Author: https://blog.youkuaiyun.com/DarkRabbit // Threaded Binary Tree // 递归初始化中序遍历线索 template<typename T> void InitializeInorderThread(ThreadNode<T>* node, ThreadNode<T>*& predNode) { if (node != 0) { // 左标识 if (node->leftChild == 0) { node->leftTag = ThreadTag::PredecessorOrSuccessor; } else { node->leftTag = ThreadTag::LeftOrRightChild; } // 右标识 if (node->rightChild == 0) { node->rightTag = ThreadTag::PredecessorOrSuccessor; } else { node->rightTag = ThreadTag::LeftOrRightChild; } InitializeInorderThread(node->leftChild, predNode); // 左子树(L) // 左子树为空,则NULL或前驱 if (node->leftTag == ThreadTag::PredecessorOrSuccessor) { node->leftChild = predNode; } // 如果前驱存在,且右子树为空,则NULL或后继 if (predNode != 0 && predNode->rightTag == ThreadTag::PredecessorOrSuccessor) { predNode->rightChild = node; } predNode = node; InitializeInorderThread(node->rightChild, predNode); // 右子树(R) } }
-
非递归方式:
// Author: https://blog.youkuaiyun.com/DarkRabbit // Threaded Binary Tree // 初始化中序遍历线索 template<typename T> void InitializeInorderThread(ThreadNode<T>* root) { if (root == 0) { return; } std::stack<ThreadNode<T>*> nodeStack; ThreadNode<T>* predNode = 0; ThreadNode<T>* loopNode = root; do { while (loopNode != 0) // 左子树(L) { if (loopNode->leftChild == 0) { loopNode->leftTag = ThreadTag::PredecessorOrSuccessor; } else { loopNode->leftTag = ThreadTag::LeftOrRightChild; } if (loopNode->rightChild == 0) { loopNode->rightTag = ThreadTag::PredecessorOrSuccessor; } else { loopNode->rightTag = ThreadTag::LeftOrRightChild; } nodeStack.push(loopNode); loopNode = loopNode->leftChild; } if (!nodeStack.empty()) { loopNode = nodeStack.top(); nodeStack.pop(); // 左子树为空,则NULL或前驱 if (loopNode->leftTag == ThreadTag::PredecessorOrSuccessor) { loopNode->leftChild = predNode; } // 如果前驱存在,且右子树为空,则NULL或后继 if (predNode != 0 && predNode->rightTag == ThreadTag::PredecessorOrSuccessor) { predNode->rightChild = loopNode; } predNode = loopNode; loopNode = loopNode->rightChild; // 右子树(R) } } while (loopNode != 0 || !nodeStack.empty()); }
4 线索二叉树的遍历 (Traversal)
由于我们多了线索,所以遍历可以更轻松了,只需要根据线索逐个进行即可。
以下以中序遍历为例。
4.1 获取首尾结点 (First and Last Node)
// Author: https://blog.youkuaiyun.com/DarkRabbit
// Threaded Binary Tree
// 中序遍历第一个结点
template<typename T>
ThreadNode<T>* ThreadTreeInorderFirstNode(ThreadNode<T>* root)
{
if (root == 0)
{
return 0;
}
ThreadNode<T>* first = root;
while (first->leftTag == ThreadTag::LeftOrRightChild)
{
first = first->leftChild;
}
return first;
}
// Author: https://blog.youkuaiyun.com/DarkRabbit
// Threaded Binary Tree
// 中序遍历最后一个结点
template<typename T>
ThreadNode<T>* ThreadTreeInorderLastNode(ThreadNode<T>* root)
{
if (root == 0)
{
return 0;
}
ThreadNode<T>* last = root;
while (last->rightTag == ThreadTag::LeftOrRightChild)
{
last = last->rightChild;
}
return last;
}
4.2 获取后继前驱结点 (Successor and Predecessor Node)
// Author: https://blog.youkuaiyun.com/DarkRabbit
// Threaded Binary Tree
// 中序遍历后继结点
template<typename T>
ThreadNode<T>* ThreadTreeInorderSuccessor(ThreadNode<T>* node)
{
if (node == 0)
{
return 0;
}
if (node->rightTag == ThreadTag::LeftOrRightChild) // 如果有右子树
{
return ThreadTreeInorderFirstNode(node->rightChild); // 返回右子树中序第一个结点
}
else // 如果没有右子树,找线索
{
return node->rightChild; // 后继
}
}
// Author: https://blog.youkuaiyun.com/DarkRabbit
// Threaded Binary Tree
// 中序遍历前驱结点
template<typename T>
ThreadNode<T>* ThreadTreeInorderPredecessor(ThreadNode<T>* node)
{
if (node == 0)
{
return 0;
}
if (node->leftTag == ThreadTag::LeftOrRightChild) // 如果有左子树
{
// 返回左子树中序最后一个结点(最后一个没有右子树的结点)
return ThreadTreeInorderLastNode(node->leftChild);
}
else // 如果没有左子树,找线索
{
return node->leftChild; // 前驱
}
}
4.3 中序遍历 (Inorder Traversal)
// Author: https://blog.youkuaiyun.com/DarkRabbit
// Threaded Binary Tree
// 中序遍历
template<typename T>
void ThreadTreeInorderTraversal(ThreadNode<T>* root, void(*pVisit)(ThreadNode<T>&))
{
for (ThreadNode<T>* node = ThreadTreeInorderFirstNode(root);
node != 0;
node = ThreadTreeInorderSuccessor(node))
{
(*pVisit)(*node);
}
}
// 中序反向遍历
template<typename T>
void ThreadTreeInorderTraversalReverse(ThreadNode<T>* root, void(*pVisit)(ThreadNode<T>&))
{
for (ThreadNode<T>* node = ThreadTreeInorderLastNode(root);
node != 0;
node = ThreadTreeInorderPredecessor(node))
{
(*pVisit)(*node);
}
}
5 主函数与测试 (Main Method and Testing)
测试的线索二叉树为:
5.1 主函数 (Main Method)
// Author: https://blog.youkuaiyun.com/DarkRabbit
// Threaded Binary Tree
#include "threaded_binary_tree.h"
#include <iostream>
using namespace std;
using namespace BinaryTrees;
typedef ThreadNode<int>* ThreadedBinaryTree;
// 打印访问结点
void PrintVisitedElement(ThreadNode<int>& node)
{
cout << (char)(node.element);
}
int main()
{
ThreadedBinaryTree tree = new ThreadNode<int>('A');
tree->leftChild = new ThreadNode<int>('B');
tree->rightChild = new ThreadNode<int>('C');
tree->leftChild->rightChild = new ThreadNode<int>('D');
tree->rightChild->leftChild = new ThreadNode<int>('E');
InitializeInorderThread(tree); // 非递归
//ThreadNode<int>* pred = 0;
//InitializeInorderThread(tree, pred); // 递归
ThreadTreeInorderTraversal(tree, PrintVisitedElement);
cout << endl;
ThreadTreeInorderTraversalReverse(tree, PrintVisitedElement);
cout << endl;
delete tree->rightChild->leftChild;
delete tree->leftChild->rightChild;
delete tree->rightChild;
delete tree->leftChild;
delete tree;
//system("pause"); VC++
return 0;
}
5.2 打印结果 (Print Output)
BDAEC
CEADB
请按任意键继续. . .