<think>我们使用C语言来实现这个《红楼梦》人物分析系统。由于系统功能较为复杂,我们将分模块实现。主要功能模块:1.文件读取:读取小说文本和人物别名配置文件2.人物统计:统计每个人物的出场次数、出现的篇幅(章节)以及共同出现的情况(用于计算亲密度)3.数据结构:使用结构体存储人物信息,并设计合适的数据结构来存储共同出现信息(如二维数组)4.排序功能:按照不同的要求排序5.查询功能:按用户输入查询特定人物信息6.界面:提供简单的命令行交互界面7.数据保存:可选,将结果保存到文件考虑到人物别名,我们需要一个配置文件(如alias.txt)来存储别名映射。格式为每行一个真实人物,后面跟着别名,用空格隔开,例如:贾宝玉宝玉宝二爷林黛玉黛玉林妹妹步骤:1.设计数据结构:-人物结构体(包括编号、姓名、出场次数、出现的章节数组、亲密度数组等)-由于亲密度是与其他人物共同出现的频率,我们可以用一个二维数组(矩阵)来存储任意两个人物的共同出现次数。2.读取别名配置文件,构建一个映射表(从别名到真实姓名的映射)。3.读取小说文本,按章分割(红楼梦共120回,我们按回分割)。4.对于每一章:扫描文本,根据别名表匹配人物,记录每个人物在该章是否出现(注意:只记录一次,即同一章出现多次只算一次)。更新人物的出场次数和出现的章节。同时,对于该章中出现的所有人物,两两之间在共同出现矩阵中增加一次共同出现。5.构建人物数组,并计算相关指标:出场次数:times篇幅跨度:出现的最小章节和最大章节之间的差值(或者出现的不同章节数)与其他人的亲密度:可以用共同出现的次数,或者用共同出现的次数除以该人物的总出场次数(需要归一化处理?)6.实现排序函数:按出场次数、篇幅跨度、与指定人物的亲密度等排序。7.实现查询功能:输入人物姓名,输出其出场次数、篇幅跨度、以及与其他人的亲密度(可以只输出前N个最亲密的人)。8.实现保存结果到文件的功能。9.异常处理:文件读取失败时,提示用户重新输入路径。由于时间限制,我们在此实现核心功能。以下是代码框架,省略了部分细节。注意:本程序假设小说文本文件是按章分割的,每章标题为“第x回”的格式。由于代码较长,我们将分多个函数实现。我们定义如下结构体:*/#include<stdio.h>#include<stdlib.h>#include<string.h>#include<ctype.h>#include<time.h>#defineMAX_CHAPTER120//红楼梦120回#defineMAX_NAME_LEN20#defineMAX_ROLES100//最大人物数#defineMAX_ALIASES_PER_ROLE5//每个人物的别名数量#defineMAX_ALIAS_LEN20//别名映射结构typedefstruct{charreal_name[MAX_NAME_LEN];charaliases[MAX_ALIASES_PER_ROLE][MAX_ALIAS_LEN];intalias_count;}AliasMap;//人物信息结构体typedefstructRole{charname[MAX_NAME_LEN];//姓名inttimes;//出场次数(统计所有章节)intchapters[MAX_CHAPTER];//记录是否在某一章出现,0表示未出现,1表示出现(也可记录次数,但题目要求按篇幅跨度,所以用0/1即可)intspan;//篇幅跨度(出现的不同章节数,在统计时计算)//亲密度:与其他角色的共同出现次数intintimacy[MAX_ROLES];//存储与每个人物的共同出现次数intindex;//在全局角色数组中的索引}Role;//全局变量Roleroles[MAX_ROLES];//存储所有角色introle_count=0;//实际角色数量AliasMapalias_map[MAX_ROLES];//别名映射表intalias_map_count=0;//别名映射数量char*chapters_content[MAX_CHAPTER];//存储每章内容的指针数组intchapter_count=0;//实际章节数//从别名配置文件中读取别名voidread_alias_config(constchar*filename){FILE*fp=fopen(filename,"r");if(fp==NULL){printf("无法打开别名配置文件%s\n",filename);exit(1);}charline[200];while(fgets(line,sizeof(line),fp)){//去掉换行符line[strcspn(line,"\n")]=0;if(strlen(line)==0)continue;char*token=strtok(line,"");if(token==NULL)continue;//第一个token是真实姓名strncpy(alias_map[alias_map_count].real_name,token,MAX_NAME_LEN);alias_map[alias_map_count].alias_count=0;token=strtok(NULL,"");while(token!=NULL&&alias_map[alias_map_count].alias_count< MAX_ALIASES_PER_ROLE){strncpy(alias_map[alias_map_count].aliases[alias_map[alias_map_count].alias_count],token,MAX_ALIAS_LEN);alias_map[alias_map_count].alias_count++;token=strtok(NULL,"");}alias_map_count++;}fclose(fp);}//根据别名获取真实姓名char*get_real_name(char*name){for(inti=0;i< alias_map_count;i++){//检查是否是真实姓名if(strcmp(alias_map[i].real_name,name)==0){returnalias_map[i].real_name;}//检查是否是别名for(intj=0;j< alias_map[i].alias_count;j++){if(strcmp(alias_map[i].aliases[j],name)==0){returnalias_map[i].real_name;}}}//如果没有找到,返回原始名字(可能是一个新人物,但题目要求自行决定主要人物,所以我们只统计配置文件中有的人物)returnNULL;}//在角色数组中查找角色,不存在则添加Role*find_or_add_role(char*name){char*real_name=get_real_name(name);if(real_name==NULL){returnNULL;//不在别名映射表中,忽略}//在角色数组中查找for(inti=0;i< role_count;i++){if(strcmp(roles[i].name,real_name)==0){return&roles[i];}}//没找到,添加新角色if(role_count< MAX_ROLES){strncpy(roles[role_count].name,real_name,MAX_NAME_LEN);roles[role_count].times=0;for(inti=0;i< MAX_CHAPTER;i++){roles[role_count].chapters[i]=0;}roles[role_count].span=0;//初始化亲密度数组for(intj=0;j< MAX_ROLES;j++){roles[role_count].intimacy[j]=0;}roles[role_count].index=role_count;role_count++;return&roles[role_count-1];}else{printf("超出最大角色数限制\n");returnNULL;}}//读取小说文本并按章分割voidread_novel_text(constchar*filename){FILE*fp=fopen(filename,"r");if(fp==NULL){printf("无法打开小说文本%s\n",filename);exit(1);}//计算文件大小fseek(fp,0,SEEK_END);longsize=ftell(fp);fseek(fp,0,SEEK_SET);char*content=(char*)malloc(size+1);fread(content,1,size,fp);content[size]='\0';fclose(fp);//按章分割:使用"第"和"回"作为分章标识,但要注意避免分错//红楼梦每章标题格式为:第x回标题char*chapter_start=content;char*p=content;intin_chapter=0;intchapter_index=0;while(*p){if(*p=='第'&&(*(p+1)>='0'&&*(p+1)<='9')){//找到章节开始if(in_chapter){//上一章结束char*end=p;//章节内容长度longlength=end-chapter_start;if(length>0){chapters_content[chapter_index]=(char*)malloc(length+1);strncpy(chapters_content[chapter_index],chapter_start,length);chapters_content[chapter_index][length]='\0';chapter_index++;}}chapter_start=p;in_chapter=1;}p++;}//处理最后一章if(in_chapter){longlength=p-chapter_start;chapters_content[chapter_index]=(char*)malloc(length+1);strncpy(chapters_content[chapter_index],chapter_start,length);chapters_content[chapter_index][length]='\0';chapter_index++;}chapter_count=chapter_index;}//在某一章中统计人物voidprocess_chapter(intchapter_index,char*content){//临时数组,记录本章出现的人物(避免重复)intappeared[MAX_ROLES]={0};//初始为0//遍历角色列表,检查每个角色的所有别名是否在本章出现for(inti=0;i< role_count;i++){//检查真实姓名和每个别名char*name=roles[i].name;if(strstr(content,name)!=NULL){appeared[i]=1;}else{//检查别名for(intk=0;k< alias_map_count;k++){if(strcmp(alias_map[k].real_name,name)==0){for(intj=0;j< alias_map[k].alias_count;j++){if(strstr(content,alias_map[k].aliases[j])!=NULL){appeared[i]=1;break;}}break;//跳出别名映射的循环}}}}//更新角色信息:出现次数和章节标记for(inti=0;i< role_count;i++){if(appeared[i]){roles[i].times++;if(roles[i].chapters[chapter_index]==0){roles[i].chapters[chapter_index]=1;}//共同出现:更新两两亲密度for(intj=0;j< role_count;j++){if(appeared[j]&&j!=i){roles[i].intimacy[j]++;}}}}}//计算每个角色的篇幅跨度(不同章节数)voidcalculate_span(){for(inti=0;i< role_count;i++){intcount=0;for(intch=0;ch< chapter_count;ch++){if(roles[i].chapters[ch]){count++;}}roles[i].span=count;}}//排序函数:按出场次数降序intcompare_times(constvoid*a,constvoid*b){Role*roleA=(Role*)a;Role*roleB=(Role*)b;returnroleB->times-roleA->times;}//排序函数:按篇幅跨度降序intcompare_span(constvoid*a,constvoid*b){Role*roleA=(Role*)a;Role*roleB=(Role*)b;returnroleB->span-roleA->span;}//按与特定人物的亲密度降序排序voidsort_by_intimacy_to_role(Role*role,Role*sorted_roles){//复制一份角色数组memcpy(sorted_roles,roles,role_count*sizeof(Role));//用亲密度作为比较标准进行排序for(inti=0;i< role_count-1;i++){for(intj=0;j< role_count-1-i;j++){if(sorted_roles[j].intimacy[role->index]< sorted_roles[j+1].intimacy[role->index]){Roletemp=sorted_roles[j];sorted_roles[j]=sorted_roles[j+1];sorted_roles[j+1]=temp;}}}}//查询并显示某个角色的信息voidquery_role(char*name){Role*role=NULL;for(inti=0;i< role_count;i++){if(strcmp(roles[i].name,name)==0){role=&roles[i];break;}}if(role==NULL){printf("没有找到名为%s的角色。\n",name);return;}printf("角色:%s\n",role->name);printf("出场次数:%d\n",role->times);printf("篇幅跨度(出现章节数):%d\n",role->span);printf("亲密度最高的前10个人物:\n");//按亲密度排序Rolesorted_roles[MAX_ROLES];sort_by_intimacy_to_role(role,sorted_roles);//打印前10个(不包括自己)intcount=0;for(inti=0;i< role_count&&count<11;i++){//跳过自己if(strcmp(sorted_roles[i].name,role->name)==0)continue;if(count==10)break;printf("%s:%d\n",sorted_roles[i].name,sorted_roles[i].intimacy[role->index]);count++;}}//主程序intmain(){//初始化别名映射read_alias_config("alias.txt");//读取小说文本read_novel_text("hongloumeng.txt");//初始化角色:从别名映射中获取所有真实姓名for(inti=0;i< alias_map_count;i++){find_or_add_role(alias_map[i].real_name);}//按章处理for(inti=0;i< chapter_count;i++){process_chapter(i,chapters_content[i]);}//计算篇幅跨度calculate_span();//主菜单while(1){printf("\n《红楼梦》人物分析系统\n");printf("1.按出场次数排序\n");printf("2.按篇幅跨度排序\n");printf("3.查询特定人物\n");printf("4.退出\n");printf("请选择操作:");intchoice;scanf("%d",&choice);getchar();//吃掉回车if(choice==1){//按出场次数排序qsort(roles,role_count,sizeof(Role),compare_times);printf("按出场次数排序:\n");for(inti=0;i< role_count;i++){printf("%s:%d\n",roles[i].name,roles[i].times);}}elseif(choice==2){//按篇幅跨度排序qsort(roles,role_count,sizeof(Role),compare_span);printf("按篇幅跨度排序:\n");for(inti=0;i< role_count;i++){printf("%s:%d\n",roles[i].name,roles[i].span);}}elseif(choice==3){charname[MAX_NAME_LEN];printf("请输入要查询的人物姓名:");fgets(name,MAX_NAME_LEN,stdin);name[strcspn(name,"\n")]=0;//去掉换行符query_role(name);}elseif(choice==4){break;}else{printf("无效选择\n");}}//释放章节内容内存for(inti=0;i< chapter_count;i++){free(chapters_content[i]);}return0;}/*注意:1.这个程序是简化版,实际应用中需要考虑更多细节,比如:-别名的匹配可能不准确(比如“宝玉”可能出现在描述其他宝物的时候)-处理效率:如果文本很大,逐章处理时字符串搜索可能较慢,可以优化(如使用AC自动机等)2.由于红楼梦人物众多,别名也众多,需要仔细配置别名文件。3.选做要求:异常处理和保存功能。我们这里没有完全实现,但给出了框架。保存功能可以添加在查询结果后,将结果写入文件。4.增加异常处理:在文件读取失败时,提示用户重新输入文件路径。可以修改read_novel_text函数和read_alias_config函数,使其具有重试功能。我们将异常处理加入:以别名配置文件读取为例,修改read_alias_config,如果打开失败,则允许用户重新输入文件名。代码示例(修改read_alias_config):voidread_alias_config(){charfilename[100];printf("请输入别名配置文件路径:");scanf("%s",filename);while(1){FILE*fp=fopen(filename,"r");if(fp){...//正常读取fclose(fp);break;}else{printf("无法打开%s,请重新输入文件名:",filename);scanf("%s",filename);}}}同样的方式修改读取小说文本的函数。保存功能:在显示结果后,询问用户是否保存,然后输入文件名,将当前屏幕内容保存到文件。由于篇幅限制,不在此处展开。以上程序是一个基本实现,可在此基础上完善。§§相关问题§§:1.在统计人物出现时,如何避免误匹配(比如“宝玉”出现在非人名的语境中)?2.当角色数量较多时,如何优化亲密度矩阵的内存占用?3.除了共同出现次数,还有哪些方法可以计算人物亲密度?4.如何设计图形用户界面(GUI)来展示人物关系图?</think>
# 《红楼梦》人物分析系统
下面是一个完整的C语言实现,满足系统要求的功能:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/stat.h>
#define MAX_ROLES 50 // 最大人物数量
#define MAX_NAME_LEN 20 // 姓名最大长度
#define MAX_TEXT_LEN 10000 // 文本内容最大长度
#define MAX_CHAPTERS 120 // 最大章节数
// 人物信息结构体
struct Role {
char ID[10]; // 人物编号
char name[MAX_NAME_LEN]; // 姓名
int times; // 出场次数
int first_appearance; // 首次出场章节
int last_appearance; // 最后一次出场章节
int cooccurrence[MAX_ROLES]; // 与其他人物共同出现的次数
};
// 系统全局变量
struct Role roles[MAX_ROLES]; // 人物数组
int role_count = 0; // 实际人物数量
char text_content[MAX_TEXT_LEN]; // 文本内容缓存
char current_file[256] = ""; // 当前文件路径
// 函数声明
void init_roles();
void load_role_aliases();
char* get_real_name(char* name);
int find_role_index(char* name);
void display_menu();
void load_novel();
void analyze_novel();
void display_all_roles();
void display_single_role();
void sort_by_times();
void sort_by_span();
void sort_by_intimacy();
void save_results();
void extract_chapter(char* content, int chapter_num);
void process_chapter_content(char* content, int chapter_num);
// 主函数
int main() {
init_roles();
load_role_aliases();
while (1) {
display_menu();
int choice;
printf("请输入您的选择: ");
scanf("%d", &choice);
switch (choice) {
case 1:
load_novel();
break;
case 2:
analyze_novel();
break;
case 3:
display_all_roles();
break;
case 4:
display_single_role();
break;
case 5:
sort_by_times();
break;
case 6:
sort_by_span();