枚举
枚举数据类型是一种由程序员定义的数据类型,其合法值是与它们关联的一组命名整数常量。
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 };
- 结点类和线索二叉树类
//节点类 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); //普通遍历 };
- (前序)单序构造
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); }
- 中序线索化 ,遍历第一个元素的前驱和最后一个元素的后继是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);
}