数据结构 | C++ |线索二叉树

 

枚举

枚举数据类型是一种由程序员定义的数据类型,其合法值是与它们关联的一组命名整数常量。

0.关键词:enum(小写)

1.实例:

enum BiTHrNodeType { LINK,THREAD };

创建了一个名为BiTHrNodeType的数据类型.

默认情况下,第一个枚举量LINK是0,第二个枚举量THREAD是1,依此类推.

 

2.创建变量

BiTHrNodeType b1 = LINK;

创建一个变量b1,赋值为LINK.

3.LINK枚举量虽然是0,但是不能赋值,只能比较

b1 = 1;//错误
if (b1 == 0);//正确
if( b1 < b2 );//正确

4.除了默认情况,可以在定义枚举类型时自己赋值

enum Department { factory = 1, sales = 2, warehouse = 4 };

如果在赋值时省略了一个或多个符号,则它们将被赋给一个默认值:
 

enum Colors { red, orange, yellow = 9, green, blue };

在该示例中,命名常量 red 将被赋值为 0,orange 将为 1,yellow 将为 9,green 将为 10,blue 将为 11。

5.输出

enum BiTHrNodeType { LINK,THREAD };
BiTHrNodeType b1 = LINK;
cout << b1;//输出值为0,而不是LINK

线索二叉树

    0.枚举类型和全局变量

enum BiTHrNodeType { LINK,THREAD };

  1. 结点类和线索二叉树类
    //节点类
    struct BiThrNode
    {
    	BiTHrNodeType ltype, rtype;//两个数据成员是合法值为0或1的BiThrNode类型
    	int data;
    	BiThrNode *Lchild, *Rchild;
    };
    
    
    //线索二叉树类
    class BiThr
    {
    public:
    	BiThr() { root = nullptr; }										//无参构造函数
    	BiThr(vector<int> &pre);										//前序构造函数
    	void InThreaded();												 	//中序线索化 
    	~BiThr();																 //析构函数
    	BiThrNode *GetNext(BiThrNode *p);						 //求中序遍历的后继结点
    	BiThrNode *GetPrev(BiThrNode *p);						 //求中序遍历的前驱结点
    	void Travese();														 //线索遍历
    	void DeTravese();													 //逆序线索遍历
    	void bl(); 
    	void SearchByValue();											//按值搜索
    	void InsertNode(int n);											//添加结点
    	void DeleteNode();											//删除结点
    	BiThrNode *GetParent(BiThrNode *p);					 //求父节点地址
    private:
    	BiThrNode *root;
    	BiThrNode *CreatByPre(vector<int> &pre, int &i);	//前序构造
    	void InThreaded(BiThrNode *&p);							//中序线索化
    	void Free(BiThrNode *p);									    //析构函数   
    	void bl(BiThrNode *p);											//普通遍历
    };

     

  2.  (前序)单序构造
    BiThrNode *BiThr::CreatByPre(vector<int> &pre, int &i)
    {
    	int e = pre[i]; i++;
    	if (e == 0) { return NULL; }//0表示无子树
    	BiThrNode *p = new BiThrNode;
    
    	p->data = e;
    	p->ltype = LINK;
    	p->rtype = LINK; 
    	p->Lchild = CreatByPre(pre, i);
    	p->Rchild = CreatByPre(pre, i);
    
    	return p;
    }
    BiThr::BiThr(vector<int> &pre)
    {
    	int i = 0;
    	root = CreatByPre(pre, i);
    }
    

     

  3. 中序线索化  ,遍历第一个元素的前驱和最后一个元素的后继是nullptr
    //*pre是全局变量,而非线索化函数void InThreaded(BiThrNode *p)的局部变量
    //否则每次递归会重新赋值为nullptr.导致后续的线索遍历失败.
    //最好写在函数头前面
    
    
    BiThrNode *pre = nullptr;						
    void BiThr::InThreaded(BiThrNode *&p)//中序遍历第一个结点的前驱和最后一个结点的后继nullptr
    {
    
    	if(p==nullptr)
    	{
    		return;       //说明此处无子树
    	}
    
    	//左子树
    	InThreaded(p->Lchild);
    
    	//根
    	if (p->Lchild==nullptr)		//对p操作,当左子树为空,连接其前驱结点
    	{
    		p->ltype = THREAD;
    		p->Lchild = pre;
    	
    	}
    	if (p->Rchild==nullptr)
    	{
    		p->rtype = THREAD;
    	}
    
    	if (pre&&pre->rtype == THREAD)   //对pre操作,
    	{
    			pre->Rchild = p;
    	}
    	pre = p;					
    	
    	//右子树
    	InThreaded(p->Rchild);
    
    }
    void BiThr::InThreaded()
    {
    	InThreaded(root);
    }

     

     ①*pre必须是全局变量,否则在每次递归时会重新赋值为nullptr.

          ②为什么形参列表是(BiThrNode * &p):

             形参p是一个指针,而且每次递归都会变更所指对象.

          ③为什么前驱和后继代码不对称:

             p的前驱线索指向pre,pre可以是NULL;

             pre的后继线索指向p,pre必为不空

          ④p是形参,指的是每次递归时当前的根结点(在递归中,叶节点是左右子树均NULL的根结点)

 

  4.线索化后,

