程序设计思维与实践 Week2 Blog

本文总结了一门程序设计思维与实践课程的实验成果,详细解析了迷宫问题、倒水问题、烷烃基识别问题、AC排名问题及桥牌问题的解决思路与代码实现,涉及广度优先搜索、数据结构应用及算法优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、常规前言

  本博客系列用于记录程序设计思维与实践课程相关编程学习收获,亦作为实验报告用于TA验收,同时记录大学学习足迹也是不错的选择!!希望通过这一个学期的学习能够实现能力的提升,新的学期加油!
本周作业与实验题目如下:

  • 迷宫问题(显式运用BFS)
  • 倒水问题(隐式运用BFS)
  • 烷烃基识别问题
  • AC排名问题
  • 桥牌问题

二、逐个击破

1.迷宫问题

题目描述

  地图显示,0表示可以走,1表示不可以走,左上角是入口,右下角是出口,这两个位置保证为0。请你编一个程序,写出从入口到出口的最短路线。

  1. Input

  输入是一个5 × 5的二维数组,仅由0、1两数字组成,表示法阵地图。

  1. Output

  输出若干行,表示从左上角到右下角的最短路径依次经过的坐标,格式如样例所示。数据保证有唯一解。

题目分析

  地图寻找出口类问题较为常用的一种方法就是广度优先搜索(BFS),而实现广度优先搜索使用的数据结构是队列(queue),队列的数据类型为定义的结构体point,其中包括点的x坐标和y坐标,代码如下:

struct point
{
	int x;
	int y;
	point(int _x, int _y) : x(_x), y(_y) {}
};

  利用先进先出的结构特点可以实现从起始点“波纹式”扩散寻找可行路径,如下图所示:
图例仅为说明BFS在迷宫问题中的应用,非本题实例
  当确定了BFS的思路以后,首先应该考虑的问题是广度优先搜索过程中队列的压入(push)条件有哪些,即寻找合法路径作为下一层搜索点。不满足push条件的情况有:

  1. 超出迷宫边界的路径
  2. 标识为“1”的障碍物路径
  3. 已经走过的路径

  除了以上情况外,其余均可以压入队列中。可见,我们需要记录曾经走过的路径和障碍物位置,而其中障碍物的位置通过输入存储在2维bool数组 barrier 中,曾经走过的路径也单独创建一个2维int数组 map 中。而由于题目的需要我们需要在找到最短路径后输出行走过程,所以创建了一个pair<int,int>类型的二维数组 road (这里也可以选择 map 类型),road的作用是用来记录(x,y)点的前序路径,例如从(1,3)走到(1,2),那么 road[1] [2] = (1,3) ,这样做的目的是找到出口后可以一步一步回溯到起点从而实现保存路径的效果。

  这里需要特别注意的一个小技巧是一个点向四个方向走的实现方法,如果按部就班的用循环写会出现大量重复的代码,所以定义了两个 int 数组dx,dy用于表示上下左右四个方向的路径尝试。
综上所述实现BFS和初始化数组的代码如下:

queue<point> Q;
bool map[5][5];
int barrier[5][5];
pair<int, int> road[5][5];
int dx[] = { 0,0,1,-1 };
int dy[] = { 1,-1,0,0 };


void bfs()
{
	Q.push(point(4, 4));
	map[4][4] = true;
	while (!Q.empty())
	{

		point tmp = Q.front();
		if (tmp.x == 0 && tmp.y == 0)break;
		Q.pop();
		for (int i = 0; i < 4; i++)
		{
			int x = tmp.x + dx[i];
			int y = tmp.y + dy[i];
			if (x >= 0 && x <= 4 && y >= 0 && y <= 4 && barrier[x][y] != 1 && map[x][y] != true)
			{
				map[x][y] = true;
				road[x][y] = pair<int, int>(tmp.x, tmp.y);
				Q.push(point(x, y));
			}
		}
	}
	return;
}

  最后就是按照路径回溯输出即可,但是这里自己当时犯了一个错误,找这个bug找了许久,以后要记得规避,就是在往前回溯的时候,一定要注意更新x后再更新y会出现错误,应先用变量保存更新前的x,y值。
错误写法如下:

