当我们建立二叉树的时候,会有很多多出来的空指针没有利用好,这就不符合我们勤俭节约的好习惯了
例如:
其中∧表示的就是空指针
我们可以对其进行线索化,从而达到充分利用好空指针
首先,
我们可以使左非空指针指向它的左孩子,右非空指针指向它的右孩子
我们可以使左空指针指向它的前驱,右空指针指向它的后继
经过某种遍历后会发现,二叉树就会变成一个双向链表
其中:虚线箭头指向前驱,实线箭头指向后继
那么问题来了,我们如何让计算机知道左指针是指向左孩子,还是指向前驱呢?
我们可以在结点下手:
struct Node {
Eletype data;
Node *lchild;
Node *rchild;
bool LTag = 0;
bool RTag = 0;
};
LTag = 0,lchild 指向左孩子;LTag = 1,lchild 指向前驱
RTag = 0,rchild 指向右孩子;RTag = 1,rchild 指向后继
既然是双向链表,我们就给链表加一个头结点,方便后面遍历。
并且令头结点的左指针指向树根,右指针指向中序遍历的最后一个结点,
同时,第一个结点的左指针指向头结点,最后一个结点的右指针也指向头结点
建立二叉树: 输入(ABDH##I##EJ###CF##G##)
void CreateBiTree(Node<T>*q) {
T data;
cin >> data;
if (data == '#') { //自己找个符号当作 虚结点
q->data = '#'; //标志为 空结点
}
else {
q->data = data;
q->lchild = new Node<T>;
q->rchild = new Node<T>;
CreateBiTree(q->lchild);
CreateBiTree(q->rchild);
}
}
二叉树线索化:
//Pre为全局变量,并且始终指向当前结点,
初始化的时候Pre设置为head,那么第一个结点的前驱自然就会指向head
void InThreading(Node<T> *p) {
if (p->data!='#') {
//-------------------------------------------
InThreading(p->lchild);
//----------------------------------------------
if (p->lchild->data=='#') { // 如果左指针为空,那么就要利用好这空指针,让它指向前驱
p->LTag = Thread; //标志存在前驱
p->lchild = Pre; // 当前结点的前驱结点为上一个结点
}
if (Pre->rchild->data=='#') { //如果上一个结点的右指针为空,那么就要利用好这空指针,让它指向后继,即指向当前结点
Pre->RTag = Thread;//标志存在后继
Pre->rchild = p; // 则 前驱结点的后继就是当前结点
}
Pre = p; //Pre 指向当前结点
//-----------------------------------------------
InThreading(p->rchild);
//-------------------------------------------
}
}
接下来,让头结点和最后一个结点相互指向(线段②④)
注意:线索化后,Pre已经指向最后一个结点
void HeadToTail() {
head->rchild = Pre; // 头结点 的 右孩子 指向最后一个结点
head->rchild->RTag = Thread;//标记 最后一个结点存在后继结点
head->rchild->rchild = head;//最后一个结点的后继指向头结点
}
完整代码:
#include<iostream>
using namespace std;
#define Link 0
#define Thread 1
template<typename T>
struct Node {
T data;
Node *lchild;
Node *rchild;
bool LTag = 0;
bool RTag = 0;
};
//线索二叉树
template<class T>
class Tree {
private:
Node<T>*head;
Node<T>*root;
Node<T>*Pre; //始终指向刚刚访问的结点
public:
//初始化头结点和树根
Tree() {
head = new Node<T>;
root = new Node<T>;
head->lchild = root;//头结点的左孩子 指向 树根
head->rchild = root;
Pre = head;//初始化的时候指向头结点
}
//前序遍历建立二叉树
void CreateBiTree(Node<T>*q) {
T data;
cin >> data;
if (data == '#') { //自己找个符号当作 虚结点
q->data = '#'; //标志为 空结点
}
else {
q->data = data;
q->lchild = new Node<T>;
q->rchild = new Node<T>;
CreateBiTree(q->lchild);
CreateBiTree(q->rchild);
}
}
//采用 中序遍历 进行 中序线索化
void InThreading(Node<T> *p) {
if (p->data!='#') {
//-------------------------------------------
InThreading(p->lchild);
//----------------------------------------------
if (p->lchild->data=='#') { // 如果左指针为空,那么就要利用好这空指针,让它指向前驱
p->LTag = Thread; //标志存在前驱
p->lchild = Pre; // 当前结点的前驱结点为上一个结点
}
if (Pre->rchild->data=='#') { //如果上一个结点的右指针为空,那么就要利用好这空指针,让它指向后继,即指向当前结点
Pre->RTag = Thread;//标志存在后继
Pre->rchild = p; // 则 前驱结点的后继就是当前结点
}
Pre = p; //Pre 指向当前结点
//-----------------------------------------------
InThreading(p->rchild);
//-------------------------------------------
}
}
//把 最后一个结点 和 头结点 连接
void HeadToTail() {
head->rchild = Pre; // 头结点 的 右孩子 指向最后一个结点
head->rchild->RTag = Thread;//标记 最后一个结点存在后继结点
head->rchild->rchild = head;//最后一个结点的后继指向头结点
}
//根据线索进行遍历
void InOrderTraverse_Thr(Node<T> *H) {
Node<T>*p;
p = H->lchild; //指向树根
while (p != H) { //如果 当前结点 不是 头结点
while (p->LTag == Link)p = p->lchild; //LTag == Thread 的时候 p指向中序的第一个结点
cout << p->data;
//存在后继的时候循环,直到结点不存在后继
while (p->RTag == Thread && p->rchild != H) { //RTag == Thread 即存在后继,且后继不为头结点
p = p->rchild; // 当前结点p指向后继
cout << p->data;
}
p = p->rchild;//p指向右孩子
}
}
Node<T> *GetRootaddr() { return root; }
Node<T> *GetHeadaddr() { return head; }
};
int main() {
Tree<char> T;
T.CreateBiTree(T.GetRootaddr());
T.InThreading(T.GetRootaddr());
T.HeadToTail();
T.InOrderTraverse_Thr(T.GetHeadaddr());
return 0;
}
运行结果: