二叉树数据结构 用户登录系统的模拟

目录

一、实验题目和要求

二、运行环境

三、设计思路

四、调试分析

五、测试结果分析


一、实验题目和要求

【问题描述】

        在登录服务器系统时,都需要验证用户名和密码,如 telnet 远程登录服务 器。 用户输入用户名和密码后,服务器程序会首先验证用户信息的合法性。由于用户信息 的验证 频率很高,系统有必要有效地组织这些用户信息,从而快速查找和验证用户。另 外,系统也会经常会添加新用户、删除老用户和更新用户密码等操作,因此,系统必须采 用动态结构,在添加、删除或更新后,依然能保证验证过程的快速。请采用相应的数据结 构模拟用户登录系统,其功能要求包括用户登录、用户密码更新、用户添加和用户删除 等。

【基本要求】

  1. 要求自己编程实现二叉树结构及其相关功能,以存储用户信息,不允许使用标准模 板类 的二叉树结构和函数。同时要求根据二叉树的变化情况,进行相应的平衡操 作,即 AVL 平衡树操作,四种平衡操作都必须考虑。测试时,各种情况都需要测 试,并附上测试截图;

  2. 要求采用类的设计思路,不允许出现类以外的函数定义,但允许友元函数。主函数 中只 能出现类的成员函数的调用,不允许出现对其它函数的调用。

  3. 要求采用多文件方式:.h 文件存储类的声明,.cpp 文件存储类的实现,主函数 main 存 储在另外一个单独的 cpp 文件中。如果采用类模板,则类的声明和实现都放在.h 文件中。

  4. 不强制要求采用类模板,也不要求采用可视化窗又;要求源程序中有相应注释;

  5. 要求测试例子要比较详尽,各种极限情况也要考虑到,测试的输出信息要详细易

    懂,表明各个功能的执行正确;

二、运行环境

在 Xcode 平台下开发,

操作系统:MacOS 10.15.4

处理器:Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz 2.50GHz

内存:8.00GB

系统类型:64 位操作系统

三、设计思路

3.1 系统总体设计

3.1.1 数据结构

用户登录系统需要频繁的查找,删除,添加用户,所以采用二叉查找树数据结构(每 个节点包含数据部分和左右指针,数据为账号和密码,左右指针指向左右子节点,左子节 点的数据排序在本节点之前,右子节点排序在后)。本实验的二叉查找树用来存放用户的 账号密码信息,排序顺序为账号字典序,区分大小写。二叉查找树利用类 BST 实现,BST 类在 3.2.2 中详细介绍。

3.1.2 系统的技术思路

因为用户对账号密码的操作也比较复杂,所以除了 BST 类定义二叉查找树数据结构, 新建 UserInfo 类来实现对账号密码的修改,比较,输入等功能。另外,系统的功能基本上 都由类的成员函数实现,在主函数中只显示交互界面,并由用户的选择来调用相关函数, 从而实现功能。

3.2 系统功能设计 

3.2.1 类的设计

系统涉及对象有两个基本类,UserInfo 类和 BST 类。首先介绍 UserInfo 类。

·UserInfo 类:

class UserInfo{ public:

UserInfo(string Id="Null",string pw="Null"); ~UserInfo(){};
string get_id(){return id;}
string get_password(){return password;}

void input_id();//输入账号
void input_password();//输入密码
void setting_password();//设置密码
//重载运算符
UserInfo &operator=(UserInfo &c);//赋值运算符重载
bool operator==(const UserInfo &c1)const;
bool operator<(const UserInfo &c1)const;
friend ostream&operator<<(ostream &output,UserInfo &c1);

private: string id;

string password; };

UserInfo 类的设计和函数功能比较简单,类中存储用户账号密码信息,以及对涉及对 账号密码的一些操作,部分函数功能如下表所示。

表 1 UserInfo 类的部分函数解释

函数功能

string get_id(){return id;}

返回账号

string get_password(){return password;}

返回密码
void setting_password();

设置密码,用正则表达式规范密码

bool operator==(const UserInfo &c1)const;

比较是否是同一个账户

bool operator<(const UserInfo &c1)const;

比较账号密码的顺序,如果账号一样就 按照密码顺序排序,本程序中未使用该 函数,因为本程序不允许相同的账号。

