程序设计方法专题实验报告Word文档下载推荐.docx
《程序设计方法专题实验报告Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《程序设计方法专题实验报告Word文档下载推荐.docx(25页珍藏版)》请在冰豆网上搜索。
![程序设计方法专题实验报告Word文档下载推荐.docx](https://file1.bdocx.com/fileroot1/2023-2/3/9366bd25-9349-4fbc-b721-b24f97458a2c/9366bd25-9349-4fbc-b721-b24f97458a2c1.gif)
输出文件:
一行,为D=A*B*C的第x行y列元素的值。
算法设计及主要程序:
问题分析:
本题的主要考虑两个方面,一是稀疏矩阵的压缩储存,二是两个稀疏矩阵之间的乘法。
其中最重要的一步又是矩阵的三元组乘法。
任务一流程图
(1).根据矩阵相乘的定义有:
,
在经典算法中,不论
,
的值是否为0,都要进行一次乘法,而实际上,这两者有一个值值为0时,其积也为0。
因此,在对稀疏矩阵进行相乘运算时,应该免去这种无效操作,为求Q的值,只需在M.data中和N.data中找到对应元素(即M.data中的j与N.data中的i相等的元素)相乘即可。
(2).这样相乘的基本操作是:
对于M中的每个元素M.data[p](p=1,2,3……,M.da_num),找到N中所有满足M.data[p].j=N.data[p].i的元素N.data[q],求得其乘积。
由于矩阵Q中每个元素的值是个累计和,这个乘积M.data[p].v×
N.data[p].v只是Q[i][j]的一部分。
为了便于操作,应当对每个元素设计一个累计和变量,其初值为0,然后扫描数组M,求得相应元素的乘积并累加到适当的求和累计和的变量上。
(3).两个稀疏矩阵相乘的乘积不一定为零矩阵。
而两个即使矩阵的分量不为0,而乘积也可能是0。
因此乘积矩阵Q中的元素是否为非零元,只有在求得其累加和后才能得知。
由于Q中元素的行号和M中的行号一致,由此可对Q进行逐行处理,先求得累计求和的中间结果(Q的一行),然后再压缩到Q.data中去。
在解决了稀疏矩阵三元组相乘后,其储存结构也就迎刃而解了。
只需够造一个三元组类记录三元组的属性(行号,列号,值),在构造矩阵类的时候加入属性举证行数,列数,三元组非零元总数,行优先标记数组就可。
根据以上分析,程序设计流程图如上面所示:
其中最重要的矩阵的三元组乘法程序如下:
//---------两稀疏矩阵相乘的主要算法,定义为友元函数--------------//
MatrixMult_Matrix(Matrix&
m,Matrix&
n){
MatrixQ(m.row,n.col);
int*ctemp=newint[n.col];
inttp,t_row,tr,t_col;
if(m.col!
=n.row){
cout<
<
"
两矩阵不能相乘!
endl;
exit
(1);
}
if(m.da_num*n.da_num){//若果结果为非零矩阵
for(inti=0;
i!
=m.row;
++i){//逐行相乘,得到Q各行的的结果
for(intii=0;
ii!
=n.col;
++ii)
ctemp[ii]=0;
//每次temp都必须初始话为
Q.rops[i]=Q.da_num;
//查找到一行的首个非零元素,找到就返回其值,否值就返回-,表示该行全为,不用相乘
//找到该行的最后一个非零元的下一个元素位置,注意,若该行为行(全为)的话,那么就该行首个非零元素
//就和最后一个非零元的下一个元素位置相同,那么直接跳出循环,不用相乘
if(i!
=m.row-1)
tp=m.rops[i+1];
else
tp=m.da_num;
for(intj=m.rops[i];
j!
=tp;
++j){//一行的各个三元组分别相乘累加
t_row=m.da[j].j;
//该行某个元素找到与其相对应Matrix&
n的行的首个非零元
if(t_row!
=n.row-1)//该行某个元素找到与其相对应Matrix&
n的行的最后个非零元的下一位置
tr=n.rops[t_row+1];
else
tr=n.da_num;
//如果中对应的Matrix&
n行不是行,则相乘累加
for(intk=n.rops[t_row];
(k+1)&
&
k!
=tr;
++k){
t_col=n.da[k].j;
//找到结果所在列号
ctemp[t_col]+=m.da[j].e*n.da[k].e;
//对应的行号和列号相等的元素相加后累加
}//fork;
}//求得Q中第i行的非零元。
//压入储存三元组结果
for(t_col=0;
t_col!
=Q.col;
++t_col)
if(ctemp[t_col]){
Q.da_num++;
dataDA(i,t_col,ctemp[t_col]);
Q.da.push_back(DA);
}
}
delete[]ctemp;
returnQ;
}
//-------------------------------------------------------------//
根据稀疏矩阵相乘算法的要求与题意,定义三元组数据类和矩阵类如下:
//--------定义数据类,储存三元组数据---------//
classdata{
public:
inti,j;
inte;
data(inti=0,intj=0,inte=0){
this->
i=i;
j=j;
e=e;
voidset_data(inti,intj,inte){
this->
};
//-------—————定义矩阵类--------------//
classMatrix{
vector<
data>
da;
introps[MAXSIZE];
introw,col,da_num;
Matrix(){
row=0;
col=0;
da_num=0;
Matrix(introw,intcol,intda_num=0){
row=row;
col=col;
da_num=da_num;
=da_num;
++i)
rops[i]=-1;
voidput_data(datad){
if(!
da.size())
put_rops(d.i,da.size());
if(da.size()&
d.i!
=da[da.size()-1].i)
da.push_back(d);
voidput_rops(intm,intn){
rops[m]=n;
intget_number(intm,intn){
++i){
if(da[i].i==m&
da[i].j==n)
returnda[i].e;
return0;
friendMatrixMult_Matrix(Matrix&
n);
//---------------------------------------------------------------//
运行结果:
本程序在LinuxGCC和WindosSP2VC++2008下均调试通过。
其运行结果如下:
程序结果分析:
分析算法的时间复杂度有如下结果:
累加器ctemp初始化的效率为O(A.da_num*B.da_num),求两矩阵相乘结果中的所有非零元的时间复杂度为O(M.col*N.col/N.row),进行压缩的时间复杂度为O(M.row*N.col),总的时间复杂度为O(M.row*N.col+M.col*N.col/N.row)。
实验任务二:
单词统计
统计输入文件中出现的不同的单词个数以及每个单词出现的频率,并起将这些单词按照词典的顺序排列好输出到文件中。
输入:
以文件的形式记录程序中所需要的数据。
输出:
结果应该存放在一个文件中,该文件的第一行是不同的单词个数,从第二行开始则为每个单词和其相应的频率,单词与频率之间用空格符分割,单词需按字典顺序排列。
算法设计与主要程序段:
本题的主要是单词的有序统计,主要难点在于如何设计高效的统计函数,达到广泛的统计要求(一般可统计数百万单词),首先是读入文件时的单词分割,然后是两个单词的比较(本题而言,可以重载“<
”和“=”操作符),单词分割和两个单词的比较都比较简单,时间代价是线性的0
(1),故不需要优化。
只要是单词的统计上,如何去寻找好的数据结构和高效的算法。
首先可以想到数组,数组的优点是其有序性,故重载“<
”和“=”操作符时可以利用二分查找法插入位置,时间代价为0(log(n)),但是在新的单词插入后,整个数组的序列会改变,这种元素移位操作却非常耗时,代价为O(n^2),再加上数组对统计单词数具有容量限制(数组的大小必须实现分配),就算用优化后的向量可以满足动态分配内存,但总的说来时间时间代价昂贵,不能采用。
再次是链表,链表优点可以动态分配内存,对单词的统计量没有限制,缺点是没有好的方法来实现重载“<
”和“=”操作符,这个代价是0(n^2)。
当然优化后的List在排序时可以小一点,但时间效代价仍然非常昂贵,也不能采用。
考虑到前面两种数据结构各自的优缺点,可以直到数据结构中的二叉排序树(AVL)可以很好的解决动态储存空间的分配和插入查找,其时间代价为O(log(n)),已经非常廉价,但AVL在实现重载“<
”和“=”操作符只能单向二分,为此可以对节点进行红黑标记,实现双向二分查找。
任务二流程图
查找与插入问题解决后,接下来就是单词与其个数之间的关系问题。
利用红黑数时,不妨可采用每个节点5个域,分别标记前驱,后继,红黑标记,键(单词),码(单词个数)。
这样的优点是可以将单词与其个数之间形成二元关系,即二元键值对。
通过“<
”和“=”操作符运算,若一个单词(键)已经存在,只需将它的码值加1,若不存在,就在双向二分得到的位置插入新的结点,得到新的键值对。
当然,要实现一颗完好的红黑数是比较麻烦的,可以采用STL中对红黑优化过后的map。
经过以上分析,程序设计流程图如上所示:
其中单词分割程序为:
//--------------------将字符串转换成为单词----------------------//
boolchangeWords(string&
s){
stringtemp;
inti=0;
intj;
while(!
((s[i]>
='
a'
'
z'
>
=s[i])||(s[i]>
A'
Z'
=s[i]))&
=s.size())
++i;
j=i;
while(((s[j]>
=s[j])||(s[j]>
=s[j])||(s[j]==39)))
++j;
if(i==s.size())
returnfalse;
for(;
=j&
=s.size();
if(s[i]<
)
s[i]=s[i]+'
-'
;
temp.push_back(s[i]);
s=temp;
returntrue;
//--------------------------------------------------------------//
单词计数数据结构map的选择:
map<
string,int>
counters;
intsum=0;
strings;
while(in>
s){
if(changeWords(s)){
counters[s]++;
sum++;
进行单词统计的时候,只统计ASCII编码的单词,对于其他编码,如UNICLDE,GB2312,GBK,BIG5等编码的全角字母(占两个字节),全部滤过不做统计,对汉字及其他双字节的文字一律滤过,不做统计。
算法设计主要是利用map中红黑树,实现键值对,一个单词(键)对应一个值(值),形成映射.在查找和插入时主要是红黑数的双向二分查找插入,效率为O(log(size_of_map)),对于本程序的运用(统计量大约在10MB,1000000个单词以下来说,效率是可以接受的)。
如果需要进行更大的统计,则可以用hash表来实现编码。
查找和插入时间效率为O
(1)。
但是编码和处理地址冲突的指令较多,所以在统计少量的单词时,反而没有map快,同时由于统计对单词是有序要求,hash技术对于保持有序上很难做到,故另外还需要设计排序方案,但排序本身也是一个很耗时的事情,因而本题目的最佳选择还是map。
实验任务三:
指针式时钟
可视化的显一个指针式模拟时钟;
可为程序设计一个美观大方的图标;
通过菜单可以调整时间,定制指针式时钟的显示风格,比如指针、表盘的颜色、外形等,可以按照个人的兴趣进行其他的属性的扩展。
主要设计思路和所涉及的类:
本题是一个可视化编程问题,目前可视化编程工具比较多,我采用的是比较流行的VC++。
对于题目要求的可视化,可以建一个SDI(单文档视图结构)程序,设计主要分为可视化和控件标准两部分。
前一部分主要在Cview类中完成,后一部分主要在Cframe类中完成。
其中可视化画图主要在OnDraw函数中添加代码实现。
设计时首先是读取时间,现在读取时间的方式主要有从操作系统和网络远程读取,由于寝室上网不方便,我采用的是从操作系统中读取时间,然后由时间变量用三角函数转换得到各个指针的首末坐标,进而画出表盘,再添加计时器,每个一秒重新读取一次新时间,并更新表盘画面,这样就可以使闹钟动起来。
各个控件的属性与响应函数可以自己在Cframe类中添加代码完成。
按照以上的设计规划,可以将整个任务分为以下三块:
1.读取时间,以及记录闹钟的时间等,设定定时器。
2.画出表盘时钟,实现美观大方的可视化。
3.附加功能,如闹钟,日列,备忘录等控件设计。
对于第一块,先定义几个全局型变量
externboolifon;
//闹钟标记
externboolifsound;
//闹钟声音标记
externinth,m,s;
//分别记录闹钟的时分秒
再设计计时器,使之每一秒钟向系统读一次时间。
代码如下:
intCAlarmClockView:
:
OnCreate(LPCREATESTRUCTlpCreateStruct)
{
if(CFormView:
OnCreate(lpCreateStruct)==-1)
return-1;
//TODO:
Addyourspecializedcreationcodehere
//设置时间步长为1s.
SetTimer(1,1000,NULL);
接下来将重操作系统读取的UNIX时间节转化为时分秒数字时钟。
代码为:
CTimeNow=CTime:
GetCurrentTime();
//读取操作系统的时间。
m_tDate=Now;
UpdateData(false);
m_dDate.SetToday(&
Now);
//将UNIX时间节转化数字形式
CStrings1,stime,ntime;
stime.Format("
%d:
%02d:
%02d"
h,m,s);
//将闹钟时间格式化
//将UNIX时间节转化为时分秒数字时钟,并格式化
ntime.Format("
Now.GetHour(),Now.GetMinute(),Now.GetSecond());
第二块,有了以上读出的系统时间后,就可以根据时间变量来画出此时刻的半盘,代码如下:
//界面美观性的设计
if(Now.GetHour()<
=11)s1="
早上好,欢迎您使用Rolex情侣珍藏版AlarmClock!
elseif(Now.GetHour()<
=13)s1="
中午好,欢迎您使用Rolex情侣珍藏版AlarmClock!
=18)s1="
下午好,欢迎您使用Rolex情侣珍藏版AlarmClock!
elses1="
晚上好,欢迎您使用Rolex情侣珍藏版AlarmClock!
pDC->
SetTextColor(RGB(0,0,255));
TextOut(90,15,s1);
//为闹钟同时也设计界面
if(!
ifon)
{
m_Static1.ShowWindow(false);
pDC->
SetTextColor(RGB(255,0,0));
TextOut(60,245,"
闹铃功能未启动"
);
else
m_Static1.ShowWindow(true);
闹铃已启动,时间为"
+stime);
SetTextColor(RGB(0,0,0));
TextOut(300,230,"
现在时刻:
Rectangle(365,255,465,270);
SetTextColor(RGB(0,255,255));
TextOut(300,255,ntime);
CBrushbr,br1;
br.CreateSolidBrush(RGB(0,255,0));
SelectStockObject(NULL_PEN);
SelectObject(&
br);
intl;
l=int(((double)Now.GetHour()+(double)Now.GetMinute()/60.0+(double)Now.GetSecond()/3600.0)/24.0*100);
Rectangle(366,256,366+l,270);
br1.CreateSolidBrush(RGB(255,255,255));
br1);
//画表盘
intnCenterX=385;
intnCenterY=135;
CStringstrDigits;
inti,x,y;
CSizesize;
CPenPen(PS_SOLID,5,RGB(0,128,255));
CPen*pOldPen=pDC->
Pen);
Ellipse(300,50,470,220);
doubleRadians;
for(i=1;
i<
=12;
i++)
strDigits.Format("
%d"
i);
size=pDC->
GetTextExtent(strDigits,strDigits.GetLength());
Radians=(double)i*6.28/12.0;
x=nCenterX-(size.cx/2)+(int)((double)72*sin(Radians));
y=nCenterY-(size.cy/2)-(int)((double)72*cos(Radians));
TextOut(x,y,strDigits);
Radians=(double)Now.GetHour()+(double)Now.GetMinute()/60.0+(double)Now.GetSecond()/3600.0;
//画表盘主要利用三角函数进行坐标变化。
Radians*=2*3.14159/12.0;
CPenHourPen(PS_SOLID,5,RGB(233,233,15));
HourPen);
MoveTo(nCenterX,nCenterY);
LineTo(nCenterX+(int)((double)(25)*sin(Radians)),nCenterY-(int)((double)(25)*cos(Radians)));
//画时间指针
Radians=(double)Now.GetMinute()+(double)Now.GetSecond()/60.0;
Radians*=2*3.14159/60.0;
CPenMinutePen(PS_SOLID,3,RGB(0,0,255));
MinutePen);
LineTo(nCenterX+(int)((double)(40)*sin(Radians)),nCenterY-(int)((do