/**
* 实验题目
* 用二叉树实现家谱的相关运算
* 实验内容
* 编写程序,采用一棵二叉树表示一个家谱关系。要求程序具有如下功能:
* 1、文件操作功能:
* 记录输入,记录输出,清除全部文件记录和将家谱记录存盘。
* 2、家谱操作功能:
* 用括号表示法和凹入表示法输出家谱二叉树,查找某人的所有儿子,查找
* 某人所有祖先。
* 分析:
* 由于家谱是一棵树形,而不是一棵二叉树,所以在存储时要转换成二叉树的
* 形式,约定:一个父亲结点的左孩子结点表示母亲结点(父亲结点没有右孩子结点),
* 母亲结点的右子树表示他们的所有儿子,其基本结构如下图所示,该图中表示
* 有三个儿子的这种结构。
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MaxWidth 40
#define MaxSize 30
#define NAMEWIDTH 10
typedef struct fnode {
char father[NAMEWIDTH]; // 父
char wife[NAMEWIDTH]; // 母
char son[NAMEWIDTH]; // 子
}FamType; // 家结点类型
typedef struct tnode {
char name[NAMEWIDTH]; // 数据域
struct tnode *lchild; // 左孩子结点指针
struct tnode *rchild; // 右孩子结点指针
}BTree; // 家谱树类型
/* ------------------------读家谱文件并存入fam数组中--------------------------*/
static void ReadFile(FamType fam[], int &n)
{
FILE *fp;
long length; // 家谱文件长度
int index;
fp = fopen("fam.dat", "rb");
if(fp == NULL){
n = 0;
printf(" >>不能打开家谱文件\n");
return;
}
fseek(fp, 0, SEEK_END); // 家谱文件位置指针移到家谱文件末尾
length = ftell(fp); // 求家谱文件长度
rewind(fp); // 家谱文件位置指针移到家谱文件首
n = length / sizeof(FamType); // 求出家谱文件中的记录个数
for(index = 0; index < n; index++)
fread(&fam[index], sizeof(FamType), 1, fp); // 将家谱文件中的数据读到fam数组中
fclose(fp);
}
/* ------------------------清除家谱文件的全部记录--------------------------*/
static void DelAll(FamType fam[], int &n)
{
FILE *fp;
fp = fopen("fam.dat", "wb");
if(fp == NULL){
printf(" >>不能打开家谱文件\n");
return;
}
n = 0;
fclose(fp);
}
/* ------------------------将fam数组存入家谱文件--------------------------*/
static void SaveFile(FamType fam[], int n)
{
FILE *fp;
int index;
fp = fopen("fam.dat", "wb");
if(fp == NULL){
printf(" >>家谱文件不能打开\n");
return;
}
for(index = 0; index < n; index++)
fwrite(&fam[index], sizeof(FamType), 1, fp);
fclose(fp);
}
/* ------------------------添加一个记录--------------------------*/
static void InputFam(FamType fam[], int &n)
{
printf(" >>输入父亲、母亲和儿子姓名:");
scanf("%s %s %s", fam[n].father, fam[n].wife, fam[n].son);
n++;
}
/* ------------------------输出家谱文件的全部记录--------------------------*/
static void OutputFam(FamType fam[], int n)
{
int i;
if(n < 0){
printf(" >>没有任何记录\n");
return;
}
for(i = 0; i < n; i++){
printf(" >>%10s%10s%10s\n", fam[i].father, fam[i].wife, fam[i].son);
}
}
/*
>> f1 m1 f11
>> f1 m1 f12
>> f11 m11 f111
*/
/*---------------------从fam中(含n个记录)递归创建一棵二叉树----------------------*/
static BTree * CreateBTree(char *root, FamType fam[], int n)
{
BTree *bt;
BTree *p;
int i = 0;
int j;
bt = (BTree *)malloc(sizeof(BTree)); // 创建父亲结点
strcpy(bt->name, root);
bt->lchild = bt->rchild = NULL;
/*---------------查找父亲名字为root的记录----------------*/
while((i < n) && (strcmp(fam[i].father, root) != 0)){
i++;
}
if(i < n){ // 查找到父亲名字为root的记录
p = (BTree *)malloc(sizeof(BTree)); // 创建母亲结点
p->lchild = p->rchild = NULL;
strcpy(p->name, fam[i].wife);
bt->lchild = p;
for(j = 0; j < n; j++){ // 找所有儿子
if(strcmp(fam[j].father, root) == 0){ // 找到一个儿子
p->rchild = CreateBTree(fam[j].son, fam, n);
p = p->rchild;
}
}
}
return bt;
}
// 以括号表示法输出二叉树
static void DispTree1(BTree *b)
{
if(b != NULL){
printf("%s", b->name);
if((b->lchild != NULL) || (b->rchild != NULL)){
printf("(");
DispTree1(b->lchild);
if(b->rchild != NULL)
printf(",");
DispTree1(b->rchild);
printf(")");
}
}
}
// 以凹入表示法输出二叉树
static void DispTree2(BTree *bt)
{
int top = -1; // 栈指针
BTree *St[MaxSize]; // 栈指针数组
BTree *p; // 出栈结点指针
int width = 4; // 场宽度
int level[MaxSize][2]; // 二维数组
int n; // n为显示场宽,字符以右对齐显示
int i;
char type; // 结点类型
if(bt != NULL){
printf(" >>家谱凹入表示法:\n");
top++;
St[top] = bt; // 根结点入栈
level[top][0] = width;
level[top][1] = 2; // 2表示根
while(top > -1){
p = St[top]; // 退栈并凹入显示该结点的值
n = level[top][0];
switch(level[top][1])
{
case 0:
type = 'L';
break;
case 1:
type = 'R';
break;
case 2:
type = 'B';
break;
}
for(i = 1; i <= n; i++){ // 1.从最左侧开始输出n个空格
printf(" ");
}
printf("%6s(%c)", p->name, type); // 2.输出结点名字和类型
// 3.输出--
for(i = n + 1; i <= MaxWidth - 6; i += 2){
printf("--");
}
printf("\n");
top--;
if(p->rchild != NULL){ // 右子树根结点入栈
top++;
St[top] = p->rchild;
level[top][0] = n + width; // 最左侧输出空格宽度增width
level[top][1] = 1; // 1表示右
}
if(p->lchild != NULL){ // 左子树根结点入栈
top++;
St[top] = p->lchild;
level[top][0] = n + width; // 最左侧输出空格宽度增width
level[top][1] = 0; // 0表示左
}
}
}
}
// 采用先根递归算法查找name为xm的结点
static BTree * FindNode(BTree *bt, char xm[])
{
BTree *p = bt;
if(p == NULL)
return NULL;
else{
if(strcmp(p->name, xm) == 0)
return p;
else{
bt = FindNode(p->lchild, xm);
if(bt != NULL)
return bt;
else
return FindNode(p->rchild, xm);
}
}
}
// 输出某人的所有儿子
static void FindSon(BTree *bt)
{
char xm[NAMEWIDTH];
BTree *p = NULL;
printf(" >>请输入父亲姓名:");
scanf("%s", xm);
p = FindNode(bt, xm);
if(p == NULL)
printf(" >>不存在%s的父亲!\n", xm);
else{
p = p->lchild;
if(p == NULL)
printf(" >>%s没有妻子!\n", xm);
else{
p = p->rchild;
if(p == NULL)
printf(" >>%s没有儿子!\n", xm);
else{
printf(" >>%s的儿子:", xm);
while(p != NULL){
printf("%10s", p->name);
p = p->rchild;
}
printf("\n");
}
}
}
}
// 采用后根非递归遍历算法输出从根结点到s结点的路径
static int Path(BTree *bt, BTree *s)
{
BTree *St[MaxSize]; // 栈指针数组
BTree *p; // 出栈结点指针
int top = -1; // 栈指针初始化
int i;
int flag; // 访问标志 1:已访问 0:未访问
do
{
while(bt){ // 将bt的所有左结点进栈
top++;
//printf("name = %s\n", bt->name); // name = f1 name = m1
St[top] = bt;
bt = bt->lchild;
}
p = NULL; // p指向当前结点的前一个已访问的结点
flag = 1; // 设置bt的访问标记为已访问
while((top != -1) && flag){
bt = St[top]; // 取出当前的栈顶元素
if(bt->rchild == p){ // 右子树不存在或已被访问
if(bt == s){ // 当前访问的结点为要找的结点,输出路径
printf(" >>所有祖先:");
for(i = 0; i < top; i++)
printf("%s ", St[i]->name);
printf("\n");
return 1;
}else{ // 不是要查找的结点
top--;
p = bt; // p指向刚被访问的结点
}
}else{
bt = bt->rchild; // bt指向右子树
flag = 0; // 设置未被访问的标记
}
}
}while(top != -1); // 栈不空时循环
return 0; // 其它情况时返回0
}
// 输出某人的所有祖先
static void Ancestor(BTree *bt)
{
BTree *p;
char xm[NAMEWIDTH];
printf(" >>请输入姓名:");
scanf("%s", xm);
p = FindNode(bt, xm); // 查找到姓名为xm的结点指针p
if(p != NULL)
Path(bt, p);
else
printf(" >>不存在%s\n", xm);
}
int main(void)
{
BTree *bt;
FamType fam[MaxSize];
int n;
int sel;
int sel1;
ReadFile(fam, n);
do
{
printf("1:文件操作 2:家谱操作 0:退出 请选择:");
scanf("%d", &sel);
switch(sel)
{
case 1:
do
{
printf("1:输入 2:输出 9:全清 0:存盘返回 请选择:");
scanf("%d", &sel1);
switch(sel1)
{
case 1:
InputFam(fam, n);
break;
case 2:
OutputFam(fam, n);
break;
case 9:
DelAll(fam, n);
break;
case 0:
SaveFile(fam, n);
break;
}
}
while(sel1 != 0);
break;
case 2:
bt = CreateBTree("f1", fam, n);
do
{
printf("1:括号表示法 2:凹入表示法 3:找某人所有儿子 4:找某人所有祖先 0:返回 请选择:");
scanf("%d", &sel1);
switch(sel1)
{
case 1:
printf(" >>");
DispTree1(bt);
printf("\n");
break;
case 2:
DispTree2(bt);
break;
case 3:
FindSon(bt);
break;
case 4:
printf(" >>");
Ancestor(bt);
printf("\n");
break;
}
}
while(sel1 != 0);
break;
}
}while(sel != 0);
return 0;
}
测试结果:
g++ main.cpp -o test
./test
1:文件操作 2:家谱操作 0:退出 请选择:2
1:括号表示法 2:凹入表示法 3:找某人所有儿子 4:找某人所有祖先 0:返回 请选择:3
>>请输入父亲姓名:f1
>>f1的儿子: f11 f12
1:括号表示法 2:凹入表示法 3:找某人所有儿子 4:找某人所有祖先 0:返回 请选择:4
>> >>请输入姓名:f111
>>所有祖先:f1 m1 f11 m11
1:括号表示法 2:凹入表示法 3:找某人所有儿子 4:找某人所有祖先 0:返回 请选择:1
>>f1(m1(,f11(m11(,f111),f12)))
1:括号表示法 2:凹入表示法 3:找某人所有儿子 4:找某人所有祖先 0:返回 请选择:2
>>家谱凹入表示法:
f1(B)------------------------------
m1(L)--------------------------
f11(R)----------------------
m11(L)------------------
f111(R)--------------
f12(R)------------------
1:括号表示法 2:凹入表示法 3:找某人所有儿子 4:找某人所有祖先 0:返回 请选择:0