1、家谱管理系统设计实现分析课程设计报告课程名称 数据结构 课题名称 排序综合 专 业 班 级 学 号 姓 名 联系方式 指导教师 20 11 年V2 月21 日1.问题陈述 32 设计方法阐述 32.1总体规划 32.2 功能构想 42.2.1 增加成员 42.2.2修改成员资料 52.2.3 删除成员 62.2.4打开家谱 72.2.5新建家谱 82.2.6保存家谱 102.2.7查看某代信息 112.2.8按姓名查找 122.2.9按生日查找 122.2.10查看成员关系 132.2.11 按出生日期排序 142.3板块整合 152.4调试分析 193.总结 194.测试结果 201.问题陈
2、述家谱用于记录某家族历代家族成员的情况与关系。现编制一个家谱资料管理软件,实现对一个家族所有的资料进行收集整理。支持对家谱的存储、更新、查询、统计等操作。并 用计算机永久储存家族数据,方便随时调用。2.设计方法阐述2.1总体规划在动手编制程序之前,先要做好程序的规划,包括程序储存数据所用的结构,数据类型等等,只有确定了数据类型和数据结构, 才能在此基础上进行各种算法的设计和程序的编写。首先是考虑数据类型。在家谱中,家族成员是最基本的组成部分,对于家族管理中,已经不能再进行细分了,所以选定家族成员作为数据的基本类型,并在 程序中定义COperatio nFamilytree 类。其中 COper
3、atio nFamilytree 类的各种属性可以根据需要进行添加或删除,从日常生活应用的角度出发,制定了 COperatio nFamilytree 类中包含了一下属性:char nameMAX_CHARNUM; / 姓名出生日期基本资料健在否死亡日期返回pNode孩子数/返回pNode在其兄弟中的排行类的某些属性中用数字代替了某些不会改变的(1为是,0为否)。在设置日期上,为方便以后的计算与比较, 也将日期用整型数字表示 19990505表示1999年5月5日,这种表示方法只需在输入和输出上作少许的运算便可方便地与日期进行转换。 在家族关系的表示上,并没有用相关家属的姓名作为储存数据,而仅
4、仅是存储了各关系亲属的 ID,方便日后作为指针指示调用相对应的家族成员。 其中在属性pNode上,其表示的是下一个同父母的弟或妹ID,也就是说,当某家族成员有若干个子女,其 pNode仅指向第一个孩子,其余的孩子如何表示呢?可以通过第一个孩子的 pNode指示,如此类推,直到孩子的 pNode =0为止。有多少孩子就表示多少,实现这样就可以避免需在程序设计时预定父母可以拥有的孩子数, 了动态的储存数据。在选择数据结构方面,从直观来说,选择树型结构通过链表来连接数据无疑是最直观易懂的,我在一开始构思的时候也是从树型结构去想的, 但当构思到如何存储和提取数据是,便发现了问题。毫无疑问,用指针来处理
5、数据的确是方便直观,但当我要储存数据是, 便发现把指针储存进去是没有作用的, 因为当我们下一次读取数据的时候, 数据内存地址已经不同了,不在是我们上次存储数据时的地址, 也就是说指针这时已经是没有作用了。 要解决这样的问题,我们必须要在存储数据之前, 先家族树序列化,用数组 (或者其他可以用数字表示关系的方法)来存储,并且,再下一次读取数据时,再把数据按照序列号重新组成一个家族树,过程比较繁复,而且实现起来也不容易。所以我便考虑直接用数组来存储数据, 即使是在内存中也用数组来处理数据间的联系。 运用顺序表这个结构虽然不是那么直观, 但在查效率高,而且在内存中的数据可以直接读入到文件不需要进行转
6、换。所以在衡量的各个方面之后,我找数据时的算法设计比较简单容易实现, 中,文件中的数据也可以直接读入内存, 决定用数组来处理数据间的联系。2.2功能构想构想好总体规划之后,便开始设计程序中需要用到的各个功能函数,初步构想是要先 实现最基本的几项功能,其中数据操作的有:增加成员,修改成员资料,删除成员;数据存 取的有:打开家谱,新建家谱,保存家谱,另存家谱;数据查询的有:查看某代信息,按姓 名查找,按生日查找,查看成员关系,按出生日期排序等等。221增加成员这项功能做得不够理想,在规划时没有把成员以配偶的形式增加,而只能以子女的形式增加。对应的函数代码如下:void COperati onF a
7、milytree:Add(Pers on pare nt, Pers on addNode)/本函数把addNode结点加入到其父结点 pare nt下addNode-child=addNode-sibling=0; /把欲加入的结点所有指针域置空addNode-pare nt=pare nt; / 因 addNode 欲加为 pare nt 的孩子,故addNode结点的父指针域应指向 pare nt修改成员这项功能实现起来比较简单, 找到要修改成员的名字, 再输入新修改的值, 整个函数没有什么需要运用算法的地方, 但如果想真正写好这个函数,则需要考虑相当多的细节,譬如各个输入项目的错误处理
8、等等,要非常全面地考虑各项细节。函数代码如下:void CFamilytreeDlg:O nM odify()II TODO: Add your comma nd han dler code hereif(operFamilytree.GetRoot()=0)return;CModifylnfoDlg dlg;HTREEITEM hltem;hItem=m_peTree.GetSelectedItem();dlg.m_ newn ame=m_peTree.GetltemText(hltem);Pers on on eself=0;char old nameMAX_CHARNUM;strcpy(
9、old name,dlg.m _newn ame);operFamilytree.Fi nd(operFamilytree.GetRoot(),o neself,old name);if(dlg.DoModal()=IDCANCEL)return;UpdateData(FALSE);Pers on n ewValue=new Pers onN ode;strcpy(newValue-info.name,dlg.m_newname); II 判断家谱中是否已有用户给定的新名字if(strcmp( newValue-info.n ame,old name)=O); /用户不修改姓名elsePers
10、 on p=0;operFamilytree.Fi nd(operFamilytree.GetRoot(),p, newValue-i nfo. name);/查找家谱中有没有此人if(p!=O)AfxMessageBox(家谱中已有此人!);delete n ewValue;return;strcpy( newValue-i nfo.addr,dlg.m _n ewaddr);n ewValue-i nfo.marry=dlg.m_marry;n ewValue-i nfo.live=dlg.m_live;n ewValue-i nfo.birthday.day=dlg.m_birthday
11、_day;n ewValue-i nfo.birthday.m on th=dlg.m_birthday_mo nth;n ewValue-in fo.birthday.year=dlg.m_birthday_year;if(!newValue-info.live) / 如若过世,则应有死亡日期n ewValue-in fo.deathday.day=dlg.m_deathday_day;n ewValue-i nfo.deathday.m on th=dlg.m_deathday_mo nth;n ewValue-in fo.deathday.year=dlg.m_deathday_year
12、;if(!operFamilytree.lsDateValid( newValue-i nfo.deathday)AfxMessageBox(此人信息中死亡日期不合实际 );delete n ewValue;return;if(operFamilytree.CompareDate( newValue-i nfo.deathday, newValue-i nfo.bi rthday)=-1)AfxMessageBox(此人死亡日期不可能比其出生日期早 r);return;operFamilytree.Modify(o neself, newValue);RefreshTree();Refresh
13、List();IsFamilytreeModified=true; / 置家谱修改标记为真delete n ewValue;2.2.3删除成员用数组来储存数据,最麻烦的就是删除数组元素了,在这个程序中,删除数组不但意味着要重新排列各成员, 还要重新更新各成员的关系, 所以我个人认为在这个程序中, 删除成员函数可以说是一个难点。 通过分析,发现删除成员的情况就只有两种, 只要针对这两种情况处理好删除,就可以完成成员删除这个功能。1,删除的成员是出于家族中最底层的, 也就是删除该成员不会牵连其他成员, 但这也 需要处理好其父母的孩子数。2,删除的成员还有子孙,则需要连带所有子孙都要删除出家谱。再是
14、简单删除了一个人,而是若干个,通过递归调用,可以统计出需要删除的数目删除函数的相关代码如下:第一个孩子位置for(;p-sibli ng!=rootNode;p=p-sibli ng)删除以 rootNode-child/删除rootNode 结点。如PostOrderTraverse(rootNode-child,DestroyNode); 为根结点的所有结点if(rootNode=T)果rootNode为根结点,则删除根结点 TDestroyNode(T);elseDestroyNode(rootNode);224打开家谱打开家谱函数的相关代码如下:int COperati onF ami
15、lytree:ReadNode(FILE *fp, Pers on &T,char* pare ntn ame) /本函数从文件fp中读取信息到结点 T中,并读取结点的父亲名字到字符数组 parentname中/分别读取结点值,为:姓名,出生日期(年,月,日),婚否,地址,健在否,(如过世,还有死亡日 期)fscan f(fp,%s%d%d%d%d%s%d,T-i nfo.n ame,&T-i nfo.birthday.year,&T-i nfo.birthday.mo nth,&T-i nfo.birthday.day, &T-i nfo.marry,T-i nfo.addr, &T-i n
16、fo.l ive);if(T-in fo.live=0)fsca nf(fp,%d%d%d,& T-i nfo.deathday.year, &T-i nfo.deathday.mo nth,&T-in fo.deathday.day);fscan f(fp,%s,pare ntn ame);if(!lsDateValid(T-info.birthday) / 出生日期合法性检查return FILE_DATA_NOT_PRACTICAL;if(T-info.live=0) /若过世,死亡日期合法性检查 if (CompareDate(T-in fo.birthday,T-in fo.deat
17、hday)!=-1)return FILE_DATA_NOT_PRACTICAL;if(!lsDateValid(T-i nfo.deathday)return FILE_DATA_NOT_PRACTICAL;return OK;2.2.5新建家谱新建家谱函数的相关代码如下 :void COperatio nF amilytree:NewFamilytree()/本函数新建一空家谱DestroyFamilytree(); / 删除原有家谱T=0;int COperati onF amilytree:CreateFamilytree(CStri ng file name) /本函数建立一新家谱D
18、estroyFamilytree();/建立一新豕谱之前,清空原有家谱FILE* fp;if(fp=fope n(file name,r)=0)/打开文件file namereturn READ_FILE_ERROR;T=new Pers onN ode;/定义根结点if(!T)return NOT_ENOUGH_MEMORY;T-child=0;T-sibli ng=0;T-pare nt=O;Pers on pare ntT, temp;/定义两个临时结点char pare ntn ameMAX_CHARNUM;/定义一个临时字符串数组/读取根结点值,(姓名,出生日期(年,月,日),婚否,
19、地址,健在否,(如过世,还有死 亡日期)int result;result=ReadNode(fp,T,pare ntn ame);if(result=FILE_DATA_NOT_PRACTICAL)delete T; /若不合法,删除申请的堆空间T=0;return result;/根结点名字与其父亲名字相/申请一结点/申请失败/释放申请空间if(strcmp(T-i nfo. name,pare ntn ame)=0) 同,说明为空树delete T;T=0;return PEDIGREE_EMPTY;temp=new Pers onN ode;if(!temp)DestroyFamily
20、tree();return NOT_ENOUGH_MEMORY; result=ReadNode(fp,temp,pare ntn ame);while(strcmp(temp-i nfo. name,pare ntn ame)&strcmp(temp-i nfo.n ame,e nd) /读取信息结束的条件是两个人的名字同为 endif(result=FILE_DATA_NOT_PRACTICAL) / 若数据不合法,释放已申请空间,然后返回delete temp;DestroyFamilytree();return result;pare ntT=O;Find(T,parentT,pare
21、ntname); / 找至U parentname 所在结点pare ntTif(parentT) / 女口果 parentT 存在,说明 parentname 在家谱中/ 并且 parentname 为temp 的父亲 int cmp;cmp=CompareDate(temp-in fo.birthday,pare ntT-in fo.birthday);if(cmpchild,Visit); PreOrderTraverse(fp,T-sibli ng,Visit);void SaveNode(FILE *fp, Person &pNode)/本函数向文件fp中存取一结点pNodechar
22、 ch=n: if(pNode)fprin tf(fp,%s %d %d”,pNode-info.n ame,pNode-in fo.birthday.year,pNode-i nfo.birthday.mo nth,pNode-i nfo.birthday.day,pNode-i nfo.marry, pNode-in fo.addr,pNode-info.li ve);%dif(pNode-info.live=0)fprin tf(fp, %d %d”,pNode-in fo.deathday.year,pNode-in fo.deathday. mon th,pNode-in fo.de
23、athday.day);if(pNode-pare nt)fprin tf(fp, %s ,pNode-pare nt-info.n ame);elsefprin tf(fp, %s,-1);fprintf(fp, %c,ch);227查看某代信息查看某代信息函数的相关代码如下:int COperati onF amilytree:l nGen erati on Pos(Pers on pNode) /本函数返回pNode结点在第几代int pos=1;Pers on p;p=pNode-pare nt;for(;p!=0;p=p-pare nt)pos+;return pos;2.2.8按姓
24、名查找按姓名查找函数的相关代码如下:void COperatio nF amilytree:F in d(Pers on& T,Pers on& Tn ame,char* n ame)/本函数以T为根结点开始,搜索结点信息中名字等于 name的结点if(T) /如果T存在/T结点姓名和name相同,把T结/对T的兄弟递归搜索/对T的孩子递归搜索if(strcmp(T-i nfo. name, name)=0) 点指针传给TnameTn ame=T;elseFi nd(T-sibli ng,T name, name); Fin d(T-child,T name ,n ame);2.2.9按生日查
25、找按生日查找函数的相关代码如下:void COperati onF amilytree:F in d(Pers on &T, Person*& Tn ame,i nt mon th, int day)/本函数以T为根结点开始,搜索结点信息中生日等于 month,day的结点,/并把所有符合条件的结点指针值存入以 Tname为起始地址的地址数组中if(T) /如果T存在if(T-in fo.birthday. mon th=month&T-in fo.birthday.day=day)/T结点生日与所给相同,把T结点指针传给Tname,同时Tname指针前进 Pers on temp;temp=
26、new Pers onN ode;temp=T;if(temp-in fo.birthday. mon th=month&temp-in fo.birthday.day=day)*Tn ame=temp;Tn ame+;temp=NULL;Find(T-child,Tname,month,day); / 对 T 的孩子递归搜索Find(T-sibling,Tname,month,day); / 对 T 的兄弟递归搜索2.2.10查看成员关系查看成员关系函数的相关代码如下:void CFamilytreeDlg:O nF amilytreeRelatio ns()/ TODO: Add your
27、 comma nd han dler code hereCRelatio nsDIg dlg;if(dlg.DoModal()=IDCANCEL)return;UpdateData(FALSE);int pos1,pos2;Pers on on eself=0;char name1MAX_CHARNUM, name2MAX_CHARNUM;strcpy (n ame1,dlg.m_first name);operFamilytree.Fi nd(operFamilytree.GetRoot(),o neself, name1); if(on eself)pos1=operFamilytree .InGen erati on Pos( on eself);elseAfxMessageBox(本家谱中找不到+CString(name1)+!); return;Pers on p,q;CString generation;gen erati on+=on eself- info.n ame;gen eratio