①前驱结点函数

②后继结点函数

③父结点函数

//求中序遍历的后继结点
BiThrNode* BiThr::GetNext(BiThrNode *p)
{
	if (p == nullptr) { return nullptr; }
	//状态1/2:rtype==THREAD
		if(p->rtype==THREAD)
		{
			return p->Rchild;
		}
		else
		{
			//状态2/2:rtype==LINK
			p = p->Rchild;
			while (p->ltype == LINK)//条件不可以是while(p->Lchild),会循环到有前驱的结点上
			{
				p = p->Lchild;
			}
			return p;
		}
}


 //求中序遍历的前驱结点
BiThrNode* BiThr::GetPrev(BiThrNode *p)
{
	if (p == nullptr) { return nullptr; }
	if (p->ltype==THREAD)
	{
		return p->Lchild;
	}
	else
	{
		p = p->Lchild;
		while (p->rtype == LINK)
		{
			p = p->Rchild;
		}
		return p;
	}
}


//查找父节点
BiThrNode * BiThr::GetParent(BiThrNode *p)
{
	BiThrNode *parent = p;

	//p可能是parent的左子树
	while (parent->rtype==LINK)
	{
		parent = parent->Rchild;			//parent访问到p的最右下
	}
	parent = parent->Rchild;				//再访问后继结点
	if (parent&&parent->Lchild == p)  //通过if语句控制return,来达到两个return二选一的目的
	{
		return parent;
	}

	//p可能是parent的右子树
	parent = p;
	while (parent->ltype==LINK)		
	{
		parent = parent->Lchild;			//parent访问到p的最左下
	}
	parent = parent->Lchild;				//再访问前驱结点
		return parent;							//不需要再写if,因为前面的if语句实现后终止代码,不会执行此处
}

5.析构函数

线索二叉树的析构函数也要与时俱进,采用线索化遍历来清理空间

//析构函数
void BiThr::Free(BiThrNode *p)
{
	BiThrNode *q = p;//q用来清理空间,p用来指向下一个
	while (p)
	{
		p = GetNext(p);
		delete q;
		q = p;
	}

}
BiThr::~BiThr()
{
	BiThrNode *p = root;
	while (p->ltype == LINK)		//先移动到中序的第一个结点,注意while(p->Lchild)和while(p->ltype==LINK)并不等价
	{
		p = p->Lchild;
	}
	Free(p);
}

 

  6.线索遍历

//线索遍历(中序)
void BiThr::Travese()
{
	BiThrNode *p = root;
	while (p->ltype==LINK)		//先移动到中序的第一个结点,注意while(p->Lchild)和while(p->ltype==LINK)并不等价
	{
		p = p->Lchild;
	}
	while (p!=nullptr)
	{
		cout << p->data << "	";
		p = GetNext(p);
	}
}

//线索遍历(逆中序)
void BiThr::DeTravese()
{
	BiThrNode *p = root;
	while (p->rtype == LINK)		//先移动到中序的最后一个结点,注意while(p->Rchild)和while(p->rtype==LINK)并不等价
	{
		p = p->Rchild;
	}
	while (p != nullptr)
	{
		cout << p->data << "	";
		p = GetPrev(p);
	}
}

7.按值搜索(返回值)

第二版改写成按值返回指针,其他函数可重复调用