friend ostream&operator<<(ostream &output,UserInfo &c1);

重载<<,允许账号密码信息可以直接用 cout 输出

//·BST 类

template<typename DataType> class BST{
private:

//定义节点类 class BinNode{ public:

//

UserInfo Data; //存储账号和密码
DataType data; //存储该账号的内容,本实验以日记为例
BinNode *left; //指向左子节点
BinNode *right; //指向右子节点
short int balanceFactor;//平衡因子
BinNode(DataType a):balanceFactor(0),data(a),left(0),right(0){};
BinNode(string Id="Null",string pw="Null"):balanceFactor(0),Data(Id,pw),left(0),right(0){};

~BinNode(){if(left)delete left;if(right)delete right;}

};
short int Is_balance; //判断二叉树是否平衡,此处10为为平衡,否则平衡 typedef BinNode* BinNodePointer;
void search2(const string &Id,bool &found,BinNodePointer &locptr,BinNodePointer

&parent)const;
void inorderAux(BinNodePointer subtreePtr)const;//实现中序
void graphAux(int indent,BinNodePointer subtreeRoot)const;//实现按图型的方式输出 BinNodePointer myRoot;//根结点

public:
//账号登录系统的功能函数
BST();
bool empty()const;//判空
bool search(const string &Id)const;//查找函数,账号存在就返回true,不存在就返回false void insert(const string &id,const string &pw);//插入一个新的账号到二叉树

void remove();
void inorder()const; void graph()const;

//利用账号,删除一个用户 //中序输出账号密码

//按图输出账号密码

//二叉树需要具备的某些功能
int leafCount(BinNodePointer root);//计算叶节点
int height(BinNodePointer root);//计算高度
void exchange(BinNodePointer root);//交换左右子树,本实验中可以使账号从大到小输出 void levelBylevel();//按层输出
BinNodePointer getMyRoot(){return myRoot;}//获取根节点
~BST(); //析构函数
BST(const BST &c);//拷贝构造函数
BST &operator=(BST &c);//赋值运算符重载
void VLR(BinNodePointer subtreePtr)const; //前序
void LRV(BinNodePointer subtreePtr)const; //后序
int foundLevel(const string &id)const; //层查找
void release(BinNodePointer root); //析构利用该函数递归实现

void copy(BinNodePointer &myR,BinNodePointer &root); //复制和赋值都用该函数递归实现

//为了实现系统功能函数的函数
void sign();//登录
void add();//注册
void read_from_file();//从文件中读取用户信息

void display_all_Username(BinNodePointer subtreePtr)const;//显示树中所有用户名

void delete_from_file(BinNodePointer subtreePtr,fstream &del);//删除文件中的数据

BinNodePointer ll_rotate(BinNodePointer y);//左旋
BinNodePointer rr_rotate(BinNodePointer y);//右旋
BinNodePointer lr_rotate(BinNodePointer y);//先左旋后右旋
BinNodePointer rl_rotate(BinNodePointer y);//先右旋后左旋
void calculate_balanceFactor(BinNodePointer &x,BinNodePointer y);//计算节点的平衡因子 void To_Balance();//平衡二叉树,插入删除都适用

BinNodePointer get_parent(BinNodePointer root,BinNodePointer p);//寻找p的父节点

// int getBalance(BinNodePointer &y);

};

首先,在 BST 类中定义私有成员节点类 BinNode,BinNode 用来储存账号密码信息,以及平衡因子,设置两个指针 left,right 分别指向节点的左子节点和右子节点。BST 类中 其它私有成员和私有成员函数(私有部分实现功能,在公有部分被调用)功能如下表 2。

表 2 BST 类的部分私有成员解释

成员功能

short int Is_balance;

判断二叉树是否平衡,函数在公有成员函 数 calculate_balanceFactor()中被调用,10 (只是随便取的一个数,便于判断)为平

衡,否则未平衡。

void search2(const string &Id,bool &found,BinNodePointer &locptr,BinNodePointer &parent)const;

寻找 Id。如果找到,found=true,并使 locptr 指向 Id 所在的节点,parent 指向其

父节点。否则 found=flase。

void inorderAux(BinNodePointer subtreePtr)const;

