DNA序列还原
某生物学文件内容为多条长度不等DNA序列,为书写方便其中有些片段用小括号括起并跟一个int数字,表示该片段需要重复多次。如“(AGG)3”表示“AGG”片段需要重复3次,请按要求将源文件还原后写入新的文件中。注意:括号内的DNA片段长度不定长,重复次数可能是个多位数。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX 5
#define LEN 10000
char source[LEN]; // 原DNA序列
// 利用数组模拟栈,实现左括号索引存储
int stack[MAX];
int top = 0; // 栈顶指针
// 添加左括号索引
void addLeft(int ind) {
top++; // 栈顶指针移动
stack[top] = ind; // 存入
}
// 弹出左括号索引
int indLeft() {
top--; // 移动栈顶指针
return stack[top+1]; // 返回索引
}
// 解析重复次数
int frequency(int indRight, const char *source) {
int cnt = 0;
int i = 1;
while (isdigit(source[indRight + i])) {
cnt = cnt * 10 + (source[indRight + i] - '0');
i++;
}
return cnt;
}
// 解析重复次数的位数
int numlen(int cnt) {
int len = 0;
while (cnt) {
len++;
cnt /= 10;
}
return len;
}
// 函数用于还原DNA序列
void expand() {
int len = strlen(source); // 当前DNA序列的长度
char tmp[100]; // 临时数组存放重复单元
char expandSource[100000]; // 用于存放每次还原后的DNA序列
int i = 0; // 遍历指针
while (i < len) {
// 如果找到左括号就加入栈
if (source[i] == '(') {
addLeft(i); // 记录左括号索引
}
// 如果找到右括号,那么一定是和栈顶匹配的
// 那么此时就可以将这对括号内的重复单元进行复制
// 复制完后进行变量维护
else if (source[i] == ')') {
int indL = indLeft(); // 左括号索引
int indR = i; // 右括号索引
int cnt = frequency(indR, source); // 解析重复次数
int l = numlen(cnt); // 求重复次数的位数
int k = 0;
// 拷贝重复单元
for (int j = indL + 1; j < indR; j++) {
tmp[k++] = source[j];
}
tmp[k] = '\0'; // 添加结束标记
// 一次还原过程
k = 0;
// 拷贝左括号左侧
for (int j = 0; j < indL; j++) {
expandSource[k++] = source[j];
}
// 拷贝重复单元
for (int j = 0; j < cnt; j++) {
for (int p = 0; p < strlen(tmp); p++) {
expandSource[k++] = tmp[p];
}
}
// 拷贝右括号右侧
for (int j = indR + l + 1; j < len; j++) {
expandSource[k++] = source[j];
}
// 补上结束标记
expandSource[k] = '\0';
// 更新len,source,i
len = strlen(expandSource);
for (int j = 0; j < len; j++) {
source[j] = expandSource[j];
}
source[len] = '\0';
i = indL;
}
i++;
}
if (i == len) return;
}
int main() {
FILE *fp1 = fopen("source.txt", "r");
FILE *target = fopen("target.txt", "w");
if (target == NULL) {
printf("文件打开失败\n");
return 1;
}
while (fgets(source, LEN, fp1)) {
expand();
fprintf(target, "%s", source);
}
fclose(fp1);
fclose(target);
return 0;
}
【运行结果】
【小结】此题主要思想是解码过程。原题样例较弱,此处额外考虑括号嵌套括号(存在运算优先级的情况),为实现该功能,采用栈结构来记录左括号的索引值。当第一次匹配到右括号时,则弹出栈顶元素,此时二者中间部分即为一个重复单元。在处理多位重复次数,需要采用位权求和的思想实现字符串到数值的转换。
【额外思考】此处读者如果学过 dfs 算法可以尝试使用 dfs 算法来完成(考虑括号嵌套的情况)本题。以此加深对于栈数据结构,实现c语言函数调用的系统栈的理解。
学生成绩管理系统
- 输入n个学生的学号,姓名,高等数学、大学物理、英语和C语言四门课程的成绩;
- 计算每门课程平均分数并输出;
- 按照某类课程的成绩高低排序;
- 找出每门课程最高分的同学学号和姓名(如果最高分有多名同学,都输出)。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX_LEN 15 // 名字的最大长度
/*
1 zhao 88 87 86 85
2 qian 79 85 96 88
3 sun 92 88 92 83
4 li 90 91 80 88
*/
// 定义学生结构体
struct Student {
int id; // 学号
char name[MAX_LEN]; // 姓名
float math; // 高等数学
float physics; // 大学物理
float english; // 英语
float cLanguage; // C语言
struct Student* next; // 下一结点
};
// 头、尾结点
Student* head;
Student* rear;
// 链表初始化
void init() {
// 初始化头、尾结点
head = (Student*)malloc(sizeof(Student));
rear = head;
rear->next = NULL; // 尾结点指向空
}
// 创建新学生结点
Student* newStudent(int id, const char* name, float math, float physics, float english, float cLanguage) {
Student* stu = (Student*)malloc(sizeof(Student)); // 动态开辟内存空间
// 为新学生结点赋值
stu->id = id;
strcpy(stu->name, name); // 利用strcpy()实现名字拷贝
stu->math = math;
stu->physics = physics;
stu->english = english;
stu->cLanguage = cLanguage;
stu->next = NULL;
return stu; // 返回新学生结点
}
// 插入学生结点到链表 ——尾插法
void addStudent(Student* stu) {
rear->next = stu; // 尾指针指向新学生结点
rear = stu; // 更新尾指针
}
// 计算每门课的平均分数并输出
void calculateAverage() {
float sum[4] = {0.0, 0.0, 0.0, 0.0}; // 初始化每门科目学生成绩的总和
int n = 0; // 记录学生个数
Student* p = head->next;
// p指针遍历所有学生成绩,更新sum数组
while (p != NULL) {
float math = p->math;
sum[0] += math;
sum[1] += p->physics;
sum[2] += p->english;
sum[3] += p->cLanguage;
p = p->next; // 更新p指针,指向下一结点
n++; // 学生数增加
}
// 打印输出平均分数,采用%.2f保留两位小数
printf("the average of math:%.2f\n",sum[0]/n);
printf("the average of physics:%.2f\n",sum[1]/n);
printf("the average of english:%.2f\n",sum[2]/n);
printf("the average of C language:%.2f\n",sum[3]/n);
}
// 比较器,若前者大于后者,返回1;若前者小于后者返回-1;若二者相等,返回0。
int compareMath(Student* a, Student* b) {
if (a->math > b->math) return 1;
else if (a->math < b->math) return -1;
else return 0;
}
int comparePhysics(Student* a, Student* b) {
if (a->physics > b->physics) return 1;
else if (a->physics < b->physics) return -1;
else return 0;
}
int compareEnglish(Student* a, Student* b) {
if (a->english > b->english) return 1;
else if (a->english < b->english) return -1;
else return 0;
}
int compareCLanguage(Student* a, Student* b) {
if (a->cLanguage > b->cLanguage) return 1;
else if (a->cLanguage < b->cLanguage) return -1;
else return 0;
}
// 交换结点——简单值交换,不改变链表的连接
void swap(Student* a, Student* b) {
Student* tmp = (Student*)malloc(sizeof(Student));
tmp->id = a->id;
strcpy(tmp->name, a->name);
tmp->math = a->math;
tmp->physics = a->physics;
tmp->english = a->english;
tmp->cLanguage = a->cLanguage;
a->id = b->id;
strcpy(a->name, b->name);
a->math = b->math;
a->physics = b->physics;
a->english = b->english;
a->cLanguage = b->cLanguage;
b->id = tmp->id;
strcpy(b->name, tmp->name);
b->math = tmp->math;
b->physics = tmp->physics;
b->english = tmp->english;
b->cLanguage = tmp->cLanguage;
}
// 根据某类课程的成绩高低排序
void sort(int (*compare)(Student*, Student*)) { // 通过传入函数指针作为参数,实现模拟多态
int swapped = 0; // 当前排序是否发生交换 ——用于循环控制
Student* p = head;
Student* lptr = NULL; // 用于标记上一轮排序的结束位置(优化)
// 特判
if (head->next == NULL || head->next->next == NULL) {
printf("The list is empty or only has one record!");
return; // 结束
}
// 冒泡排序
do {
swapped = 0;
p = head->next;
while (p->next != lptr) {
// 如果当前结点比其后继结点小,则交换
if (compare(p, p->next) < 0) {
swap(p,p->next);
swapped = 1; // 标记
}
p = p->next;
}
lptr = p; // 记录已排序部分的首结点
} while (swapped);
}
// 找出每门课最高分的同学学号和姓名(如果最高分有多名同学,都输出)
void findHighest() {
float max[4] = {0.0, 0.0, 0.0, 0.0}; // 初始化最高分数组
Student* p = head->next;
// 遍历得到每门课的最高分
while (p != NULL) {
if (p->math > max[0]) max[0] = p->math;
if (p->physics > max[1]) max[1] = p->physics;
if (p->english > max[2]) max[2] = p->english;
if (p->cLanguage > max[3]) max[3] = p->cLanguage;
p = p->next;
}
// p指针复位,重新遍历输出
p = head->next;
printf("Students with highest math grades:\n");
while (p != NULL) {
if (p->math == max[0]) printf("\tid:%d,name:%s\n", p->id, p->name);
p = p->next;
}
p = head->next;
printf("Students with highest physics grades:\n");
while (p != NULL) {
if (p->physics == max[1]) printf("\tid:%d,name:%s\n", p->id, p->name);
p = p->next;
}
p = head->next;
printf("Students with highest english grades:\n");
while (p != NULL) {
if (p->english == max[2]) printf("\tid:%d,name:%s\n", p->id, p->name);
p = p->next;
}
p = head->next;
printf("Students with highest C language grades:\n");
while (p != NULL) {
if (p->cLanguage == max[3]) printf("\tid:%d,name:%s\n", p->id, p->name);
p = p->next;
}
}
// 打印链表
void printList() {
Student* p = head->next;
while (p != NULL) {
printf("id:%d\tname:%s\tmath:%.2f\tphysics:%.2f\tenglish:%.2f\tC language:%.2f\n", p->id, p->name, p->math, p->physics, p->english, p->cLanguage);
p = p->next;
}
}
int main() {
int n; // 学生数
printf("please input the number of students:");
scanf("%d",&n);
// 链表初始化
init();
printf("please input the informations of these students:\n");
while (n--) { // 输入n个学生的信息
int id, math, physics, english, cLanguage;
char name[MAX_LEN];
scanf("%d %s %d %d %d %d", &id, name, &math, &physics, &english, &cLanguage);
addStudent(newStudent(id, name, math, physics, english, cLanguage));
}
printList();
// 测试计算每门课平均分数并输出
calculateAverage();
// 测试按照某类课程的成绩高低排序
printf("Sort using math as keyword:\n");
sort(compareMath);
printList();
printf("Sort using physics as keyword:\n");
sort(comparePhysics);
printList();
printf("Sort using english as keyword:\n");
sort(compareEnglish);
printList();
printf("Sort using C language as keyword:\n");
sort(compareCLanguage);
printList();
// 测试找出每门课最高分的同学学号和姓名
findHighest();
return 0;
}
【小结】此题主要涉及到结构体和链表的操作。为实现此类管理系统,通常有两类处理方法。一是采用结构体数组,但其需要预先开辟一定的空间。当记录个数n足够大时,需要重新开辟连续空间,增加系统开销。二是采用链表的离散存储方式,可以根据需要动态的开辟内存空间。但在涉及到结点交换时,需要较多的指针来实现。而从最终结果的角度来看,可以选择简单的值交换而非结点交换。为了实现按某一字段进行排序操作时,若对每一字段依次编写排序代码会导致大量代码重复。联想到面向对象编程思想中的多态—对不同对象的行为不同这一观点,采用函数指针来模拟这一特征。参考STL中的sort()函数实现机制,编写了每个字段的比较器作为参数传入实现排序的函数中。排序过程采用冒泡算法,为实现循环控制,设置了标记变量swapped来记录当前遍历是否发生交换,若swapped = 0,则说明已完成排序;若swapped = 1,则说明尚未完成。根据冒泡的思想,每趟都能找到一个最大(小)值,故引入指针lptr来区分尚未完成排序部分和已排序部分,减少无意义的遍历过程。