//按值返回值
void BiThr::SearchByValue()
{
	int e;
	p1:	cout << "输入要查找的值:\n";
    cin >> e;
	if (e == 0)
	{
		cout << "值不合法,请重新输入!"; goto p1;
	}


	BiThrNode *p = root;
	while (p->ltype == LINK)		
	{
		p = p->Lchild;
	}
	while (p)
	{
		if (e!=p->data )
		{
			p = GetNext(p);
		}
		else
		{
			cout << "存在该值:" << p->data << endl;
			return;
		}

	}

	if (p == nullptr)
	{
		cout << "该树中不存在该值" << endl;
		return;
	}
}

 

8.添加结点

还是比较赘余,第二版提炼出

①返回中序第一个结点的函数

②按值返回指针的函数,让r工作指针指向它

//添加节点
/*	1.工作指针p搜索所有非满结点
	2.cin>>x选择结点,工作指针r跳动到该结点.
	3工作指针q创建新结点存入新值n,
	4.把q链入r的左子树或右子树,并线索化
*/
void BiThr::InsertNode(int n)
{
	//part.1
	BiThrNode *p = root;
	while (p->ltype == LINK)		//p移动到中序的第一个结点,
	{
		p = p->Lchild;
	}
	cout << "所有可插入的结点\n";
	while (p)  //打印所有非满的二叉结点
	{
		if (p->ltype == THREAD || p->rtype == THREAD)
		{
			cout << p->data << "		";		//此处可把值输入一个vector容器,用在part.2中判断选择的值是否合法
		}
		p = GetNext(p);
	}
	cout << endl;

	//part.2
	cout << "请在以上结果中选择一个插入新值\n";
	int x;
	cin >> x;


	BiThrNode *r = root;    
	while (r->ltype == LINK)

	{
		r = r->Lchild;
	}
	while (r->data != x)
	{
		r = GetNext(r);
	}//此时r已指向选定的结点

	//part.3
	BiThrNode *q = new BiThrNode;
	q->data = n;
	q->ltype = THREAD;//链入的结点必为叶结点,两个指针域必指向前驱与后继结点,两个标记域必不为LINK
	q->rtype = THREAD;
	q->Lchild = nullptr;
	q->Rchild = nullptr;

	//part.4
	if (r->ltype == THREAD)//优先链入Lchild
	{
		q->Lchild = r->Lchild;		//r的前驱结点变成q的前驱结点
		r->Lchild = q;					//q是r的左孩子
		r->ltype = LINK;				//连接后修改标记域
		q->Rchild = r;					//r是q的后继结点 
	}
	else								       //左子树已连接,再考虑链入右子树
	{
		q->Rchild = r->Rchild;		//r的后继结点变成q的后继结点
		r->Rchild = q;					//q是r的右孩子
		r->rtype = LINK;				//连接后修改标记域
		q->Lchild = r;					//r是q的前驱结点 
	}
}

9.删除结点

※①先修改标记域,后重制删除后的线索关系.否则会导致指针权限冲突

分析:

判断r是左右孩子,

status1/2:r是左孩子(r后继结点的左孩子是r)

  • r的后继节点的新前驱是r的旧前驱
  • 该前驱结点已经有:rtype==LINK,所以没必要写:r->前驱->新后继=r->后继.因为不需要考虑r->前驱->后继,所以r的前驱是nullptr还是存在都没关系,也就是说,没必要区分中序第一结点还是普通结点.
  • 由于是叶结点,r的后继结点必定是其父结点.

 

status2/2:r是右孩子(r前驱结点的右孩子是r)

  • r的前驱结点的新后继是r的旧后继
  • r的前驱结点必是父结点