while (x != 4 || y != 4)
	{
		printf("(%d, %d)\n", y, x);
		x = road[x][y].first;
		y = road[x][y].second;//这里的x已经是更新以后的x了!
	}

正确写法如下:

while (x != 4 || y != 4)
	{
		printf("(%d, %d)\n", y, x);
		int cx = x, cy = y;
		x = road[cx][cy].first;
		y = road[cx][cy].second;
	}

这道迷宫问题的完整代码如下给出:

#include <iostream>
#include<queue>
using namespace std;

struct point
{
	int x;
	int y;
	point(int _x, int _y) : x(_x), y(_y) {}
};



int main()
{
	int x = 0, y = 0;
	for (int i = 0; i <= 4; i++)
	{
		scanf("%d%d%d%d%d", &barrier[0][i], &barrier[1][i], &barrier[2][i], &barrier[3][i], &barrier[4][i]);
		for (int j = 0; j < 5; j++)
		{
			road[i][j] = pair<int, int>(-1, -1);
			map[i][j] = false;//表示地图上该点没有访问过
		}
	}
	bfs();
	while (x != 4 || y != 4)
	{
		printf("(%d, %d)\n", y, x);
		int cx = x, cy = y;
		x = road[cx][cy].first;//这里栽了,因为x发生了变化
		y = road[cx][cy].second;
	}
	printf("(%d, %d)", x, y);
	return 0;
}

2.倒水问题

题目描述

  倒水问题 “fill A” 表示倒满A杯,"empty A"表示倒空A杯,“pour A B” 表示把A的水倒到B杯并且把B杯倒满或A倒空。

  1. Input

  输入包含多组数据。每组数据输入 A, B, C 数据范围 0 < A <= B 、C <= B <=1000 、A和B互质。

  1. Output

  你的程序的输出将由一系列的指令组成。这些输出行将导致任何一个罐子正好包含C单位的水。每组数据的最后一行输出应该是“success”。输出行从第1列开始,不应该有空行或任何尾随空格。

题目分析

  该题目的情形和上一题类似,已知明确的起始条件终止条件,而且中间过程的每一步操作都是相同且有限的,这类题目就可以向BFS的角度去思考。
  就这道题而言,队列的数据类型为status结构体,结构体中有两个int型变量用于记录当前状态A中的水量和B中的水量,代码定义如下:

struct status
{
	int a;
	int b;
	bool operator<(const status &s)const
	{
		return a!=s.a?a<s.a:b<s.b; 
	}	
};

  终止的条件就是任意一个杯子中含有C容量的水,这也就是BFS中止的条件,而中间过程的可能操作有如下几种:

  1. A(B)杯不变,将B(A)杯倒空
  2. A(B)杯不变,将B(A)杯倒满
  3. A杯倒入B杯
  4. B杯倒入A杯

  当然,在杯子之间互倒时有四种可能,A空B未满,B满A未空;B空A未满,A满B未空(恰好一空一满的情况任意归入一种即可),而为了提高代码的可读性,update()函数用于特判当前情况是否可以压入,前面出现过的状态不能够再次压入栈中,避免出现无限循环,同时记录当前状态的前序状态,用于输出操作过程;将倒空操作写为函数empty(),将另外一些情况写为函数fill(),将所有可以执行操作后的状态压入队列中,然后再写bfs(),代码如下:

void update(status &s, status &t) 
{
    if ( before.find(t) == before.end() ) 
    { // 若之前没有出现过这种状态,加入队列
        before[t] = s;
        Q.push(t);
    }
}
void empty(status &s)
{//倒空水的操作 
	status re;
	if(s.a>0)
	{
		re.a=0;
		re.b=s.b;
		update(s,re);
	}
	if(s.b>0)
	{
		re.b=0;
		re.a=s.a;
		update(s,re);
	}
}