中序输出的递归函数。首先一直向左递 归,输出根节点后,向右递归。

void graphAux(int indent,BinNodePointer subtreeRoot)const;

横向输出用户名,左子节点在根节点下 方,右子节点在上方,使用递归,层层输

出,使二叉树结构在视觉上更加直观。

BinNodePointer myRoot;

指向根节点

然后这里简单介绍一些递归(两个与二叉树层相关的函数除外)函数,这些函数在本程序中可能没有起到很大的作用,但是可以作为二叉树一些比较好用的功能,为以后的程 序改进做一些铺垫,函数对应的功能如表 3 所示。二叉树遍历的递归方式类似,不重复介绍。

表 3 BST 类中关于二叉树的一些基本函数

函数功能

int leafCount(BinNodePointer root);

计算叶节点的数量。

int height(BinNodePointer root);

计算二叉树高度,该函数在本程序的平衡二叉树部分 起到了很大的作用。

void exchange(BinNodePointer root);

交换左右子节点,类似于使整个二叉树对称变换/

void levelBylevel();

非递归,但是用到了指针队列来实现功能。因为队列 不是程序的主要数据结构,所以没有新建队列类,而 是直接采用了 STL 库里面的模板。首先将新建一个 指针指向 myRoot,并使 root 进队,在队列不为空的 情况下,输出账户,然后将队列最前面节点的左右子 节点都推进队列,然后删除该节点,一直循环,。就

可以按层输出二叉树中的账号密码信息。

void VLR(BinNodePointer subtreePtr)const;

前序输出账号密码信息

void LRV(BinNodePointer subtreePtr)const;

后序输出账号密码信息

int foundLevel(const string &id)const;

查找账号 id 所在的层数

void release(BinNodePointer root);

析构函数利用该函数递归实现

void copy(BinNodePointer &myR,BinNodePointer &root);

复制和赋值利用该函数递归实现

下面重点介绍系统中的几个核心函数
·void insert(const string &id,const string &pw); //插入函数 Ps:locptr 指针指向的当前节点

 

图 1 insert 函数的实现

插入函数,在原二叉树中没有需要插入的账号时,找出需要新建节点的位置,然后将 新建的节点插入,插入后重新平衡二叉树,平衡函数在后面介绍。原二叉树中有相同的账 号时,则插入失败。

函数时间复杂度:O(log2 n)

·void BST<DataType>::remove() //删除节点函数

 

图 2 remove 函数的实现

删除时输入的 id 就是就是需要删除的节点的 id,这里平衡二叉树用的函数和上述 insert 为同一个。因为在需要删除的节点左右子树都有内容时,直接删除根节点的操作实 现比较困难,所以采用寻找该节点的直接后继与它交换后删除直接后继所在的节点的方式 (因为直接后继一定没有左子节点),如流程图所示。

函数时间复杂度:O(log2 n)

·void BST<DataType>::To_Balance() //平衡二叉树

​​​​​​​

图 3 To_Balance 函数的实现

补充说明:

1. root 指向二叉树中第一个找到的未平衡的节点。

2. 判断二叉树是否平衡用 calculate_balanceFactor(BinNodePointer &x, BinNodePointer y)函 数实现,这里将函数新建的 root 传给 x,函数从最顶层开始查找,找到未平衡节点后传给 root。

3. 该函数功能复杂,调用了几个其他成员函数。在表 4 列出这几个函数。

 表 4 To_Balance 所用到的函数

函数功能

void calculate_balanceFactor(BinNodePointer &x,BinNodePointer y);

利用 height 计算平衡因子,找到第一个不平衡 节点将该节点的地址传给 x。因为本实验中并 不需要每一个节点的平衡因子,所以为了节省 函数执行时间,找到不平衡后,赋完值后就直 接退出循环。调用 height 函数也需要时间,所 以该函数结束后直接在 To_Balance 中判断是

哪一种不平衡。

BinNodePointer get_parent(BinNodePointer root,BinNodePointer p);

寻找 p 的父节点,是为了将旋转至平衡的子树 接回原来的二叉树。该函数依旧递归实现,找 到父节点后退出。如果没有父节点,说明传入

的节点是最顶层根节点。

BinNodePointer ll_rotate(BinNodePointer y);

左旋,具体如图所示.

