一、需求分析
1、问题描述
设计一个程序实现对英文字典的查找。
2、功能需求
用户输入希望查找的英文单词,经程序在预存的英文字典中搜索,并将结果输出在屏幕上(待查找单词在字典中被收录,输出该单词中文释义,待查找单词在字典中未被收录,予以提示)。
程序执行的命令包括:
(1)、程序运行,提示用户输入单词;
(2)、用户输入希望查找的英文单词;
(3)、系统进行搜索,并将结果输出在屏幕上;
(4)、结束。
二、概要设计
1、 问题的抽象数据类型分析
本题要实现对英文字典的查找,需要构造两个抽象数据类型。
一个是英文字典。对于一个结点(结构体)即代表字典中收录的一个英文单词,数据域分为两部分,一部分是单词,另一部分是单词释义。一个英文字典由很多英文单词(即结点)组成,可直接用stl中的内置类型vector容器进行存储。
另一个是高效的搜索结构。其中要存英文单词以供查找,同时,从中查找到待查单词后,要到已构建的英文字典中找该单词对应的释义。因此,要建立英文字典与搜索结构之间的关系。英文字典是利用vector存储的,通过下标可以确实唯一一个英文单词。故可在搜索结构数据域中增加一部分,来记录单词在字典中存储的下标,从而可以快速获取其释义。
2、存储结构设计
1)、存储结构的确定
高效的查找方法,如分块查找、AVL树、B树、哈希表,各有各的特点。我认为AVL树(高度平衡的二叉搜索树)较为高效,选用该存储结构。其搜索时间代价(不论成功还是失败)都不超过树的高度,又因插入时通过平衡化旋转,保证了树的平衡,使得树的高度较小,因而效率较高。
2)、设计AVL树结点
3)、逻辑操作设计
(1)、构造一个空的AVL树
(2)、在AVL树中插入新的结点
(3)、在AVL树中进行搜索
(4)、读取英文字典的txt文档并存储
(5)、利用英文字典构造AVL树
(6)、查找单词并将结果输出
三、详细设计
1、字典结构体的定义:
struct Dictionary
{
string word;
string meaning;
Dictionary():word(""),meaning(""){}
};
2、AVL树结点的定义:
struct AVLNode
{
string data;
int bf;
int num;
AVLNode *left, *right;
AVLNode():data(""),left(NULL),right(NULL),bf(0),num(-1){}
AVLNode(string s,int n,AVLNode *l=NULL,AVLNode *r=NULL)
:data(s),left(l),right(r),bf(0),num(n){}
};
3、AVL树类的定义:
class AVLtree
{
private:
AVLNode *root;
void RotateL(AVLNode *&ptr);
void RotateR(AVLNode *&ptr);
void RotateLR(AVLNode *&ptr);
void RotateRL(AVLNode *&ptr);
public:
AVLtree() :root(NULL) {}
~AVLtree() {};
AVLNode * getRoot()
{
return root;
}
bool Insert(AVLNode *&ptr, string &s,int n);
int Search(const string s, AVLNode *ptr);
};
int AVLtree::Search(const string s, AVLNode *ptr)
{
if (ptr == NULL) return -1;
else if (s < ptr->data) return Search(s, ptr->left);
else if (s > ptr->data) return Search(s, ptr->right);
else return ptr->num;
}
void AVLtree::RotateL(AVLNode *&ptr)
{
AVLNode *subL = ptr;
ptr = subL->right;
subL->right = ptr->left;
ptr->left = subL;
ptr->bf = subL->bf = 0;
}
void AVLtree::RotateR(AVLNode *&ptr)
{
AVLNode *subR = ptr;
ptr = subR->left;
subR->left = ptr->right;
ptr->right = subR;
ptr->bf = subR->bf = 0;
}
void AVLtree::RotateLR(AVLNode *&ptr)
{
AVLNode *subR = ptr, *subL = subR->left;
ptr = subL->right;
subL->right = ptr->left;
ptr->left = subL;
if (ptr->bf <= 0) subL->bf = 0;
else subL->bf = -1;
subR->left = ptr->right;
ptr->right = subR;
if (ptr->bf == -1) subR->bf = 1;
else subR->bf = 0;
ptr->bf = 0;
}
void AVLtree::RotateRL(AVLNode *&ptr)
{
AVLNode *subL = ptr, *subR = subL->right;
ptr = subR->left;
subR->left = ptr->right;
ptr->right = subR;
if (ptr->bf >= 0) subR->bf = 0;
else subR->bf = 1;
subL->right = ptr->left;
ptr->left = subL;
if (ptr->bf == 1) subL->bf = -1;
else subR->bf = 0;
ptr->bf = 0;
}
bool AVLtree::Insert(AVLNode *&ptr, string &s,int n)
{
AVLNode *pr = NULL;
AVLNode *p = ptr, *q;
int lable; //标签
stack<AVLNode*> st; //栈记录查找路径,当插入元素后,通过出栈回溯,平衡化祖先节点
while (p != NULL)
{
if (s == p->data) return false; //存在值为x的结点,不插入
pr = p; //pr存储插入节点的父结点
st.push(pr); //存储搜索路径
if (s < p->data) p = p->left;
else p = p->right;
}
p = new AVLNode(s,n);
if (p == NULL)
{
cerr << "存储空间分配错误!" << endl; exit(1);
}
if (pr == NULL)
{
ptr = p;
return true; //空树,新插入结点为根节点
}
if (s < pr->data)
{
pr->left = p;
}
else
{
pr->right = p;
}
while (st.empty() == false) //将插入新结点后得到的树重新平衡化
{
pr = st.top();
st.pop();
if (p == pr->left) pr->bf--;
else pr->bf++;
if (pr->bf == 0)break; //插入后父结点平衡因子为0,无需调整,直接退出
if (abs(pr->bf) == 1) p = pr; //插入后父结点平衡因子为1,回溯判断
if (abs(pr->bf) == 2)
{
lable = (pr->bf < 0) ? -1 : 1; //记录父结点平衡因子符号,判断单旋还是双旋
if (p->bf == lable)//两结点平衡因子同号,单旋
{
if (lable == 1)RotateL(pr);
else RotateR(pr);
}
else //两节点平衡因子异号,双旋
{
if (lable == 1) RotateRL(pr);
else RotateLR(pr);
}
break;
}
}
if (st.empty())
{
ptr = pr;
}
else//将AVL树重新链接好
{
q = st.top();
if (q->data > pr->data)
{
q->left = pr;
}
else
{
q->right = pr;
}
}
return true;
}
4、主函数:
void main()
{
vector<Dictionary> VecDic;
AVLtree at;
int i = 0;
Dictionary dic;
string s1, s2;
ifstream ifile;
ifile.open("English dictionary.txt");
if (!ifile)
{
cout << "无法打开 English dictionary.txt !" << endl;
exit(1);
}
VecDic.empty();
while (ifile >> s1 >> s2)
{
dic.word = s1;
dic.meaning = s2;
VecDic.push_back(dic);
}
ifile.close();
AVLNode *ptr = at.getRoot();
for (i = 0; i < VecDic.size(); i++)
{
at.Insert(ptr, VecDic[i].word, i);
}
cout << "欢迎使用Snoopy的英文字典(该字典只收录了以a开头的单词)" << endl;
cout << "请输入您希望查找的单词:";
string s;
cin >> s;
int r = at.Search(s, ptr);
if (r == -1)
cout << "抱歉,此字典未收录您希望查找的单词!" << endl;
else
cout << "您希望查找的单词" << VecDic[r].word << "的释义为" << VecDic[r].meaning << endl;
cout << "欢迎您再次使用!" << endl;
system("pause");
}
四、测试实例
结论:测试结果正确。
PS:读入的English dictionary.txt长这个样子
关于为什么只有a开头的单词,是因为我太懒了,没有收集整理其他字母开头单词的txt文档(狗头)。要扩充的话,大噶自己整理就阔以啦。(唔,txt俺就不放了8~)