void fill(status &s, int A, int B)
{//倒入杯子操作 
	status re;
	if(s.a<A)
	{//倒满的情况
		re.a=A;
		re.b=s.b;
		update(s,re);
        if (s.b != 0) 
        {//
            if (s.a + s.b <= A) 
            {
                re.a = s.a + s.b;
                re.b = 0;
           		update(s, re);
            } else 
            {
                re.a = A;
                re.b = s.a + s.b - A;
            	update(s, re);
            }
        }
	}
	if(s.b<B)
	{
		re.b=B;
		re.a=s.a;
		update(s,re);
		if (s.a != 0) 
        {
            if (s.a + s.b <= B) 
            {
                re.b = s.a + s.b;
                re.a = 0;
           		update(s, re);
            } else 
            {
                re.b = B;
                re.a = s.a + s.b - B;
            	update(s, re);
            }
        }
	}
}


void bfs(int a,int b,int c)
{
	status start;
	start.a=0;
	start.b=0;
	Q.push(start);
	while(!Q.empty())
	{
		start=Q.front();
		Q.pop();
		if(start.a==c||start.b==c)
		{
			print(start);
			return;
		}
		empty(start);
		fill(start,a,b);
	}
}

  BFS部分写完以后本质的问题就已经解决了,然后根据before数组中存储的前序状态打印逐步操作过程,这个题的输出与迷宫问题的输出略有不同,迷宫问题中为了“偷懒”不用特别处理输出,直接把出口作为入口往回寻找路径,这样记录的状态就是从入口逐步到出口的状态。但是这道题没法从结束状态作为起点,所以只能正向记录然后递归输出,代码如下:

void print(status &p)
{
	if(before.find(p) == before.end()||(p.a == 0&&p.b==0))
	{//递归终止条件:任何一个瓶子满足条件c
		return ;
	}
	print(before[p]);
	status bp=before[p];
	if(bp.a!=p.a&&bp.b!=p.b)
	{//pour情况 
		if(p.a<bp.a)printf("pour A B\n");
		else printf("pour B A\n");
	}
	else
	{
		if(bp.a!=0&&p.a==0)printf("empty A\n");
		if(bp.b!=0&&p.b==0)printf("empty B\n");
		if(bp.a==0&&p.a!=0)printf("fill A\n");
		if(bp.b==0&&p.b!=0) printf("fill B\n"); 
	}
}

该题全部代码如下:

#include<iostream>
#include<queue>
#include<map>
using namespace std;

//可以作为全局变量 


struct status
{
	int a;
	int b;
	bool operator<(const status &s)const
	{
		return a!=s.a?a<s.a:b<s.b; 
	}	
};

queue<status>Q;
map<status,status>before;//利用map录前一步水的状态 

void print(status &p)
{
	if(before.find(p) == before.end()||(p.a == 0&&p.b==0))
	{//递归终止条件:任何一个瓶子满足条件c
		return ;
	}
	print(before[p]);
	status bp=before[p];
	if(bp.a!=p.a&&bp.b!=p.b)
	{//pour情况 
		if(p.a<bp.a)printf("pour A B\n");
		else printf("pour B A\n");
	}
	else
	{
		if(bp.a!=0&&p.a==0)printf("empty A\n");
		if(bp.b!=0&&p.b==0)printf("empty B\n");
		if(bp.a==0&&p.a!=0)printf("fill A\n");
		if(bp.b==0&&p.b!=0) printf("fill B\n"); 
	}
}

void update(status &s, status &t) 
{
    if ( before.find(t) == before.end() ) 
    { // 若之前没有出现过这种状态,加入队列
        before[t] = s;
        Q.push(t);
    }
}

void empty(status &s)
{//倒空水的操作 
	status re;
	if(s.a>0)
	{
		re.a=0;
		re.b=s.b;
		update(s,re);
	}
	if(s.b>0)
	{
		re.b=0;
		re.a=s.a;
		update(s,re);
	}
}

void fill(status &s, int A, int B)
{//装满一个杯子 
	status re;
	if(s.a<A)
	{
		re.a=A;
		re.b=s.b;
		update(s,re);
        if (s.b != 0) 
        {
            if (s.a + s.b <= A) 
            {
                re.a = s.a + s.b;
                re.b = 0;
           		update(s, re);
            } else 
            {
                re.a = A;
                re.b = s.a + s.b - A;
            	update(s, re);
            }
        }
	}
	if(s.b<B)
	{
		re.b=B;
		re.a=s.a;
		update(s,re);
		if (s.a != 0) 
        {
            if (s.a + s.b <= B) 
            {
                re.b = s.a + s.b;
                re.a = 0;
           		update(s, re);
            } else 
            {
                re.b = B;
                re.a = s.a + s.b - B;
            	update(s, re);
            }
        }
	}
}