BinNodePointer rr_rotate(BinNodePointer y);

右旋,与左旋同理

BinNodePointer lr_rotate(BinNodePointer y);

先左旋后右旋

BinNodePointer rl_rotate(BinNodePointer y);

先右旋后左旋

3.2.1 主程序的设计

表 5 主函数界面及功能实现

首先在主函数中新建一棵二叉查找树 bst,从已有的文件读取账号信息,形成一棵有内容的树。然后根据用户需要对二叉树进行操作。

四、调试分析

先不写了

五、测试结果分析

5.1 程序主界面。

可用键盘输入需要进行的操作。

5.2 登录

键盘输入1 

 5.3 高级

博客到这里暂时就结束了,欢迎一起学习,如有侵权请联系我删除。

源码放在码云,链接 https://gitee.com/fruitparty/task/tree/BST/

  • 2
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
叉树是一种非常基础和重要的数据结构,对于理解递归和非递归的遍历方式也有很大的帮助。本文将介绍二叉树的前序、中序、后序的递归和非递归遍历方式,并对它们进行比较和分析。 ### 前序遍历 前序遍历的顺序是:先遍历根节点,然后遍历左子树,最后遍历右子树。递归方式的代码如下: ```python def preorder_recursive(root): if not root: return print(root.val) preorder_recursive(root.left) preorder_recursive(root.right) ``` 非递归方式的代码如下: ```python def preorder_iterative(root): if not root: return stack = [root] while stack: node = stack.pop() print(node.val) if node.right: stack.append(node.right) if node.left: stack.append(node.left) ``` 可以看出,非递归方式使用了一个栈来模拟递归过程。首先把根节点入栈,然后每次取出栈顶元素,并打印它的值。如果有右孩子,就把右孩子入栈;如果有左孩子,就把左孩子入栈。这样,就能保证在遍历到一个节点时,它的左子树已经全部遍历完了。 ### 中序遍历 中序遍历的顺序是:先遍历左子树,然后遍历根节点,最后遍历右子树。递归方式的代码如下: ```python def inorder_recursive(root): if not root: return inorder_recursive(root.left) print(root.val) inorder_recursive(root.right) ``` 非递归方式的代码如下: ```python def inorder_iterative(root): if not root: return stack = [] node = root while stack or node: if node: stack.append(node) node = node.left else: node = stack.pop() print(node.val) node = node.right ``` 可以看出,非递归方式同样使用了一个栈来模拟递归过程。首先把根节点入栈,并把指针指向左子树的最底层。然后每次取出栈顶元素,并打印它的值。如果有右孩子,就把右孩子入栈;如果没有右孩子,就返回到它的父节点。 ### 后序遍历 后序遍历的顺序是:先遍历左子树,然后遍历右子树,最后遍历根节点。递归方式的代码如下: ```python def postorder_recursive(root): if not root: return postorder_recursive(root.left) postorder_recursive(root.right) print(root.val) ``` 非递归方式的代码如下: ```python def postorder_iterative(root): if not root: return stack = [root] res = [] while stack: node = stack.pop() res.append(node.val) if node.left: stack.append(node.left) if node.right: stack.append(node.right) return res[::-1] ``` 可以看出,非递归方式同样使用了一个栈来模拟递归过程。首先把根节点入栈,在遍历完左子树和右子树之后,把它的值加入到结果列表中。最后,把结果列表翻转,就得到了后序遍历的结果。 ### 比较和分析 递归和非递归遍历方式的时间复杂度都是 O(n),空间复杂度也都是 O(n)。但是,非递归方式需要使用一个栈来模拟递归过程,所以空间复杂度比递归方式要高。而且,非递归方式的代码比较难理解和实现,需要仔细分析。 另外,对于前序和后序遍历,非递归方式的代码比递归方式要复杂一些,需要添加一些特判来保证遍历顺序的正确性。而中序遍历的非递归方式比较简单,只需要一个栈和一个指针即可。 总的来说,递归方式比较简洁和易于理解,但是可能会因为递归深度过大而导致栈溢出。非递归方式虽然可以避免这个问题,但是代码比较复杂,需要一定的实现技巧和思维难度。所以,在实际应用中,根据具体情况选择合适的遍历方式比较重要。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值