//删除结点
/*
1.用p指针打印所有的叶结点
2.选定其中一个,工作指针r指向待删除的叶结点
3.重新线索化
*/
void BiThr::DeleteNode()
{
	//part.1
	BiThrNode *p = root;
	while (p->ltype == LINK)		//p移动到中序的第一个结点,
	{
		p = p->Lchild;
	}
	cout << "所有可删除的叶结点\n";
	while (p)  //打印所有可删除的叶结点
	{
		if (p->ltype == THREAD && p->rtype == THREAD)
		{
			cout << p->data << "		";
		}
		p = GetNext(p);
	}
	cout << endl;

	cout << "选择要删除的叶节点\n";
	int m; cin >> m;
	BiThrNode *r = root;
	while (r->ltype == LINK)

	{
		r = r->Lchild;
	}
	while (r->data != m)
	{
		r = GetNext(r);
	}//此时r已指向选定的结点

	//part.3
		//判断左子树:r后继结点的左孩子是r
	if (GetParent(r)->Lchild==r)		
	{
		//一定先修改标记域,后更改指针域,否则引起指针问题
		GetParent(r)->ltype = THREAD;			//r的后继节点(父节点)失去左孩子,LINK-->THREAD
		r->Rchild->Lchild = r->Lchild;
		delete r;
	}
	//判断右子树:r前驱结点的右孩子是r
	else
	{
		GetParent(r)->rtype = THREAD;
		r->Lchild->Rchild = r->Rchild;
	
		delete r;
	}
	
}

 10.main()

注意添加结点函数在main()中cin值

而删除结点函数不需要.

using namespace std;
#include <iostream>
#include<vector>
#include"标头.h"


int main()
{
	vector<int> a = {1,2,4,0,0,5,6,0,0,7,0,0,3,0,0};
	//构造
	BiThr b1(a);

	//线索化
	b1.InThreaded();
	
	//中序遍历
	cout << "中序遍历\n";
	b1.Travese(); cout << endl;

	//中序逆向遍历
	cout << "中序逆向遍历\n";
	b1.DeTravese(); cout << endl;

	//按值查找
	b1.SearchByValue();


	//插入结点
	cout << "输入要添加的值\n";
	int n; cin >> n;
	b1.InsertNode(n);
	b1.Travese();

	//删除结点
	b1.DeleteNode();
	b1.Travese();
	
	system("pause");
	return 0;
}

 


Bug总结与回顾

1.实现让指针p指向中序第一个结点

错误代码
BiThrNode *p = root;
	while (p->Lchild)		//先移动到中序的第一个结点
	{
		p = p->Lchild;
	}

	while (p->Rchild!=nullptr)
	{
		cout << p->data << "		";
		p = p->Rchild;
	}

错误原因:Rchild可能指向线索,即遍历序列的后继结点;

也可能指向右子树,中序遍历中,p的后继结点不是右子树的根节点,而是右子树的最左下结点

 

 

2.求后继结点函数的bug

①要用if..else..区分状态,否则无法划分全部情况

②while(p->Lchild)和while(p->ltype==LINK)并不等价.

前者可能移动到前驱结点上,后者确保移动到左子树

 

 

3.按值搜索时只出现1,3.其余皆显示不存在

问题出在:指针一开始指向的是root,而非中序第一个结点

 

void BiThr::SearchByValue()

{

        int e;

        p1:   cout << "输入要查找的值:\n";

    cin >> e;

        if (e == 0)

        {

                 cout << "值不合法,请重新输入!"; goto p1;

        }

 

 

        BiThrNode *p = root;//bug

        while (p->ltype == LINK)        

        {

                 p = p->Lchild;

        }

        while (p)

        {

                 if (e!=p->data )

                 {

                         p = GetNext(p);

                 }

                 else

                 {

                         cout << "存在该值:" << p->data << endl;

                         return;

                 }

 

       }

void BiThr::SearchByValue()

{

        int e;

        p1:   cout << "输入要查找的值:\n";

    cin >> e;

        if (e == 0)

        {

                 cout << "值不合法,请重新输入!"; goto p1;

        }

 

 

        BiThrNode *p = root;//bug

        while (p->ltype == LINK)   //添加while()语句后正确      

        {

                 p = p->Lchild;

        }

        while (p)

        {

                 if (e!=p->data )

                 {

                         p = GetNext(p);

                 }

                 else

                 {

                         cout << "存在该值:" << p->data << endl;

                         return;

                 }

 

        }

 

 

 

析构函数也有类似的问题

void BiThr::Free(BiThrNode *p)

{

       BiThrNode *q = p;//q用来清理空间,p用来指向下一个

       while (p)

       {

              p = GetNext(p);

              delete q;

              q = p;

       }

 

}

BiThr::~BiThr()

{

       BiThrNode *p = root;

       while (p->ltype == LINK)          

       {

              p = p->Lchild;

       }

       Free(p);

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值