void bfs(int a,int b,int c)
{
	status start;
	start.a=0;
	start.b=0;
	Q.push(start);
	while(!Q.empty())
	{
		start=Q.front();
		Q.pop();
		if(start.a==c||start.b==c)
		{
			print(start);
			return;
		}
		empty(start);
		fill(start,a,b);
	}
}


int main()
{
	int a,b,c;
	while(~scanf("%d%d%d",&a,&b,&c))
	{
		bfs(a,b,c);
		printf("success\n"); 
		while(!Q.empty())Q.pop();
		before.clear();//这里如果不clear的化,在find函数的地方会出现错误
	}
	return 0;
 }

Tips: 这道题的由于有多组数据输入,而在函数中需要访问和改变队列和一些二维数组,所以将他们定义为全局变量比较方便,否则需要作为函数参数传入,为了保证多组数据输入能够使用同一全局变量达到重复使用,一定要每次使用过后清除至初始状态,否则后续输入的输出会产生错误!

3.烷烃基识别问题

题目描述

  假设如上图,这个烷烃基有6个原子和5个化学键,6个原子分别标号1~6,然后用一对数字 a,b 表示原子a和原子b间有一个化学键。这样通过5行a,b可以描述一个烷烃基
你的任务是甄别烷烃基的类别。

  1. Input

  输入第一行为数据的组数T(1≤T≤200000)。每组数据有5行,每行是两个整数a, b(1≤a,b≤6,a ≤b)
  数据保证,输入的烷烃基是以上5种之一

  1. Output

每组数据,输出一行,代表烷烃基的英文名

题目分析

   这道题目考察了我们的观察和分析能力,通过观察上图中五种烷烃基的原子连接情况,不难发现如下属于每种类型的独有特点:

n-hexane类型:一个原子最多与另外两个原子连接
2,2-dimethylbutane类型:有且仅有一个原子和另外四个原子连接
2,3-dimethylbutane类型:有且仅有两个原子和另外三个原子连接
2-methylpentane类型:有且仅有两个原子与另外两个原子连接而且这两个原子相邻
3-methylpentane类型:有且仅有两个原子与另外两个原子连接而且这两个原子不相邻

   经过以上特点的观察发现后,通过map<int,int> 类型的 mp 来记录烷烃基连接n个原子的个数有几个(n=2,3,4),例如某烷烃基连接3个原子的原子个数有1个,则mp[3]=1;利用int类型数组relation来记录第i号原子连接几个原子(i=1,2,3,4,5,6),但是由于2-methylpentane类型和3-methylpentane类型无法直接通过连接原子个数区分,还要看原子之间的相互位置关系,连接两个原子的位置是不同的,如下图所示:
在这里插入图片描述
  所以额外再用两个数组array1和array2分解记录输入的时候第一列原子编号和第二列原子编号,在区分这两种类型时,利用flag1和flag2记录连接两个原子的原子序数,然后判断是否存在flag1连接了flag2即可,题目代码如下:
(这道题本质上就是一个顶点和度的关系问题,在处理methylpentane的区分问题的时候处理的方法不是很好,在后期学习他人代码的时候再回来补充更为简便的,少绕一些弯子的方法。)

#include<iostream>
#include<stdio.h>
#include<map>
using namespace std;
int sum;

