背景
自组织线性表根据估算的访问频率排列记录,先放置请求频率最高的记录,接下来是请求频率次高的记录,依此类推。自组织线性表根据实际的记录访问模式在线性表中修改记录顺序。自组织线性表使用启发式规则决定如何重新排列线性表。转置方法的基本原理是,在一次查找过程中,一旦找到一个记录,则将它与前一个位置的记录交换位置。这样,随着时间的推移,经常访问的记录将移动到线性表的前端,而曾经频繁使用但以后不再访问的记录将逐渐退至线性表的后面。
尽管一般情况下自组织线性表的效率可能没有查找数和已排序的线性表那么好,但它也有自身的优势。它可以不必对线性表进行排序,新记录的插入代价很小;同时也比查找树更容易实现,且无需额外的存储空间。
实验目的
学习课程查找中的“自组织线性表”。实现一个自组织线性表。
基本要求
1.从文件中读入一组汉字集合,用自组织线性表保存。自组织线性表在查询时,采用转置法调整自组织线性表的内容。
2.从文件中依次读入需查询的汉字,把查询结果保存在文件中(如找到,返回比较的次数,如果没有找到,返回比较的次数)
一、 需求分析
0.问题描述
给定一个包含一组汉字集合的文件且文件中的汉字无重复,现在考虑查询一组汉字集合中的每个元素是否出现在该文件中。请你设计一种查找策略,使访问频率的变化能够很好的反映出来。
1.问题分析
实现的功能:
1.依次从文件读入汉字
2.依次从文件读入需查询的汉字
3.设计一种汉字的存储结构
4.实现汉字查询操作
5.将查询结果保存在文件中并打印出来,结果包括文件内容、查找是否成功和查找次数
2.输入数据
1)通过给定文件读取一组汉字组合(汉字无重复),通过待查找文件依次读取要查询的
汉字。
2)给定文件中内容仅有汉字,且两文件中汉字个数均大于0小于100。
3)原始文件与查找文件中的汉字之间不允许有空格
3.输出数据
每查询一个汉字输出给定文件内的内容、是否查找成功、比较的次数。
例:查找五 查找成功 共比较3次 查找后排序为:一五二三四六七九十
4.测试样例设计
【样例一】待查找的汉字集合中无重复汉字
给定汉字:人生若只如初见
待查汉字:之吖只蒽
正确结果:
查找之 查找失败 共比较7次 查找后排序为:人生若只如初见
查找吖 查找失败 共比较7次 查找后排序为:人生若只如初见
查找只 查找成功 共比较4次 查找后排序为:人生只若如初见
查找蒽 查找失败 共比较7次 查找后排序为:人生只若如初见
【样例二】一直查找同一个数,观察位置变化
给定汉字:壹贰叁肆伍陆柒捌玖拾
待查汉字:伍伍伍伍伍伍
正确结果:
查找伍 查找成功 共比较5次 查找后排序为:壹贰叁伍肆陆柒捌玖拾
查找伍 查找成功 共比较4次 查找后排序为:壹贰伍叁肆陆柒捌玖拾
查找伍 查找成功 共比较3次 查找后排序为:壹伍贰叁肆陆柒捌玖拾
查找伍 查找成功 共比较2次 查找后排序为:伍壹贰叁肆陆柒捌玖拾
查找伍 查找成功 共比较1次 查找后排序为:伍壹贰叁肆陆柒捌玖拾
查找伍 查找成功 共比较1次 查找后排序为:伍壹贰叁肆陆柒捌玖拾
【样例三】待查找的汉字集合中含多种汉字形态且含重复汉字
给定汉字:如果你也听说过
待查汉字:说过没听说过
正确结果:
查找说 查找成功 共比较6次 查找后排序为:如果你也说听过
查找过 查找成功 共比较7次 查找后排序为:如果你也说过听
查找没 查找失败 共比较7次 查找后排序为:如果你也说过听
查找听 查找成功 共比较7次 查找后排序为:如果你也说听过
查找说 查找成功 共比较5次 查找后排序为:如果你说也听过
查找过 查找成功 共比较7次 查找后排序为:如果你说也过听
【样例四】重复多次查找给定文件中最后两个汉字
给定汉字:数据结构好学
待查汉字:学好学好学好
正确结果:
查找学 查找成功 共比较6次 查找后排序为:数据结构学好
查找好 查找成功 共比较6次 查找后排序为:数据结构好学
查找学 查找成功 共比较6次 查找后排序为:数据结构学好
查找好 查找成功 共比较6次 查找后排序为:数据结构好学
查找学 查找成功 共比较6次 查找后排序为:数据结构学好
查找好 查找成功 共比较6次 查找后排序为:数据结构好学
二、概要设计
0.数据类型
由于汉字占两个字节,因此这里可以定义一个二元组poly表示一个汉字。
1.抽象数据类型
汉字集合中在唯一的第一个元素和最后一个元素,且他们是前后相继的,所以他们逻辑 上具有线性关系,故可以使用线性结构;而以转置法实现自组织线性表可以将访问频率 的变化能够很好的反映出来,所以以此作为查找表的结构与调整策略。
数据对象:一组汉字集合
数据关系:每一项前后相继,具有一定的线性特征,为线性关系,且以一定的规则调整
顺序
基本操作:
1)准备能储存这组数据的储存空间
2)添加元素
3)访问元素
4)设置元素的值
自组织线性表ADT的设计:
ADT node{
数据对象:D={pi|pi∈汉字字符poly,i=1,2,... ,n,n∈整数}
数据关系:R={R1,R2}
R1={<pi-1,pi>|pi-1,pi∈D,i=2,... ,n,n∈整数}
R2={如果D中有元素被查找,则通过自组织启发式规则重新排列线性表}
基本操作:
void Append(ploy &Elem);
//操作功能:将元素Elem添加到线性表的表尾
void moveToStart();
//操作功能:将当前位置移动定位到表头
void Next();
//操作功能:将当前位置移动到下一位置
void Prev();
//操作功能:将当前位置移动到上一位置
ploy getValue();
//操作功能:获取当前位置的元素值
void setValue();
//操作功能:设置当前位置的元素值
int getsize();
//操作功能:获取当前线性表的长度
int getCurrIndex();
//操作功能:获得当前位置在链表中的正向顺序,即从表头往表尾数,当前位 置所在结点是第几个结点
bool find(ploy s, void(*visit)( Node *A));
//操作功能:在自组织线性表中查找s
//若s被找到,则调用visit传进来的自组织启发规则函数,结果返回真,否 则结果返回假。
}
2.算法的基本思想
1.先读取给定文件,将文件中的汉字依次添加到自组织线性表A中;
2.依次读取查找文件中的每个汉字,调用自组织线性表A的查找函数并启用其转置规 则;
3.根据查找函数结果,将最终结果保存到文件中。
3.程序的流程
读入模块:从文件中读入一组汉字集合,将其保存在自组织线性表中,并逐个读入要查 找的汉字。
转置模块:实现自组织线性表中的转置规则
查找模块:调用自组织线性表A的查找函数进行查找
输出模块:输出查找结果、计较次数以及查找后的顺序
流程图:
三、详细设计
1.物理数据类型
物理数据类型:由于汉字集合中汉字占两个字节,故采用结构体这种物理数据结构。
typedef struct poly
{
char a[2];
} poly;
物理数据结构:由于查找都是顺序查找,且转置时仅交换前后两个元素的位置,所以不需要对线性表进行随机访问,而给定文件中汉字个数不是已知的,又为了在计算机缺少连续的储存空间时还能方便汉字的存储和转置,所以选择用双链表实现线性表。
ADT的实现:
void Append(poly &it) // 在列表的尾部追加结点并赋值
{
tail->next=new Node(it,tail,NULL);
tail=tail->next;
size++;
}
void moveToStart()//移动至链表开始位置
{
current = head;
}
void Next()//移动指针到下一位置
{
if (current != tail) current = current->next;
else return;
}
void Prev()//移动指针到上一位置
{
if (current != head) current = current->prev;
else return;
}
poly getValue() // 返回当前元素
{
assert(current != NULL);//内容为空
return current->elem;
}
void setValue(poly e) //设置当前位置的值
{
current->elem = e;
}
int getsize() //返回线性表长度
{
return size;
}
int getCurrIndex()//获得当前位置
{
int count = 0;
Node*p;
p = head;
while (curr!=p){
p = p->next();
count++; }
return count;
}
bool find(ploy s, void(*visit)(Node *A))// 若s被找到,则调用visit传进来的自组织启发规则函数,结果返回真,否则结果返回假。
{
curr = head;
for (int i = 1; i <=getSize(); i ++)
{
curr=curr->getNext();
if (curr->element.a[0] == s.a[0]&& curr->element.a[1] == s.a[1])
{
visit(curr);
return true;
}
} return false;
}
2.输入和输出的格式
输入格式:直接读取input与find文件,input与find文件中汉字间不允许有空格
输出格式:输出查找的汉字,是否查找成功以及比较的次数,之间以空格隔开
形如:汉字+空格+查找成功或失败+空格+共比较n次+空格+查找后的顺序
3.算法的具体步骤
读入模块:
bool star(Doublelist& A)
{ poly pp;
//读入文件"text1.txt"内容 ifstream fin("text1.txt");
string a;
fin>>a;
for (int i = 0; i < a.length(); i+=2)
{
pp.a[0]=a[i]; pp.a[1]=a[i+1];
//将汉字添加到线性表 A.Append(pp);
的表尾 }
fin.close();
return true;
}
转置模块:
void swap(Node *A)
{
//如果当前元素处于线性 if (A->getPrev()->getvalue().a[0]=='0') return;
表第一个位置,不做置换 poly temp1;
temp1 = A->getvalue();
//与上一个元素进行交换 A->setvalue(A->getPrev()->getvalue());
A->getPrev()->setvalue(temp1);
}
查找模块:
void search(Doublelist& A)
{
//"text2.txt"为查找文件 ifstream fin("text2.txt");
string b; getline(fin,b);
fin.close(); ofstream fout;
int i,size = 0;
//"text3.txt"用于保存结果 fout.open("text3.txt");
poly p;
for (int j = 0; j < b.length(); j +=2)
{
p.a[0]=b[j]; p.a[1]=b[j+1];
//judge表示是否查找成功 bool judge = A.find(p,swap);
调用输出模块;
}
输出模块:
//judge表示是否查找成功 if (judge)
//如果judge值为真 {
则查找成功 fout<<"“"<<p.a[0]<<p.a[1]<<"”"<<" 查找成功 "<<"查找
//将结果保存到文件中 次数为:"<< A.getCurrIndex() <<endl;
}
//否则查找失败 else
{
//将结果保存到文件中 fout <<"“"<<<p.a[0]<<p.a[1]<<"”"<<" 查找失败 "<<"
查找次数为:"<< A.getCurrIndex() <<endl;
}
//将当前位置移动到 A.MoveToHead();
第一个汉字的位置 A.Next();
//将线性表中元素顺序保存 for (i = 1; i <=A.getSize(); i ++)
到文件中 fout << A.getValue();
fout << endl;
4.算法的时空分析
1.在构建自组织线性表时,由于要依次在表尾添加元素,所以时间复杂度是O(n);
2.每次转置操作数为常数,所以时间复杂度是O(1);
3.在查找时,由于每查找一次都要遍历一遍线性表,所以时间复杂度为O(n*m);
4.每次读取文件中的查询结果的时间复杂度是O(1)。
四、调试分析
1.调试方案设计
撰写调试计划和方案。包括:调试目的,样例,调式计划和设置。
调试目的:对find操作进行调试,找到转置不成功的原因
样例:给定汉字:壹贰叁肆伍陆柒捌玖拾
待查汉字:伍伍伍伍伍伍
调试计划:在find()操作处设置断点,在函数里面设置一个断点,然后开始编译调试,观察查找的过程以及查找到后是否会实现转置。
设置:1.设置断点
2.添加查看
3.编译调试
开始查找text2中的汉字
查找成功,但是没有进入转置函数
2.调试过程和结果,及分析
调试结果:
查找成功后,并没有进行转置。
分析:函数指针并没有将转置函数传进去,所以函数指针指向的函数只能是结点,不能是类的对象。
改正方法:将swap函数中的参数改为结点指针。
五、测试结果
样例一:
分析:之、吖、蒽都不能被查找到,所以要遍历,查找次数为7;“只”能够被找到,于是与 前一个汉字交换位置。
样例二:
分析:“伍”每被找到一次,就与前一个汉字交换位置,交换到第一位时,每次查找次数都为1,且自组织线性表顺序不再改变。
样例三:
分析:每找到一个元素,就让它与前面的元素交换位置。
样例四:
分析:每互换位置2次,自组织线性表恢复原来的顺序,所以查找次数都为自组织线性表的长度。
六、实验日志(选做)
2018-12-13:复习自组织线性表的三种构建策略以及线性表的实现。
2018-12-14:写预习报告的需求分析
2018-12-15:进行概要设计与详细设计
2018-12-16:将单链表实现改为双链表实现,根据算法画出流程图并学习如何用c++写入与读取文件
2018-12-17:用双链表的ADT实现自组织线性表,发现由于汉字的字节大小原因,所以不能向处理英文字符那样处理,会出现乱码,于是改动代码,修改实验报告,对代码进行调试并测试设计的样例并截图分析。
2018-12-18:修改ADT,重新调整代码,使用一个结构体储存汉字,发现这样确实比直接储存在链表中容易多了,看来自己在编程之前确实不能够操之过急。在修改代码的时候总会弄错指针的当前位置,遇到了函数指针作为函数参数但是线性表无法修改的问题,然后我看了下二叉链表树的实现,然后把swap函数的参数有类改成了结点,然后就对了,看来还是我编程的功底太差了。
附代码:
Node.h
#include<iostream>
using namespace std;
typedef struct poly
{
char a[2];
} poly;
class Node
{
public:
poly element;
Node* prev;
Node* next;
Node()
{
element.a[0]='0';
element.a[1]='0';
prev=NULL;
next=NULL;
}
Node(const poly &it,Node *pprev,Node *pnext)
{
element=it;
prev=pprev;
next=pnext;
}
Node(Node *pprev,Node *pnext)
{
prev=pprev;
next=pnext;
}
~Node(){}
Node* getPrev();
Node* getNext();
poly getvalue();
poly setvalue(poly e);
};
//获得prev指针
Node* Node::getPrev()
{
return prev;
}
//获得next指针
Node*Node::getNext()
{
return next;
}
//获得元素值
poly Node::getvalue()
{
return element;
}
poly Node::setvalue(poly e)
{
element = e;
}
Doublelist.h
#include"Node.h"
class Doublelist
{
public:
Doublelist()
{
New();
}
~Doublelist()
{
Deletehead();
}
void Deletehead();
void Deletetail();
void Clear();
void MoveToHead();
void MoveToTail();
void Prev();
void Next();
void Append(const poly &it);
void Insert(const poly &it);
void Delete();
poly getValue();
void setValue(poly elem);
Node* getHead();
Node* getCurr();
Node* getTail();
int getSize();
int getCurrIndex();
bool find(poly p,void(*visit)(Node *A));
private:
Node* head;
Node* tail;
Node* curr;
int size;
//初始化一个双向链表
void New()
{
curr=new Node(0,0);
tail=new Node(0,0);
head=new Node(0,NULL);
tail=curr=head;
size=0;
}
};
//删除头结点
void Doublelist::Deletehead()
{
while(head!=NULL)
{
curr=head;
head=head->next ;
delete curr;
}
}
//删除尾结点
void Doublelist::Deletetail()
{
while(tail!=NULL)
{
curr=tail;
tail=tail->prev ;
delete curr;
}
}
//清空树
void Doublelist::Clear()
{
Deletehead();
New();
}
//移动至头结点
void Doublelist::MoveToHead()
{
curr=head;
}
//移动至尾结点
void Doublelist::MoveToTail()
{
curr=tail;
}
//当前位置前移
void Doublelist::Prev()
{
if(curr!=head)
{
curr=curr->prev ;
}
}
//当前位置后移
void Doublelist::Next()
{
if(curr!=tail)
{
curr=curr->next ;
}
}
//添加操作
void Doublelist::Append(const poly &it)
{
tail->next=new Node(it,tail,NULL);
tail=tail->next;
size++;
}
//插入操作
void Doublelist::Insert(const poly &it)
{
if(curr!=head)
{
curr->prev =curr->prev->next =new Node(it,curr->prev,curr);
curr=curr->prev ;
size++;
}
}
//删除操作
void Doublelist::Delete()
{
if(curr!=tail){
curr->prev->next =curr->next;
curr->next->prev =curr->prev;
curr=curr->next;
size--;
}
else
{
curr->prev=NULL;
curr=curr->prev;
size--;
}
}
//返回当前位置元素的值
poly Doublelist::getValue()
{
return curr->element ;
}
void Doublelist::setValue(poly elem)
{
curr->element = elem;
}
//获得头结点
Node* Doublelist::getHead ()
{
return head;
}
//获得当前位置
Node* Doublelist::getCurr()
{
return curr;
}
//获得尾结点
Node* Doublelist::getTail()
{
return tail;
}
//获得链表结点个数
int Doublelist::getSize()
{
return size;
}
//获得当前位置在链表中的正向顺序,即从表头往表尾数,当前位置所在结点是第几个结点
int Doublelist::getCurrIndex()
{
int count=0;
Node* p;
p=head;
while(curr!=p){
p=p->next ;
count++;
}
return count;
}
//查找s
bool Doublelist::find(poly s,void(*visit)(Node *A))
{
curr = head;
for (int i = 1; i <=getSize(); i ++)
{
curr=curr->getNext();
if (curr->element.a[0] == s.a[0]&& curr->element.a[1] == s.a[1])
{
if (curr->getPrev()!=head)
visit(curr);
return true;
}
} return false;
}
main.cpp
#include<string>
#include<iostream>
#include<fstream>
//#include "Node.h"
#include "Doublelist.h"
using namespace std;
#define Elem poly
bool star(Doublelist& A)
{
ifstream fin("text1.txt");
string a;
fin>>a;// cout<<a.length();
poly pp;
for (int i = 0; i < a.length(); i+=2)
{
pp.a[0] = a[i];
pp.a[1] = a[i+1];
A.Append(pp);
}//cout<<"Achangdu"<<A.getCurrIndex()<<" "<<A.getSize()<<endl;
fin.close();
return true;
}
void swap(Node *A)
{
if (A->getPrev()->getvalue().a[0]=='0'&&A->getPrev()->getvalue().a[1]=='0') return;
Elem temp1;
temp1 = A->getvalue();
A->setvalue(A->getPrev()->getvalue());
A->getPrev()->setvalue(temp1);
}
void search(Doublelist& A)
{
ifstream fin("text2.txt");
string b;
fin>>b; //cout<<b;
fin.close();
ofstream fout;
int i;
int size = 0;
fout.open("text3.txt"); //cout<<" "<<b.length();
poly p;
for (int j=0; j<b.length(); j+=2)
{
p.a[0]=b[j]; p.a[1]=b[j+1];
bool judge=A.find(p,swap);
if (judge)
{
// cout<<"“"<<b[j]<<b[j+1]<<"”"<<" 查找成功 "<<"查找次数为:"<< i/2+1 <<endl;
fout<<"“"<<b[j]<<b[j+1]<<"”"<<" 查找成功 "<<"查找次数为:"<< A.getCurrIndex() <<"查找后顺序为:";
}
else
{
// cout <<"“"<< b[j] << b[j + 1] <<"”"<<" 查找失败 "<<"查找次数为:"<< i / 2 << endl;
fout <<"“"<< b[j] << b[j + 1] <<"”"<<" 查找失败 "<<"查找次数为:"<< A.getCurrIndex() <<"查找后顺序为:";
}
A.MoveToHead();
for (i = 1; i <=A.getSize(); i ++)
{A.Next(); fout << A.getValue().a[0]<<A.getValue().a[1]; }
fout << endl;
}
fout.close();
}
int main()
{
Doublelist a;
star(a);
search(a);
return 0;
}