数据结构实习报告_2024-08-31

线性结构

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学校的成绩",
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值