int main()
{
	 scanf("%d",&sum);
	 while(sum--)
	 {
	 	map<int,int> mp;
		mp[2]=0;
		mp[3]=0;
		mp[4]=0;
	 	int array1[6]={0};//记录一行中的第一个原子 
	 	int array2[6]={0};//记录第二个原子,这里一定注意要多开一个空间 
	 	int relation[7]={0};//记录每一个原子连接过几次 
	 	int flag1=0,flag2=0;
	 	for(int i=1;i<6;i++)
	 	{
	 		int num1,num2;
	 		scanf("%d%d",&num1,&num2);
	 		array1[i]=num1;
	 		array2[i]=num2;
	 		relation[num1]++;
	 		relation[num2]++;
		 }
		 for(int i=1;i<=6;i++)
		 {
		 	for(int j=2;j<=4;j++)
		 	{
		 		if(relation[i]==j)
				{
					mp[j]++;
					break;
				}
			 }
		 }
		if(mp[4]!=0)printf("2,2-dimethylbutane\n");//只出现在连接过四次的原子存在的时候 
		else if(mp[3]==2)printf("2,3-dimethylbutane\n");//只出现在连接3次的原子有2个的时候  
		else if(mp[3]==1)//下面区分另外两种类型 
		{
			for(int i=1;i<=6;i++)
			{
				if(relation[i]==2)
				{
					if(flag1==0)flag1=i;
					else flag2=i;
				}
			}
			bool judge=false;
			for(int i=1;i<=5;i++)
			{
				
				if(array1[i]==flag1&&array2[i]==flag2||array1[i]==flag2&&array2[i]==flag1)
				{//这里是关键,整理的时候注意规避
					printf("2-methylpentane\n");
					judge=true;
					break;
				}		
			}
			if(judge==true)continue;
			else printf("3-methylpentane\n");
			
		}
		else printf("n-hexane\n");//其余为该种类型	
	 }
	return 0;
 } 

Tips:当时做题的时候依旧有一个找了很久的bug,就是在判断array1[i]==flag1&&array2[i]==flag2时忘记了输入原子的无序性,应当再反过来比较一次array2[i]==flag1&&array1[i]==flag2。

4.AC排名问题

题目描述

  程序设计思维作业和实验使用的实时评测系统,具有及时获得成绩排名的特点,那它的功能是怎么实现的呢?
  我们千辛万苦怼完了不忍直视的程序并提交以后,评测系统要么返回AC,要么是返回各种其他的错误,不论是怎样的错法,它总会给你记上一笔,表明你曾经在这儿被坑过,而当你历经千辛终将它AC之后,它便会和你算笔总账,表明这题共错误提交了几次。
  在岁月的长河中,你通过的题数虽然越来越多,但通过每题时你所共花去的时间(从最开始算起,直至通过题目时的这段时间)都会被记录下来,作为你曾经奋斗的痕迹。特别的,对于你通过的题目,你曾经的关于这题的每次错误提交都会被算上一定的单位时间罚时,这样一来,你在做出的题数上,可能领先别人很多,但是在做出同样题数的人中,你可能会因为罚时过高而处于排名上的劣势。
  例如某次考试一共八道题(A,B,C,D,E,F,G,H),每个人做的题都在对应的题号下有个数量标记,负数表示该学生在该题上有过的错误提交次数但到现在还没有AC,正数表示AC所耗的时间,如果正数a跟上了一对括号,里面有个正数b,则表示该学生AC了这道题,耗去了时间a,同时曾经错误提交了b次。例子可见下方的样例输入与输出部分。

  1. Input

  输入数据包含多行,第一行是共有的题数n(1≤n≤12)以及单位罚时m(10≤m≤20),之后的每行数据描述一个学生的信息,首先是学生的用户名(不多于10个字符的字串)其次是所有n道题的得分现状,其描述采用问题描述中的数量标记的格式,见上面的表格。

  1. Output

  根据这些学生的得分现状,输出一个实时排名。实时排名显然先按AC题数的多少排,多的在前,再按时间分的多少排,少的在前,如果凑巧前两者都相等,则按名字的字典序排,小的在前。每个学生占一行,输出名字(10个字符宽),做出的题数(2个字符宽,右对齐)和时间分(4个字符宽,右对齐)。名字、题数和时间分相互之间有一个空格。数据保证可按要求的输出格式进行输出。

题目分析

  这道题目首先确定结构体的构造,包括学生姓名,学生做题时间和AC数量,另外由于排序的需求需要对结构体进行排序,这里想使用STL中的sort()函数进行排序,那么就需要重载 < 符号,实现有先后次序的多关键字排序,代码如下:

