C程序设计语言实习(进阶)

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语言函数调用的系统栈的理解。

 学生成绩管理系统

  1. 输入n个学生的学号,姓名,高等数学、大学物理、英语和C语言四门课程的成绩;
  2. 计算每门课程平均分数并输出;
  3. 按照某类课程的成绩高低排序;
  4. 找出每门课程最高分的同学学号和姓名(如果最高分有多名同学,都输出)。
#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来区分尚未完成排序部分和已排序部分,减少无意义的遍历过程。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值