数据结构课程设计——用户管理系统
涉及到的东西:MFC,file,AVL;
作为一名初学者,记录一下这个学期的数据结构的课程设计;先上任务书要求。
1.【问题描述】:
在登录服务器系统时,都需要验证用户名和密码,如 telnet 远程登录服务器。
用户输入用户名和密码后,服务器程序会首先验证用户信息的合法性。由于用户信息的验证
频率很高,系统有必要有效地组织这些用户信息,从而快速查找和验证用户。另外,系统也
会经常会添加新用户、删除老用户和更新用户密码等操作,因此,系统必须采用动态结构,
在添加、删除或更新后,依然能保证验证过程的快速。请采用相应的数据结构模拟用户登录
系统,其功能要求包括用户登录、用户密码更新、用户添加和用户删除等。
2.【基本要求】:
- 要求自己编程实现二叉树结构及其相关功能,以存储用户信息,不允许使用标准模板类
的二叉树结构和函数。同时要求根据二叉树的变化情况,进行相应的平衡操作,即 AVL
平衡树操作,四种平衡操作都必须考虑。测试时,各种情况都需要测试,并附上测试截
图; - 要求采用类的设计思路,不允许出现类以外的函数定义,但允许友元函数。主函数中只
能出现类的成员函数的调用,不允许出现对其它函数的调用。 - 要求采用多文件方式:.h 文件存储类的声明,.cpp 文件存储类的实现,主函数 main 存
储在另外一个单独的 cpp 文件中。如果采用类模板,则类的声明和实现都放在.h 文件中。 - 不强制要求采用类模板,也不要求采用可视化窗口;要求源程序中有相应注释;
- 要求测试例子要比较详尽,各种极限情况也要考虑到,测试的输出信息要详细易懂,表
明各个功能的执行正确; - 建议采用 Visual C++ 6.0 及以上版本进行调试;
简单的说就是三点1.实现平衡二叉树的操作;2.文件读写;3.登陆界面的设计;
1.平衡二叉树的实现
原理不过多赘述,直接写关键函数;关于AVL树的关键函数有更新树的高度,以及四种旋转的操作;
1.LL(左单旋)
template<typename T>
binnode<T>*AVL<T>::LL(binnode<T>*ptr) {
binnode<T>*tmp = ptr->right;
//将传进来的结点的右结点进行旋转操作;
ptr->right = tmp->left;//将其tmp的左子树挂在传ptr的右子树上
tmp->left = ptr;//将ptr做为tmp的左子树挂上去
ptr->height = getheight(ptr->left) > getheight(ptr->right) ? getheight(ptr->left)+1 : getheight(ptr->right)+1;//更新树高的函数在后面解释
tmp->height = getheight(tmp->left) > ptr->height ? getheight(tmp->left) + 1 : ptr->height + 1;
return tmp;//返回旋转之后的根结点;
}
左单旋图示:
2.RR旋转(右单旋)
template<typename T>
binnode<T>* AVL<T>::RR(binnode<T>*ptr) {
binnode<T>*tmp = ptr->left;//tmp是ptr的左子女
ptr->left = tmp->right;//将tmp的右子树挂在ptr的左子树上
tmp->right = ptr;//将ptr作为子树挂在tmp的右子树上;
ptr->height = getheight(ptr->left) > getheight(ptr->right) ? getheight(ptr->left)+1 : getheight(ptr->right)+1;//更新树高
tmp->height = getheight(tmp->left) > ptr->height ? getheight(tmp->left) + 1 : ptr->height + 1;
return tmp;//返回现在的根结点;
}
右单旋图示:
3.左右双旋(LR):麻烦结点出现在不平衡结点的左子树的右子树上;
template<typename T>
binnode<T>*AVL<T>::LR(binnode<T>*ptr) {
ptr->left = LL(ptr->left);//先对ptr的左结点进行左旋
return RR(ptr);//在对ptr进行右旋并返回;
//可以将双旋看成是两次单旋;
}
左右双旋图示:
4.右左双旋(RL):麻烦结点出现在不平衡结点的右子树的左子树上时;
template<typename T>
binnode<T>*AVL<T>::RL(binnode<T>*ptr) {
//同上可以将其分解成两次双旋的过程;
ptr->right= RR(ptr->right);//对右子女进行右旋
return LL(ptr);//对其左旋;
}
右左双旋图示:
5.插入函数:对于要插入的结点,先对AVL树进行查找,若找到了则不插入,若没找到进行插入,(注意AVL是排序树所以插入的操作与BST树插入的操作相似,只不过多了一个更新树的高度的操作);
template<typename T>
void AVL<T>::insert( binnode<T>* &ptr, const T &item) {
if (ptr==NULL)
{
ptr = new binnode<T>();
//当ptr为NULL时,证明该中没有要插入的结点,所以要进行插入操作;
ptr->data = item;
ptr->height=1;//将树的高度置为1;
ptr->left = ptr->right = NULL;//左右子树置空;
}
else if (item<ptr->data)
{
insert(ptr->left, item);//递归插入左子树
if (getheight(ptr->left)-getheight(ptr->right)==2)
{//发生不平衡时,因为是在左子树上是进行操作,所以只存在左子树高于右子树;
if (item<ptr->left->data)
{//当插入的结点构成了RR不平衡,即麻烦结点在不平衡结点的左子树的左子树上时;
ptr = RR(ptr);
//右旋
}
else {
ptr = LR(ptr);
//若构成了LR不平衡,左旋;
}
}
}
else if (item > ptr->data) {
//对于右子树的操作同左子树;
insert(ptr->right, item);
if (getheight(ptr->left)-getheight(ptr->right)==-2)
{
if (item > ptr->right->data) {
ptr = LL(ptr);
}
else {
ptr = RL(ptr);
}
}
}
else
{
cerr << "树中已存在该项,插入失败";
return;
}
ptr->height=getheight(ptr->left) > getheight(ptr->right) ? getheight(ptr->left) +1: getheight(ptr->right)+1;
//在函数最后需要更新树高;
}
6.删除函数:同BST树的删除,需要分三种情况,且注意平衡情况;代码如下:
template <typename T>
void AVL<T>::remove(binnode<T>*&ptr, const T &item) {
//操作与插入相似,但是删除的时候需要分左右子树都存在,或者都不存在,或只存在一个
//不赘述了;
if (ptr == NULL) {
return;
}
else {
if (item < ptr->data) {
remove(ptr->left, item);
if (getheight(ptr->right) - getheight(ptr->left) > 1) {
if (getheight(ptr->right->left) > getheight(ptr->right->left)) {
ptr = RL(ptr);
}
else {
ptr = LL(ptr);
}
}
else {
ptr->height = getheight(ptr->left) > getheight(ptr->right) ? getheight(ptr->left) + 1 : getheight(ptr->right) + 1;
}
}
else if (item > ptr->data) {
remove(ptr->right, item);
if (getheight(ptr->left) - getheight(ptr->right) > 1) {
if (getheight(ptr->left->right) > getheight(ptr->left->left)) {
ptr = LR(ptr);
}
else {
ptr = RR(ptr);
}
}
else
{
ptr->height = getheight(ptr->left) > getheight(ptr->right) ? getheight(ptr->left) + 1 : getheight(ptr->right) + 1;
}
}
else {
if (ptr->left != NULL && ptr->right != NULL) {
if (getheight(ptr->left) > getheight(ptr->right)) {
binnode<T>*tmp = ptr->left;
while (tmp->right != NULL) {
tmp = tmp->right;
}
ptr->data = tmp->data;
remove(ptr->left, ptr->data);
}
else {
binnode<T>*tmp = ptr->right;
while (tmp->left != NULL) {
tmp = tmp->left;
}
ptr->data = tmp->data;
remove(ptr->right, ptr->data);
}
}
else {
binnode<T>*tmp = ptr;
if (ptr->left != NULL) {
ptr = ptr->left;
}
else {
ptr = ptr->right;
}
delete tmp;
}
}
}
}
7.树高更新:在node结点中加入数据height用来记录当前结点的树的高度;
template<typename T>
class binnode {
public:
T data;
binnode*left;
binnode*right;
int height;//记录树的高度;
};
获取树高的函数get_height
template<typename T>
int AVL<T>::getheight(binnode<T>*p) {
if (p == NULL) {
int a = 0;
return a;
}
else
{
return p->height;//对于非空结点直接访问其树高即可;
}
}
用户界面设计(MFC库)
在这次作业中使用了MFC(VS中可以下载)做用户的界面;先上成果图在一步步解释;
界面由密码输入框,用户名输入框,和四个控件组成;
1.新建一个MFC对话框;
在VS的新建中选择MFC应用程序,没有的朋友们可以自己去扩展程序中下载;打开后选择基于对话框然后一直下一步就好了;
接着就会来到像下面图片一样的界面
在左边的工具栏中添加相关控件;添加完之后我们需要对对话框进行一个添加类的操作,以及对相关控件进行添加变量的操作;(目的是为了是他们能够被正常的调用和程序关联起来);
1.添加类(1)右击对话框,添加类;之后会自动生成一个.h的头文件;(2)添加变量:右击相关控件,添加变量,对变量进行命名;
2.相关控件的编程:以登陆按钮为例子,添加变量之后,双击登陆按钮;会进入刚刚添加的类中,将头文件包括进来;
#include "stdafx.h"
#include "classwork.h"
#include "CDia.h"//刚刚添加的文件,将其include进来;
#include "afxdialogex.h"
#include "AVL.h"//将写好的AVL树头文件include进来,并根据文件内容初始化一颗树
双击登陆之后会得到自动跳到对该控件的编写之中;
void CclassworkDlg::OnBnClickedButton1()
{ //登陆按钮的实现。
UpdateData(true);//获取最新的用户名和密码输入框中的值
if (user_name.IsEmpty()) {//User_name是添加的用户名变量;
MessageBox(_T("登陆失败,用户名不能为空"));
return;//当用户名为空时,返回;
}
string str2 = CW2A(user_name.GetString());
//由于用户输入框的类型时cstring 类型的所以需要将其转化为string类型;
//使用CW2A将CSstring 转化为string类型。
if (t.search(str2)) {
//在树中查找给定的用户名,调用树中的查找函数(本文章中并没有写出)
//查找函数若成功找到返回true,未能找到返回false;
string str1 = CW2A(user_pwd.GetString());
//若找到了,将输入框中的值,赋给str1;
if (f.search(str2)==str1) {
//调用File类查找功能并且返回密码进行对比
//flie类中的查找功能根据用户名在文件中进行查找,若找到则返回密码;
//若密码相符,则弹出登陆成功;
MessageBox(_T("登陆成功!欢迎你!"));
}
else {
MessageBox(_T("登陆失败,密码错误"));
}
}
else {
MessageBox(_T("登陆失败,用户名不存在"));
}
}
其他控件写法类似不过多赘述;
file文件类(读写)
需要实现的目的:根据文件中的内容返回其密码等;
class file{
public:
void Insert(string name,string pwd);//向文本中插入新用户
void remove(string name);//删除文本中的指定用户
string search(string name);//根据查找的用户名返回密码;
private:
fstream f;
};
插入函数:
void file::Insert(string name,string pwd){
if(search(name)!="-1"){
return;
}
f.open("1.txt",ios::app|ios::out);
f<<name<<" "<<pwd<<endl;
f.close();
}
删除函数:首先,因为flie没有办法删除指定的内容,所以想法就是将文本中的内容先选择性保存起来,再重新写入;代码如下;
void file::remove(string name){
fstream tmpf;
tmpf.open("2.txt",ios::out|ios::trunc);
f.open("1.txt",ios::in);
while(1){
string str1,str2;
f>>str1>>str2;
if(str1!=name){
tmpf<<str1<<" "<<str2<<endl;
}
if(f.eof()){
break;
}
}
f.close();
tmpf.close();
tmpf.open("2.txt",ios::in);
f.open("1.txt",ios::out|ios::trunc) ;
while(1){
string str1,str2;
tmpf>>str1>>str2;
f<<str1<<" "<<str2<<endl;
if(tmpf.eof()){
break;
}
}
tmpf.close() ;
tmpf.open("2.txt",ios::trunc|ios::out);
tmpf.close();
f.close();
}
查找函数:对给定的用户名进行查找,并且返回密码;
string file::search(string name){
f.open("1.txt",ios::in);
while(1){
string str,str2;
f>>str>>str2;
if(name==str){
f.close();
return str2;
}
else if(f.eof()){
f.close();
break;
}
}
f.close();
return "-1";//用-1作为特殊值;
}
以上就是大概的这次课程设计的内容了,经过老师的指正后发现了一些错误,比如登陆控件的查找中,应该是对AVL树的查找和删除,文件流只提供读写的操作等;以上只是一些关键函数的实现;并非完整的代码,能力有限,希望大佬指点,这学期刚转的计算机专业,还是个菜鸟(记录自己的成长~~),欢迎大家指点呀~~;如果想要完整代码的可以私信我;(介于很多朋友私信我要代码,我已经把代码上传到了github上面以免我回复不及时。github地址能点两个星嘛再好不过。不点嘛也就算了。祝大家学业有成!!
//如果对AVL树的操作还有问题的可以参照一下《数据结构第二版》陈越老师的这本书(书上解析更详细)