struct info
{
	string name;
	int cnt;
	int time;
	bool operator <(const info& the_info)
	{
		if(cnt != the_info.cnt)
			return cnt > the_info.cnt;
		else if(time != the_info.time) 
			return time < the_info.time;
		else
			return strcmp(name.c_str(),the_info.name.c_str())<0;
		}

};

  另外本题的主要难点就在于它十分不规则的数据输入上,由于无法确定到底哪些道题会有括号“()”的输入,这样对我们数据的读入非常不友好,一开始接触到这个题的时候我采取的思路是每次读一整行,然后将一整行作为一整个字符串进行整体处理,这样的逻辑关系就十分混乱,因为你需要“瞻前顾后”,还要处理空格的问题就写出了大量if else语句,这对于程序的调试造成了非常大的不便(周末全耗在这个上面了…),最后突然间想起有一个函数叫sscanf(),其函数声明如下:

int sscanf(const char *str, const char *format, ...)

参数说明如下:

  str – 这是 C 字符串,是函数检索数据的源。
  format – 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符。

  sscanf与scanf最大的不同就是scanf是以键盘作为输入源,而sscanf是以字符串作为输入源,这样就可以完美的解决了输入数据有未知组和一行多个不规则带空格数据的尴尬情况。相当于需要两次读入,第一次从键盘读入一行字符串,第二次将第一次读入的字符串作为第二次读入的来源进行再次读入,将被空格分割开的字符串逐个读取处理。
  这样每一个数据都有了一个相同的格式,然后再每一个小字符串的处理中判断如下四种情况:

  1.若字符串第一个字符为 ‘ - ’,直接continue
  2.若字符串的最后一个字符为 ‘ )’,则以scanf(“%d(%d)”,&a,&b)分别读取
  3.若不是以上情况说明为一个非负数,将时间和AC数累加即可

该部分代码处理如下:

for(int i=0;i<n;i++)
		{
			char s[50];
			scanf("%s",s);
			int l=strlen(s);
			int t,n;
			if(s[0] == '-')continue;
			else if(s[l-1]==')')
			{
				sscanf(s,"%d(%d)",&t,&n);
				time+=t+n*m;
				num++;
	 		}
			else
			{
				time+=atoi(s);
				if(atoi(s) != 0) num++;
	 		} 
		}

  最后将排序结果进行输出即可,这里也积累一个小知识点,关于输出的左对齐和右对齐的格式,例如在输出10个字符长度的内容并且满足左对齐,在输出格式如下:(右对齐将符号去掉即可)

printf("%-10d",num);
for(vector<info>::iterator it=list.begin();it!=list.end();it++)
{
	printf("%-10s %2d %4d\n",it->name.c_str(),it->cnt,it->time);
}

  本题全部代码如下:

#include<iostream>
#include<string>
#include<string.h>
#include<vector>
#include<stdlib.h>
#include<algorithm>
using namespace std;
struct info
{
	string name;
	int cnt;
	int time;
	bool operator <(const info& the_info)
	{
		if(cnt != the_info.cnt)
			return cnt > the_info.cnt;
		else if(time != the_info.time) 
			return time < the_info.time;
		else
			return strcmp(name.c_str(),the_info.name.c_str())<0;
		}

};

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	char name[100];
	vector<info> list;
	while(~scanf("%s",name))
	{
		int time=0,num=0;
		info std;
		std.name=name;
		for(int i=0;i<n;i++)
		{
			char s[50];
			scanf("%s",s);
			int l=strlen(s);
			int t,n;
			if(s[0] == '-')continue;
			else if(s[l-1]==')')
			{
				sscanf(s,"%d(%d)",&t,&n);
				time+=t+n*m;
				num++;
	 		}
			else
			{
				time+=atoi(s);
				if(atoi(s) != 0) num++;
	 		} 
		}
		std.cnt=num;
		std.time=time;
		list.push_back(std);
	}
	sort(list.begin(),list.end());
	for(vector<info>::iterator it=list.begin();it!=list.end();it++)
	{
		printf("%-10s %2d %4d\n",it->name.c_str(),it->cnt,it->time);
	}
	return 0;	
}

5.桥牌问题

题目描述

  牌局由四个人构成,围成一圈。我们称四个方向为北 东 南 西。对应的英文是North,East,South,West。游戏一共由一副扑克,也就是52张构成。开始,我们指定一位发牌员(东南西北中的一个,用英文首字母标识)开始发牌,发牌顺序为顺时针,发牌员第一个不发自己,而是发他的下一个人(顺时针的下一个人)。这样,每个人都会拿到13张牌。
  现在我们定义牌的顺序,首先,花色是(梅花)<(方片)<(黑桃)<(红桃),(输入时,我们用C,D,S,H分别表示梅花,方片,黑桃,红桃,即其单词首字母)。对于牌面的值,我们规定2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < T < J < Q < K < A。
