线性结构
1.约瑟夫环
任务:一堆猴子都有编号,编号是 1,2,3 … n ,这群猴子(n 个)按照 1 ~ n 的顺序围坐一 圈,从第 1 开始数,每数到第 m 个,该猴子就要离开此圈,这样依次下来,直到圈中只剩 下最后一只猴子,则该猴子为大王。请设计算法编写程序输出为大王的猴子的编号。
- 需求分析————分析总结逻辑结构:这是一个约瑟夫环问题,n只猴子构成了一个环,每数到m只猴子就让这只猴子出圈,然后再从下一只猴子开始数,一直数到只剩下1只猴子。这样的问题可以使用递归进行解决,也可以不使用递归,循环数数即可。需要初始化猴子的编号,将1-n放在循环链表当中。由于使用头插法建表的速度比较快,在设计算法过程中可以从n到1倒序使用头插法建表。从链表的第一个结点开始,每次向前移动m-1个单位,并将该节点删除即可。当循环链表中只剩下一个结点的时候,说明只剩下了一只猴子,可以直接输出结果。
- 需求分析————分析总结运算集合:使用头插法向单链表中插入一个元素,删除单链表中的一个元素
- 概要设计————存储结构设计:采用循环链表的数据结构,让head指针能够指向第一支猴子的位置,完成建表以后,将单链表最后的NULL指针改成指向头节点的指针。在删除过程中如果删除了头节点,那么就需要将head指针的指向发生改变。需要设计至少2个链表的操作函数,insert(*L,x)使用头插法将x插入链表,fun(*L,m)执行删除操作,直到链表中只剩下一个元素。
- 小结:在本次约瑟夫环实习的过程中,关于仅剩一个结点的判断,我进行了很长时间的调试,我尝试过很多判断条件均没有用。后来我尝试手动更改循环,发现问题出现在如果head指向的元素被删除,head->next没有被及时更新,于是对代码进行了修改。
#include<stdio.h>
#include<stdlib.h>//因为要使用malloc,所以需要include<stdlib.h>
typedef struct List
{
int data;
struct List *next;
}List;//创建链表的结点结构题
void insert(List *L,int x)//使用头插法插入数字x
{
List *new_node=(List *)malloc(sizeof(List));//申请一个空间,插入链表的表头
new_node->data=x;//先用
new_node->next=L->next;//后改
L->next=new_node;//修改头指针指向的位置
}
void fun(List *L, int m)
{
List *p=L->next, *last=L;
while (p!=p->next)// 仅剩一个节点时停止
{
for (int i = 1; i < m; ++i)
{
last=p;//保存上一个结点的位置,方便后面修改next指针
p=p->next;//向后扫描一个结点
}
last->next=p->next;
if(p==L->next) L->next=p->next;//处理一个特殊情况,如果表头所指向的元素发生改变
free(p);//释放内存,节省内存占用
p=last->next;//下一个结点
}
}
int main()
{
List *head=(List *)malloc(sizeof(List));//申请一个单链表
head->next=NULL;//将头节点的next域赋值为NULL,方便后面扫描单链表的表尾
int n,m;
scanf("%d%d",&n,&m);
for(int i=n;i>=1;--i) insert(head,i);//倒着将数据使用头插法将数据插入单链表
List *p=head->next;//遍历单链表
while(p->next!=NULL) p=p->next;//访问链表中的最后一个元素
p->next=head->next;//将单链表的尾结点连上头节点所连接的结点,创建循环链表
fun(head,m);//执行删除操作
printf("%d",head->next->data);//输出结果
return 0;
}
2.纸牌游戏
任务:编号为 1-52 张牌,正面向上,从第 2 张开始,以 2 为基数,是 2 的倍数的牌翻一次, 直到最后一张牌;然后,从第 3 张开始,以 3 为基数,是 3 的倍数的牌翻一次,直到最后一 张牌;然后从第 4 张开始,以 4 为基数,是 4 的倍数的牌翻一次,直到最后一张牌;…直 到以 52 为基数的牌翻过,这时正面向上的牌有哪些?请设计算法编写程序输出最终正面向 上的纸牌的编号。
- 需求分析————分析总结逻辑结构:这是一个翻牌问题,因为一张牌有2面,如果一张牌被翻了奇数次,那么这张牌的状态和初始状态不一致,如果一张牌被翻了偶数次,那么这张牌的状态会和初始状态相一致。所以我们只需要依次扫描这些牌,统计被翻牌的次数即可,如果被翻了奇数次,那么这些牌是反面朝上,如果被翻了偶数次,那么就是反面朝上。初始化一个顺序表,清空顺序表中的数据项,从2到52依次将n到倍数翻牌,最后统计被翻牌的次数即可。
- 需求分析————分析总结运算集合:初始化顺序表(数组),翻牌(将该数组元素+1),判断该牌被翻的次数是否是偶数
- 概要设计————存储结构设计:因为需要很多次随机访问,为加快程序运行速度,采用顺序表的方式存储。
- 小结:在本次实习中,我们使用顺序表来存储纸牌被翻牌的次数和状态,并使用随机访问的形式来更新每一个结点,最后顺序访问输出结果。
#include<stdio.h>
int st[53];
int main()
{
for(int i=2;i<=52;++i)//以1-52为基数
for(int j=1;i*j<=52;++j)//遇到基数的整数倍就翻牌一次
++st[i*j];//记录翻牌的次数
for(int i=1;i<=52;++i)//检查被翻牌的牌
if(st[i]%2==0)//如果被翻了偶数次,那么还是正面朝上
printf("%d ",i);//输出结果
return 0;//程序结束
}
3. 一元多项式计算
任务:设计合适的存储结构,完成一元多项式的相关运算。
要求:(1)能够按照指数降序排列建立并输出多项式;(2)能够完成两个多项式的相加、相减,并将结果输出。
- 需求分析————分析总结逻辑结构:题目中所说到的降序排序,其实是按照降序排序的规则,对一个表达式中变量次数的排序。因为这道题中经常会用到对线性表的插入和删除的操作,所以采用单链表的形式存储会更加便捷。
- 需求分析————分析总结运算集合:初始化线性表(链表),基于值查找,并插入链表元素,将两个有序链表归并,对串的处理:从串中分离指数和系数
- 概要设计————存储结构设计:因为需要多次在线性表中插入元素,所以采用链式存储结构算法设计(流程图):初始化结果链表:创建一个空的结果链表 result,用来存储两个多项式相加的结果。定义指针遍历链表:pa 指向第一个多项式链表 A 的头节点,pb 指向第二个多项式链表 B 的头节点。这两个指针用来遍历两个多项式链表。遍历两个链表:使用 while 循环同时遍历两个链表 A 和 B。在每次循环中,比较 pa 和 pb 所指向节点的指数 power 大小,分以下几种情况处理:情况 1:pa->power > pb->power。如果 A 中当前节点的指数大于 B 中当前节点的指数,将 A 中当前节点插入结果链表 result 中。然后,pa 指针向后移动,指向下一个节点。情况 2:pa->power < pb->power如果 B 中当前节点的指数大于 A 中当前节点的指数,将 B 中当前节点插入结果链表 result 中。然后,pb 指针向后移动,指向下一个节点。情况 3:pa->power == pb->power如果 A 和 B 中当前节点的指数相同,则将它们的系数相加,生成一个新的节点插入结果链表 result 中。然后同时移动 pa 和 pb 指针,分别指向下一个节点。处理剩余项:当一个链表遍历结束后(pa 或 pb 为 NULL),继续处理另一个链表中的剩余项:将剩余的 pa 链表中的项插入结果链表 result。将剩余的 pb 链表中的项插入结果链表 result。返回结果链表:addPoly 函数返回存储加法结果的链表 result。(流程图实在太复杂了,这题就不画了,因为实习要求是画4个题目)
- 小结:在本次实习过程中,我遇到了很多的困难,首先是在字符串处理方面。需要从段字符串文本中分离出系数、x和次数,然后再插入链表中进行运算。通过这次实习,我更加深入理解了链表的链式存储的原理,以及两个有序链表归并的问题。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct PolyNode
{
int factor;//系数
int power;//指数
struct PolyNode *next;
//next指针,指向下一个结点
}PolyNode, *PolyList;
//创建结点类型和结点指针类型
void insertTerm(PolyList *L, int factor, int power)
{
PolyNode *newNode = (PolyNode *)malloc(sizeof(PolyNode));
//创建一个新的结点
newNode->factor = factor;
//传入新结点的系数
newNode->power = power;
//传入新结点的指数
newNode->next = NULL;
//新建的结点没有下一个指针位置
if (*L==NULL||(*L)->power<power)
//如果说发现需要插入的链表为空,或者是新插入的这个结点的次数高于原有的最高次数
{
newNode->next=*L;
//那么直接将该结点插入头节点即可
*L=newNode;
//修改头指针的位置
}
else
{
PolyNode *p=*L;
//新建一个p指针,用于遍历链表
while(p->next&&p->next->power>power) p=p->next;
//扫描链表,直到次数相等或者次数刚好大于这个点
if (p->power == power)
//如果恰巧停下来的时候,次数相等,那么合并
{
p->factor+=factor;
//将系数直接相加
free(newNode);
//释放这个已经合并同类项的点
if (p->factor == 0)
//如果次数干好为0,那么把合并以后的点也直接释放掉
{
PolyNode *temp = p->next;
//删除操作,先保存后面一个结点的指针,先连接后删除
free(p);
*L = temp;
}
}
else
{
newNode->next = p->next;
//否则,将新的结点插入链表
p->next = newNode;
}
}
}
void print(PolyList L)
//这个函数用于输出表达式
{
if (L == NULL)
//如果表达式保存的链表为空,说明表达式不存在,直接输出0即可
{
printf("0\n");
return;
}
PolyNode *p = L;
//声明一个指针类型p,用于遍历链表
while (p)
{
if (p->factor>0&&p!=L) printf("+");
//在非头节点的情况底下,如果系数大于0,那么需要输出正号
if (p->power==0) printf("%d",p->factor);
//如果次数正号为0,那么直接输出系数即可
else if(p->power==1&&p->factor==1) printf("x");
//如果系数和次数都恰好为1,那么直接输出x
else if(p->power==1&&p->factor==-1) printf("-x");
//如果次数为1,系数为-1,那么直接输出-x
else if(p->power==1&&p->factor!=1&&p->factor!=-1)
printf("%dx",p->factor);
//如果次数恰好为1,系数无特殊输出系数和x
else if(p->factor==1) printf("x%d", p->power);
//如果系数恰好为1,且次数无特殊,那么输出x和次数
else if(p->factor==-1) printf("-x%d",p->power);
//如果系数为-1,次数无特殊,那么输出-x和次数
else printf("%dx%d", p->factor, p->power);
//否则,输出系数、x、次数
p = p->next;
//遍历下一个结点
}
printf("\n");
//输出换行,输出完成
}
PolyList addPoly(PolyList A, PolyList B)
//新建加法运算
{
PolyList result=NULL;
//首先先清空答案result链表
PolyNode *pa=A,*pb=B;
//创建两个指针类型变量pa,pb,用于遍历两个链表
while (pa&&pb)
//如果在pa和pb都没有走到头掉情况底下,需要比较两个链表的次数
{
if (pa->power>pb->power)
//如果说a链表的次数大于b链表的次数,那么需要先计算a链表里值
{
insertTerm(&result,pa->factor,pa->power);
//调用insert函数将a链表中pa指向的系数和次数插入result链表中
pa=pa->next;
//pa走向下一个结点
}
else if(pa->power<pb->power)
//同样,如果说b链表的次数大于a链表的次数,那么需要先计算b链表里值
{
insertTerm(&result,pb->factor,pb->power);
//调用insert函数将b链表中pb指向的系数和次数插入result链表中
pb=pb->next;
//pb走向下一个结点
}
else
//最后只剩下了两者次数相等的情况
{
int sum=pa->factor+pb->factor;
//这种情况下,把两个系数相加即可
if(sum!=0) insertTerm(&result,sum,pa->power);
//如果说相加得到的结果为0,也没有必要自己给自己找麻烦,直接跳过插入操作
//如果相加得到的结果不为0,那么直接将这样一个系数和次数插入到result链表当中
pa=pa->next;
pb=pb->next;
//两个指针移向下位
}
}
while (pa)
//收尾,如果pb已经走到了链表的表尾,将pa走到底就行了
{
insertTerm(&result,pa->factor,pa->power);
pa=pa->next;
}
while (pb)
//收尾,如果pa已经走到了链表的表尾,将pb走到底就行了
{
insertTerm(&result,pb->factor,pb->power);
pb=pb->next;
}
return result;
//把答案返回给主调函数
}
PolyList subPoly(PolyList A,PolyList B)
//这是一个减法的过程,实现原理和上面是类似的
{
PolyList result=NULL;
//首先定义一个结果链表,这个链表原始是空的,因为里面什么也没有
PolyNode *pa=A,*pb=B;
//定义两个用于循环的指针pa和pb
while (pa&&pb)
//如果指向两个链表的指针都没有走到表尾
{
if (pa->power>pb->power)
//如果pa指向的结点的次数高于pb指向结点的次数
{
insertTerm(&result,pa->factor,pa->power);
//将pa的结点直接插入链表中即可
pa = pa->next;
//pa走向下一个结点
}
else if (pa->power<pb->power)
//相反的,如果pb指向的结点的次数高于pa指向的结点的次数
{
insertTerm(&result,-pb->factor,pb->power);
//那么pb指向的结点,取相反数以后插入result链表中即可
//注意,这里和上面的加法不一样,因为b是被减数,所以需要取负数
pb = pb->next;
//pb走向下一个结点
}
else
//最后只剩下了一种情况,就是两者的次数恰好相等
{
int ans=pa->factor-pb->factor;
//那么将两者的系数相减
if(ans!=0) insertTerm(&result,ans,pa->power);
//如果两者系数相减正号为0,也没必要白费功夫了,直接跳过即可
//将结点插入result当中
pa=pa->next;
pb=pb->next;
//pa,pb均可以走向下一个结点
}
}
while (pa)//收尾工作
{
insertTerm(&result,pa->factor,pa->power);
pa = pa->next;
//将pa剩下的结点全部插入到result中
}
while (pb)
{
insertTerm(&result,-pb->factor,pb->power);
pb=pb->next;
//将pb剩下的结点全部插入道result中
}
return result;
//将结果返回给主调函数
}
void add(PolyList *L)
//这是一个读入字符串分离字符建立链表的字符串操作函数
{
char ch[100];
//本程序可以接受的最大表达式长度为100
scanf("%s",&ch);
//将字符串读入程序中
int factor=0,sign=1,power=0;
//定义三个变量,factor用于指示系数
//sign用于指示符号
//power用于指示次数
for(int i=0;i<strlen(ch);++i)
//依次遍历整个字符串
{
if(ch[i]=='-')
//如果遇到了负号,将系数取相反数
{
sign=-1;
continue;
}
else if(ch[i]=='+') continue;
//如果遇到了加号,说明不需要处理
else if(ch[i]=='x')
//如果遇到了x,那就说明遇到了系数和次数的分界点
{
if(factor==0) factor=1;
//如果系数为0,那就说明x前面什么都没有,那系数就是1
for(++i;ch[i]!='+'&&ch[i]!='-';++i)
//通过一个循环读取次数
{
power*=10;
power+=ch[i]-'0';
}
if(power==0&&(ch[i]=='+'||ch[i]=='-')) power=1;
//如果还没读就结束了,说明次数也是1
insertTerm(L,sign*factor,power);
//将这个结点插入道链表当中
--i;
//多读了一个符号位,说不定后面需要特殊处理,返回去重新处理
power=0;factor=0;sign=1;//将标志还原
}
else
{
factor*=10;
factor+=ch[i]-'0';
//这两个代码用来读取系数
}
}
if(factor!=0) insertTerm(L,factor,0);
//收尾工作,如果最后没有读到x,那就说明存在0次方项,将0次方项插入链表
}
int main()
{
PolyList A=NULL;//新建两个链表
PolyList B=NULL;
add(&A);//读入A
add(&B);
PolyList C = addPoly(A,B);//C是答案,AB相加
printf("A+B:");
print(C);
C=subPoly(A,B);//C是答案,AB相减
printf("A-B:");
print(C);
return 0;//程序运行结束
}
4. 迷宫求解
任务:输入一个任意大小的迷宫数据,用非递归的方法求出走出迷宫的路径,并将路径输出。
- 需求分析————分析总结逻辑结构:采取数组坐标系,从入口(1,1)走到出口(n,m)所可能走过的路径。可以通过搜索和遍历来解决这样一个问题。遍历有2种常见的方式,一种是深度优先搜索(DFS,Depth First Search),另一种是广度优先搜索(BFS,Breadth First Search)。前者是基于栈来实现的,后者是基于队列来实现的。为了在这道题中体现栈和队列两种数据结构,本题采用广度优先搜索遍历的方式搜索,其特点是可以搜索到从起点到终点的最短路径。回溯路径的方法是,到达终点之后逐个返回寻找路径即可。
- 需求分析————分析总结运算集合:初始化队列将数据从队尾加入队列从队首取出元素,并删除该元素初始化栈将数据加入栈,并更新栈顶将栈顶元素去除,并删除栈顶元素
- 概要设计————存储结构设计:采用队列和栈两种数据结构来完成运算,通过队列完成广度优先搜索(BFS)的搜索过程,通过栈对答案进行逆序输出。现将终点入栈,再依次将所经过的点加入栈中,然后再逐个出栈输出。
- 小结:
#include<stdio.h>
const int MAXSIZE=100;
//用一个全局的常量去定义地图的最大大小
int map[MAXSIZE][MAXSIZE],n=0x3f3f3f3f,m=0x3f3f3f3f;
//定义一个地图,最大的大小为100*100
const int dx[4]={
0,0,1,-1};
const int dy[4]={
1,-1,0,0};
//定义两个向量,方便以后想下一步走
typedef struct Node
{
int x,y;
}Node;
//为每一个点的坐标定义一个结点类型
typedef struct Queue
{
Node data[MAXSIZE*MAXSIZE];
int front,rear;
}Queue;
//定义一个队列,用于存放广度优先搜索(bfs)
typedef struct Stack
{
Node data[MAXSIZE*MAXSIZE];
int top;
}Stack;
//定义一个栈,用于逆序输出路径
int empty(Queue *que)
{
if(que->rear==que->front) return 1;
else return 0;
}
//判断队列是否为空(用处:如果队列为空说明bfs结束)
Node pop(Queue *que)
{
int tmp=que->front;
//保存临时变量,指向需要输出的结点的位置
que->front=(que->front+1)%MAXSIZE;
//front指针向前移动
return que->data[tmp];
}
//将队列中的最前面的一个元素返回,并且删除掉这个元素
void push(Queue *que,Node *x)
{
que->data[que->rear].x=x->x;
que->data[que->rear].y=x->y;
//将结点的两个域分别更新
que->rear=(que->rear+1)%MAXSIZE;
//rear指针后移
}
int in_map(Node *x)
{
if(x->x<=n&&x->x>=1&&x->y<=m&&x->y>=1) return 1;
//如果没有超出地图边界,那么返回1,否则返回0
else return 0;
}
void bfs(Queue *que)
//这是一个广度优先搜索遍历的算法,当然我们也可以对这张图进行深度优先搜索
//但是bfs搜索的是最短路径,这道题我只输出一种最短路径
{
Node search=pop(que);
//从队列里取出一个结点
for(int i=0;i<4;++i)
{
Node new_node;
new_node.x=search.x+dx[i];
new_node.y=search.y+dy[i];
//使用向量,更新待搜索的结点
if(in_map(&new_node))
//如果没有超出边界
{
if(map[new_node.x][new_node.y]!=0) continue;
//如果这个地方是障碍物,或者已经更新过到达这个点的最短路径,那么直接continue
map[new_node.x][new_node.y]=map[search.x][search.y]+1;
//如果满足要求,则这个点到(1,1)点的最短路径就是其上一个结点+1
push(que,&new_node);
//将这个结点送入队列中
}
}
}
void print()
{
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
printf("%d ",map[i][j]);
//依次输出每一个点到(1,1)的最短路,输出-1代表该点无法到达(1,1)
}
printf("\n");
}
}
void way()
{
Stack stack;
//定义一个栈
//因为bfs到终点以后,可以倒着推出路径,但是正着无法推出,所以需要用一个栈来将结果逆序
stack.top=-1;
//初始化栈
int step=map[n][m];
//将最终步数放入临时变量step中,上一次走过的结点的步数一定是step-1
Node node;
//创建一个新的结点
node.x=n;node.y=m;
stack.data[++stack.top]=node;
//将结果放入栈中
int x=n,y=m;
while(!(x==1&&y==1))
{
for(int i=0;i<4;++i)
{
node.x=x+dx[i];
node.y=y+dy[i];
//通过向量更新出上一步有可能走过的点
if(in_map(&node))
//这个点是否在地图内
{
if(map[node.x][node.y]==step-1)
//如果步数正好是step-1,则说明这是路径上的一点
{
stack.data[++stack.top]=node;
//将这个点加入栈
x=node.x;
y=node.y;
--step;
//循环,继续寻找上一个结点
break;
}
}
}
}
printf("依次经过\n");
//输出结果
while(stack.top>=0)
//依次出栈
{
printf("%d %d\n",stack.data[stack.top].x,stack.data[stack.top].y);
--stack.top;
}
}
int main()
{
while(n>99||m>99) scanf("%d%d",&n,&m);
//输入地图的大小,保证输入的大小是在一个合法的范围内,防止超出地图的边界
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
int x;
scanf("%d",&x);
if(x==1) map[i][j]=-1;
//输入地图,0代表可以走,1代表是墙壁,但是1和第一步冲突,为了防止冲突,读入数据以后改成-1,代表走不了
}
}
if(map[1][1]==-1)
//如果起点为-1,墙壁
{
printf("输入数据错误");
//出错返回
return 0;
}
Node dot;
//新建一个点,这个点是原点
Queue que;
//新建一个队列,用于存储bfs序列
que.front=que.rear=0;
//初始化队列,方式segmentation fault
dot.x=dot.y=1;
push(&que,&dot);
//将原点推入队列中
map[1][1]=1;
while(!empty(&que)) bfs(&que);
if(map[n][m]==-1||map[n][m]==0)
{
printf("无路径\n");
//最后一个点无法到达,出错返回
return 0;
}
//print();
way();
return 0;
}
5. 八皇后问题
任务:国际西洋棋棋手马克斯·贝瑟尔于 1848 年提出在 8X8 格的国际象棋上摆放八个皇后, 使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种 摆法。请设计算法编写程序解决。 要求:(1)求解并输出八皇后的一个解;(2)在(1)的基础上,求解并输出八皇后的所有 解(92 个解)。
- 需求分析————分析总结逻辑结构:这道题需要在一个8x8的地图上生成8个皇后,让他在每一行,每一列和每一个对角线上均有且只有一个皇后,对于这样的一个问题,我们可以对64个位置上生成8个皇后,并且一次判断。但是这样做的话时间复杂度会来到2的64次方的级别,显然超出了计算机最大的计算规模。因此需要对这样的一个计算进行剪枝。其中一个剪枝的方法,就是首先生成一个长度为8的全排列,分别表示在每一列上皇后所在的行数,这样可以在生成的过程中就完成了对不在同一行、不在同一列上的判断,完成了剪枝。而生成全排列的算法,就是dfs。生成好全排列以后,只需要对对角线进行判断。通过观察我们发现,在同一主对角线上的元素,其横纵坐标的差值是一定的,而在同一副对角线上的元素,其横纵坐标之和是一定的。因此我们只需要对横纵坐标的和、差进行判重即可。
- 需求分析————分析总结运算集合:Dfs生成全排列,计算和差进行判重
- 概要设计————存储结构设计:通过一个长度为8的一维数组生成全排列,代表八皇后的位置。通过递归的方式模拟出一个栈,完成dfs的过程。通过一个长度为14的一位数组对八皇后的位置进行判重,但是对于差运算,需要计算一个偏移值。
- 小结:在本次实习的过程中,八皇后问题的存储结构设计让我印象尤其深刻。如果直接存储八皇后问题的图,则需要对行列和对角线均进行判断,但是如果先生成全排列,再再全排列中进行判断,只需要判断对角线即可,大大减少了程序的计算量,提高运算效率。
#include<stdio.h>
#include<string.h>
int q[9],st[9],ans_cnt;
//q数组用于存放答案,st代表这个数字是否被使用过,ans_cnt统计答案的个数
int check()
{
//先统计序号和q中值之和
int cnt[20];
memset(cnt,0,sizeof cnt);
for(int i=1;i<=8;++i) ++cnt[i+q[i]];
//统计每一个皇后所在位置的横坐标和纵坐标之和
for(int i=2;i<=16;++i)
if(cnt[i]>1) return 0;
//如果存在两个数字的横坐标和纵坐标之和相等
//那就说明存在两个或者更多的皇后出现在同一个副对角线上
//先统计序号和q中值之差(带偏移值)
memset(cnt,0,sizeof cnt);
for(int i=1;i<=8;++i) ++cnt[i-q[i]+10];
//统计每一个皇后坐在位置横坐标和纵坐标位置之差(算偏移值)
for(int i=3;i<=17;++i)
if(cnt[i]>1) return 0;
//如果存在两个数字其横坐标和纵坐标点差相等
//那就说明存在两个或更多的皇后出现在同一个主对角线上
return 1;
}
void print()
{
ans_cnt++;//print答案说明答案正确
for(int i=1;i<=8;++i) printf("%d ",q[i]);
//将答案数组q里的元素全部输出
printf("\n");
//输出结束
}
void dfs(int n)
{
if(n==9)
//如果已经搜索到了第九层,说明全排列已经生成完毕
{
if(check()) print();
//如果满足要求,那么输出结果
return;//递归退层
}
for(int i=1;i<=8;++i)
//生成全排列
{
if(st[i]==0)
{
st[i]=1;
q[n]=i;
//如果这个数字没有被使用过,那么使用
dfs(n+1);
st[i]=0;
//归位
}
}
}
int main()
{
dfs(1);
//从第一位开始生成
printf("%d",ans_cnt);
//输出结果的总数
return 0;
}
6. 运动会分数统计
任务:参加运动会有 n 个学校,学校编号为 1…n。比赛分成 m 个男子项目,和 w 个女子 项目。项目编号为男子 1…m,女子 m+1…m+w。不同的项目取前五名或前三名积分; 取前五名的积分分别为:7、5、3、2、1,前三名的积分分别为:5、3、2;哪些取前五名或 前三名由学生自己设定。(n<=20 , m<=20) 要求:(1)可以输入各个项目的前三名或前五名的成绩;(2)能统计各学校总分;(3)可以 按学校编号、学校总分、男女团体总分排序输出;(4)可以按学校编号查询学校某个项目的 情况;可以按项目编号查询取得前三或前五名的学校。
- 需求分析————分析总结逻辑结构:使用文件存储各个学校的获奖数据,这是一种基于结构题类型的线性表,每一个学校参与了多场比赛,而一场比赛又有两种或者多种给分模式。所以我们可以通过结构题类型的数组来记录,通过文件读写的过程来减少数据的重复输入和输出。
- 需求分析————分析总结运算集合:存储信息,添加记录,读取记录,冒泡排序,输出结果
- 概要设计————存储结构设计:使用一个结构题类型存储每一个学校的排名信息和得分情况,使用静态分配的线性表来存储。
- 小结:因为时间关系,这道题有一个遗憾在于我没有对结构题数组的异常进行处理,比如数据量过大导致结构题数组溢出。另外如果game.fzy文件为空,程序无法读取并创建新的学校和运动的问题也没有解决。一开始在做本实习题的时候,对题目的理解有所偏差,导致我没有能够写出正确的程序,从中我学到了以后在完成题目之前,应该先认真审题。
#include <stdio.h>
#include <string.h>
// 定义一个结构体来表示学校的成绩信息
struct School
{
int no; // 学校编号
int rank[100]; // 各个比赛的排名
int num[100]; // 各个比赛的队伍数量
int score[100]; // 各个比赛的得分, score[0]存储团体总分, score[98]男子, score[99]女子
} school[100]; // 定义一个包含100个学校信息的数组
int n, m, w; // n表示学校数量, m表示男子比赛项目数, w表示女子比赛项目数
// 根据排名和参赛队伍数量计算得分
int get_score(int rank, int num)
{
const int s3[4] = {
0, 5, 3, 2}; // 三支队伍时的得分表
const int s5[6] = {
0, 7, 5, 3, 2, 1}; // 五支队伍时的得分表
if (num == 3)
{
if (rank <= 3) return s3[rank]; // 排名前三的得分
else return 0; // 排名超过三名则无得分
}
else if (num == 5)
{
if (rank <= 5) return s5[rank]; // 排名前五的得分
else return 0; // 排名超过五名则无得分
}
else return -1; // 错误的队伍数量
}
// 交换两个学校结构体
void swap(struct School *a, struct School *b)
{
struct School tmp = *a;
*a = *b;
*b = tmp;
}
// 输出所有学校的总分
void output_school_total()
{
for (int i = 1; i <= n; ++i)
printf("学校%d的总分为%d\n", school[i].no, school[i].score[0]);
}
// 按照总分升序输出所有学校
void output_total()
{
struct School p[100];
memcpy(p, school, sizeof(school)); // 复制一份学校数据以免影响原始数据
// 使用冒泡排序对学校总分进行排序
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n - 1; ++j)
{
if (p[j].score[0] > p[j + 1].score[0])
swap(&p[j], &p[j + 1]);
}
}
// 输出排序后的学校总分
for (int i = 1; i <= n; ++i)
printf("学校%d的总分为%d\n", p[i].no, p[i].score[0]);
}
// 按照男子总分升序输出所有学校
void output_male()
{
struct School p[100];
memcpy(p, school, sizeof(school)); // 复制一份学校数据以免影响原始数据
// 使用冒泡排序对男子总分进行排序
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n - 1; ++j)
{
if (p[j].score[98] > p[j + 1].score[98])
swap(&p[j], &p[j + 1]);
}
}
// 输出排序后的男子总分
for (int i = 1; i <= n; ++i)
printf("学校%d的男子总分为%d\n", p[i].no, p[i].score[98]);
}
// 读取文件中的比赛数据并计算每个学校的得分
void read()
{
// n学校数量,m男子比赛项目数,w女子比赛项目数
FILE *fp = fopen("game.fzy", "r");
if (fp == NULL)
{
printf("文件打开失败\n");
return;
}
// 读取学校数量、男子比赛项目数和女子比赛项目数
fscanf(fp, "%d%d%d", &n, &m, &w);
// 读取每个学校在各个比赛中的排名和参赛队伍数量,并计算得分
for (int i = 1; i <= n; ++i)
{
school[i].no = i; // 设定学校编号
for (int j = 1; j <= m + w; ++j)
{
fscanf(fp, "%d%d", &school[i].rank[j], &school[i].num[j]); // 读取排名和参赛队伍数量
school[i].score[j] = get_score(school[i].rank[j], school[i].num[j]); // 计算得分
school[i].score[0] += school[i].score[j]; // 更新总分
if (j <= m) school[i].score[98] += school[i].score[j]; // 更新男子总分
else school[i].score[99] += school[i].score[j]; // 更新女子总分
}
}
fclose(fp);
}
// 按照女子总分升序输出所有学校
void output_female()
{
struct School p[100];
memcpy(p, school, sizeof(school)); // 复制一份学校数据以免影响原始数据
// 使用冒泡排序对女子总分进行排序
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n - 1; ++j)
{
if (p[j].score[99] > p[j + 1].score[99])
swap(&p[j], &p[j + 1]);
}
}
// 输出排序后的女子总分
for (int i = 1; i <= n; ++i)
printf("学校%d的女子总分为%d\n", p[i].no, p[i].score[99]);
}
// 将当前比赛数据保存到文件中
void save()
{
FILE *fp = fopen("game.fzy", "w+");
if (fp == NULL)
{
printf("文件打开失败\n");
return;
}
// 写入学校数量、男子比赛项目数和女子比赛项目数
fprintf(fp, "%d %d %d\n", n, m, w);
// 写入每个学校在各个比赛中的排名和参赛队伍数量
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m + w; ++j)
{
fprintf(fp, "%d %d\n", school[i].rank[j], school[i].num[j]);
}
}
fclose(fp);
}
// 新增一个男子项目
void new_male()
{
// 为每个学校新增一个男子项目
for (int i = 1; i <= n; ++i)
{
for (int j = m + 1; j <= m + w; ++j)
{
school[i].score[j + 1] = school[i].score[j];
school[i].num[j + 1] = school[i].num[j];
school[i].rank[j + 1] = school[i].rank[j];
}
}
// 询问用户获奖队伍数量
printf("请输入获奖的队伍数量(3/5)");
int num;
do scanf("%d", &num); while (!(num == 3 || num == 5));
// 询问每个学校在新项目中的排名
printf("请按照学校顺序依次输入1-%d学校的成绩",