现在你作为上帝,你要从小到大排序每个人手中的牌,并按照给定格式输出。

  1. Input

  输入包含多组数据
  每组数据的第一行包含一个大写字符,表示发牌员是谁。如果该字符为‘#’则表示输入结束。
  接下来有两行,每行有52个字符,表示了26张牌,两行加起来一共52张牌。每张牌都由两个字符组成,第一个字符表示花色,第二个字符表示数值。

  1. Output

  输出多组数据发牌的结果,每组数据之后需要额外多输出一个空行!!!!!
每组数据应该由24行的组成,输出按照顺时针方向,始终先输出South Player的结果,每位玩家先输出一行即玩家名称(东南西北),接下来五行,第一行和第五行输出固定格式(见样例),第二行和第四行按顺序和格式输出数值(见样例),第三行按顺序和格式输出花色(见样例)。

题目分析

  这道题目本质考察的内容也是结构体的多关键字排序问题,桥牌的花色为第一关键字,大小为第二关键字,但是注意到花色和牌的大小均具有无法直接比较的自定义的大小关系,采取的处理方法是利用map的对应关系将所有的无法直接比较的字符映射成可以比较大小的int型数据存储在结构体中,例如题目的‘T’<‘J’<‘Q’,可以映射为 map[‘T’]=10,map[‘J’]=11,map[‘Q’]=12,这样就可以进行多关键字排序了。另外由于数据的输入不是int型而还包括char型,所以在用scanf读入的时候需要以字符形式读入,这样原本应该整型读入的数据还需要转换成对应的整型,因此下面translate函数实现了如上所述的映射和转换,在char到int的转换过程用到了ascii码的相关知识:

poker translate(char &c1,char &c2)
{
	poker T;
	T.design=mp[c1];
	if(c2<'A')T.size=int(c2 -'0');
	else T.size=mp[c2];
	return T;
}

struct poker
{
	int design;
	int size;
	bool operator<(const poker& P)
	{//多关键字排序
		if(design!=P.design)return design<P.design;
		else return size<P.size;
	}
};

  由于54张牌需要发给4个人,第一个发牌的人由输入决定,而且以顺时针方向进行发牌,可以将N-E-S-W四个方向按照顺时针分别设置为1-2-3-0,通过循环中的start+1再对4取模来实现四个人的轮流发牌,利用一个结构体的二维数组来存储四个人各自的桥牌情况,便于后面分别排序,具体发牌代码如下:

int start;
		if(first=='N') start=0;
		else if(first=='E') start=1;
		else if(first=='S')start=2;//这里栽了,当时少了个“=”
		else start=3;
		vector<vector<poker> >player(4);
		start=(start+1)%4;//从自己的下一个人发牌,栽了,当时没有对4取模
		for(int i=0;i<26;i++)
		{
			char c1,c2;
			scanf("%c%c",&c1,&c2);
			poker tmp=translate(c1,c2);
			player[start].push_back(tmp);
			start=(start+1)%4;
		}
		getchar();//读取空格
		for(int i=0;i<26;i++)
		{
			char c1,c2;
			scanf("%c%c",&c1,&c2);
			poker tmp=translate(c1,c2);
			player[start].push_back(tmp);
			start=(start+1)%4;
		}

  到了这一步所有的牌都已经发到对应的人手中,剩下的工作就是调用一下STL的sort函数分别对四个小数组进行排序然后输出即可,但是输出同样要注意因为结构体中花色和大小当初进行了转换,所以输出的时候要再转换回去,利用switch语句可以比较直观的实现转换(尽量避免在简单的多分支情况下使用太多if-else语句),代码如下:

			for(int j=0;j<13;j++)
			{
				char c;
				switch(player[cnt][j].size)
				{
					case 10:c='T';break;
					case 11:c='J';break;
					case 12:c='Q';break;
					case 13:c='K';break; 
					case 14:c='A';break; 
					default:c=player[cnt][j].size+48;
				}
				printf("%c %c|",c,c);
				if(j==12)printf("\n"); 
			}
				
			printf("|");
			for(int j=0;j<13;j++)
			{
				char c;
				switch(player[cnt][j].design)
				{
					case 1:c='C';break;
					case 2:c='D';break;
					case 3:c='S';break;
					case 4:c='H';break; 
				}
				printf(" %c |",c);
				if(j==12)printf("\n"); 
			}

  综上得全部代码如下:

#include<iostream>
#include<string>
#include<string.h>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
map<char,int> mp;//用于转换扑克牌的花色和大小 
struct poker
{
	int design;
	int size;
	bool operator<(const poker& P)
	{
		if(design!=P.design)return design<P.design;
		else return size<P.size;
	}
};
poker translate(char &c1,char &c2)
{
	poker T;
	T.design=mp[c1];
	if(c2<'A')T.size=int(c2 -'0');
	else T.size=mp[c2];
	return T;
}


int main()
{
	mp['C']=1;
	mp['D']=2;
	mp['S']=3;
	mp['H']=4; 
	mp['T']=10;
	mp['J']=11;
	mp['Q']=12;
	mp['K']=13;
	mp['A']=14;
	char first;//确定发牌人 
	int num=0;
	while(1) 
	{
		scanf("%c",&first);
		num++;
		getchar();
		if(num!=1&&first!='#')printf("\n");
		if(first=='#')break;
		int start;
		if(first=='N') start=0;
		else if(first=='E') start=1;
		else if(first=='S')start=2;//这里栽了,当时少了个“=”
		else start=3;
		vector<vector<poker> >player(4);
		start=(start+1)%4;//从自己的下一个人发牌,栽了,当时没有对4取模
		for(int i=0;i<26;i++)
		{
			char c1,c2;
			scanf("%c%c",&c1,&c2);
			poker tmp=translate(c1,c2);
			player[start].push_back(tmp);
			start=(start+1)%4;
		}
		getchar();//读取空格
		for(int i=0;i<26;i++)
		{
			char c1,c2;
			scanf("%c%c",&c1,&c2);
			poker tmp=translate(c1,c2);
			player[start].push_back(tmp);
			start=(start+1)%4;
		}
		getchar();
		for(int i=0;i<4;i++)
		{
			sort(player[i].begin(),player[i].end());
		}
		int cnt=2;//永远都是从南边开始输出 
		for(int i=0;i<4;i++)
		{
			switch(i)
				{
					case 0:
						printf("South player:\n");
						break;
					case 1:
						printf("West player:\n");
						break;
					case 2:
						printf("North player:\n");
						break;
					default:
						printf("East player:\n");
						break;
				}
			printf("+---+---+---+---+---+---+---+---+---+---+---+---+---+\n");
			printf("|");
			for(int j=0;j<13;j++)
			{
				char c;
				switch(player[cnt][j].size)
				{
					case 10:c='T';break;
					case 11:c='J';break;
					case 12:c='Q';break;
					case 13:c='K';break; 
					case 14:c='A';break; 
					default:c=player[cnt][j].size+48;
				}
				printf("%c %c|",c,c);
				if(j==12)printf("\n"); 
			}
				
			printf("|");
			for(int j=0;j<13;j++)
			{
				char c;
				switch(player[cnt][j].design)
				{
					case 1:c='C';break;
					case 2:c='D';break;
					case 3:c='S';break;
					case 4:c='H';break; 
				}
				printf(" %c |",c);
				if(j==12)printf("\n"); 
			}
				
			printf("|");	
			for(int j=0;j<13;j++)
			{
				char c;
				switch(player[cnt][j].size)
				{
					case 10:c='T';break;
					case 11:c='J';break;
					case 12:c='Q';break;
					case 13:c='K';break; 
					case 14:c='A';break;
					default:c=player[cnt][j].size+48;
				}
				printf("%c %c|",c,c);
				if(j==12)printf("\n"); 
			}
			printf("+---+---+---+---+---+---+---+---+---+---+---+---+---+\n");
			
			cnt=(cnt+1)%4;
		}	 	
	}
	
	return 0;
 } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值