为解决交通难题,某城市修建了若干条交错的地铁线路,线路名及其所属站名如stations.txt所示。
线1
苹果园
....
四惠东
线2
西直门
车公庄
....
建国门
线4
....
其中第一行数据为地铁线名,接下来是该线的站名。
当遇到空行时,本线路站名结束。
下一行开始又是一条新线....直到数据结束。
如果多条线拥有同一个站名,表明:这些线间可以在该站换车。
为引导旅客合理利用线路资源,解决交通瓶颈问题,该城市制定了票价策略:
1. 每条线路可以单独购票,票价不等。
2. 允许购买某些两条可换乘的线路的联票。联票价格低于分别购票。
单线票价和联合票价如 price.txt 所示。
线1 180
.....
线13 114
线1,线2 350
线1,线10 390
.....
每行数据表示一种票价
线名与票价间用空格分开。如果是联票,线名间用逗号分开。
联票只能包含两条可换乘的线路。
现在的问题是:根据这些已知的数据,计算从A站到B站最小花费和可行的换乘方案。
比如,对于本题目给出的示例数据
如果用户输入:
五棵松,奥体中心
程序应该输出:
-(线1,线10)-线8 = 565
如果用户输入:
五棵松,霍营
程序应该输出:
-线1-(线4,线13) = 440
可以看出,用户输入的数据是:起始站,终到站,用逗号分开。
程序输出了购票方案,在括号中的表示联票,短横线(-)用来分开乘车次序。
等号后输出的是该方案的花费数值。
分析:
自顶向下求解。先根据需求定义求解流程(顺序),再根据每个流程定义数据结构,函数(包括功能,参数),最后实现每个函数。对于线路信息的数据结构定义,因为每个线路的站名均为汉字,且有不少重复站名,并且汉字间比较更费时间,所以无论从空间还是时间考虑都应将其映射到其它数据结构,这是将站名与整数建立映射关系,每个站名都与一个整数对应,可以使用C++中map完成此功能或自已用HASH表实现,这里使用效率低些但实现简单的字符串指针数组实现,价格的数据结构实现与此类似。对于结果的计算有很多情况需要处理,如:
1. 当某个线路可直达时,是否还查找该线路可转乘到达的线路(以下程序查找);
2. 当同一联票线路有多种转乘方案时,该联票线路输出几回(以下程序有几种方案就输出几回);
3. 当有转乘线路可到达但无联票价格时是否不输出该方案(以下程序不输出)。
WIN32控制台程序不能输入中文汉字解决办法:
打开注册表(开始--运行--输入"regedit"回车),将"HKEY_CURRENT_USER--Console"中的"LoadConIme"修改为"1",然后在控制台中按"Ctrl+Space(空格)"可切换中文或英文输入。
解:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <io.h>
#include <ctype.h>
#define FALSE false
#define TRUE true
#define BOOL bool
typedef void VOID;
typedef int INT32;
typedef char INT8;
typedef long LONG;
#define MAX_LINES 50
#define LINE_STATIONS_NUM 100
#define ALL_STATIONS_NUM (MAX_LINES*LINE_STATIONS_NUM)
#define FILE_BUF_NUM 100
#define INPUT_BUF_NUM 50
#define OUPUT_BUF_NUM 100
#define MAX_RES_NUM 20
#define MAX_TWO_PRICE_NUM 100
//线
#define FLAG1 -49
#define FLAG2 -33
typedef struct LINE_ST
{
INT32 ln; //线号
INT32 num; //线内站数目
INT32 price; //线钱
INT32 ns[LINE_STATIONS_NUM]; //线内站号
}Line;
typedef struct TWO_PRICE_ST
{
INT32 num1; //线路号
INT32 num2; //线路号
INT32 price; //联票价格
}TwoPrice; //联票价格
/*****************************************************************************
* 函数:ReadLines *
* 参数:stLine:线路信息结构体. *
* pStaTable:站名(字符串)-序号(stLine.ns,stLine.cro)表. *
* szLinesFile:线路文件名. *
* 返回值:返回TRUE表示读取成功,FALSE失败. *
* 功能:读取线路信息. *
*****************************************************************************/
BOOL ReadLines(Line *stLine, INT8 *pStaTable[], INT8 *szLinesFile)
{
FILE *fp;
INT32 i,j;
INT32 n, ln; //站对应的数字,线号,
INT8 str[FILE_BUF_NUM]; //文件缓冲
n = 0;
ln = -1;
for(i=0; i<ALL_STATIONS_NUM; i++)
pStaTable[i] = NULL;
for(i=0; i<MAX_LINES; i++)
{
stLine[i].ln = -1;
stLine[i].price = 0;
stLine[i].num = 0;
for(j=0; j<LINE_STATIONS_NUM; j++)
{
stLine[i].ns[j] = 0;
}
}
if(NULL == (fp = fopen(szLinesFile, "r")))
{
printf("Err: Open stations.txt!\n");
return FALSE;
}
while(fgets(str, FILE_BUF_NUM, fp))
{
if('\n' == str[0])//空行?
continue;
// 去掉尾部不必要的换行符号
if(str[strlen(str)-1] == '\n' )
str[strlen(str)-1] = '\0';
if(FLAG1==str[0] && FLAG2==str[1]) //新线号?
{
ln++;
stLine[ln].ln = atoi(&str[2]);
continue;
}
for(i=0; i<n; i++)
{
if(0 == strcmp(str, pStaTable[i]))
break;
}
if(i==n)
{
pStaTable[i] = (INT8 *)malloc(sizeof(INT8)*LINE_STATIONS_NUM);
strcpy(pStaTable[i], str);
n++;
}
stLine[ln].ns[stLine[ln].num] = i;
stLine[ln].num++;
}
fclose(fp);
/*
for(i=0; NULL!=pStaTable[i]; i++)
{
printf("%d:%s\n", i, pStaTable[i]);
}
for(i=0; -1!=stLine[i].ln; i++)
{
printf("线%d: %d\n", stLine[i].ln, stLine[i].num);
for(j=0; j<stLine[i].num; j++)
{
printf("%d:%s\n", stLine[i].ns[j], pStaTable[stLine[i].ns[j]]);
}
}
*/
return TRUE;
}
/*****************************************************************************
* 函数:FreeReadLines *
* 参数:pStaTable:站名(字符串)-序号(stLine.ns,stLine.cro)表. *
* 返回值:无. *
* 功能:释放动态分配的pStaTable空间. *
*****************************************************************************/
VOID FreeReadLines(INT8 *pStaTable[])
{
INT32 i;
for(i=0; i<ALL_STATIONS_NUM; i++)
{
if(NULL != pStaTable[i])
free(pStaTable[i]);
}
}
/*****************************************************************************
* 函数:GetInput *
* 参数:szStart:起点站名. *
* szDes:终点站名. *
* 返回值:无. *
* 功能:读取用户输入的起点,终点. *
*****************************************************************************/
VOID GetInput(INT8 *szStart, INT8 *szDes) //获取用户输入
{
INT8 buf[INPUT_BUF_NUM];
INT32 i,j;
printf("请输入起始站和终点站(英文逗号隔开)......\n");
//英文逗号不能作为字符串分割符,即scanf("%s,%s", szStart, szDes)是错误的
fflush(stdin);
gets(buf);;
for(i=0; ','!=buf[i]; i++)
szStart[i] = buf[i];
szStart[i++] = '\0';
for(j=0; '\0'!=buf[i]; i++,j++)
szDes[j] = buf[i];
szDes[j] = '\0';
// printf("%s,%s\n", szStart, szDes);
}
/*****************************************************************************
* 函数:ReadLines *
* 参数:stLine:线路信息结构体. *
* pStaTable:站名(字符串)-序号(stLine.ns,stLine.cro)表. *
* szStart:起点站名. *
* szDes:终点站名. *
* aRes:结果,aRes[i][0]为线路1在stLine中序号,aRes[i][1]为线路2(若有) *
* 在stLine中序号. *
* 返回值:返回TRUE表示计算路线成功,FALSE失败. *
* 功能:计算起点到终点的可行乘车方案. *
*****************************************************************************/
BOOL CalcLines(Line *stLine, INT8 *pStaTable[], INT8 *szStart, INT8 *szDes,
INT32 aRes[][2]) //计算结果
{
INT32 i,j,k,l,m,n;
INT32 nStart = -1, nDes = -1; //起点,终点-序号(stLine.ns,stLine.cro)表
BOOL bStartFlag = FALSE, bDesFlag = FALSE; //找到起点,终点标志
INT32 nNotFind; //未找到的(起点或终点)
INT32 nResCount = 0; //结果计数
INT32 nLoop; //循环次数
aRes[0][0] = -1;
aRes[0][1] = -1;
//查找起点,终点在stLine中序号
for(i=0; NULL!=pStaTable[i]; i++)
{
if(0 == strcmp(szStart, pStaTable[i]))
nStart = i;
if(0 == strcmp(szDes, pStaTable[i]))
nDes = i;
}
if(-1==nStart || -1==nDes)
return FALSE;
//查找保存结果
for(i=0; -1!=stLine[i].ln; i++)
{
bStartFlag = FALSE;
bDesFlag = FALSE;
for(k=0; k<stLine[i].num; k++)
{
if(nStart == stLine[i].ns[k])
bStartFlag = TRUE;
if(nDes == stLine[i].ns[k])
bDesFlag = TRUE;
}
if(!bStartFlag && !bDesFlag) //未找到起点和终点?
continue;
nLoop = 1;
if(bStartFlag && bDesFlag) //找到起点和终点?
{
aRes[nResCount][0] = i;
aRes[nResCount][1] = -1;//?
nResCount++;
aRes[nResCount][0] = -1;
aRes[nResCount][1] = -1;
nLoop = 2; //以此线为起点或终点寻找可换乘路线,虽然一般没人这么做...
}
for(l=0; l<nLoop; l++)
{
if(1 == l)
bStartFlag = !bStartFlag;
if(bStartFlag) //只找到起点?
nNotFind = nDes;
else//只找到终点
nNotFind = nStart;
//找是否存在转乘线路可到达
for(j=i+1; -1!=stLine[j].ln; j++)
{
for(k=0; k<stLine[j].num; k++)
{
if(nNotFind == stLine[j].ns[k])
break;
}
if(k != stLine[j].num) //找到另一点
{
for(m=0; m<stLine[i].num; m++)
for(n=0; n<stLine[j].num; n++)
{
if(stLine[i].ns[m] == stLine[j].ns[n]
&& nStart!=stLine[i].ns[m] && nDes!=stLine[i].ns[m]
&& nStart!=stLine[j].ns[n] && nDes!=stLine[j].ns[n]) //转乘可到达?
{
aRes[nResCount][0] = i;
aRes[nResCount][1] = j;
nResCount++;
aRes[nResCount][0] = -1;
aRes[nResCount][1] = -1;
}
}
}
else
{
continue;
}
}
}
}
// for(i=0; -1!=aRes[i][0]; i++)
// printf("Line1:%d, Line2:%d\n", aRes[i][0], aRes[i][1]);
return TRUE;
}
/*****************************************************************************
* 函数:ReadLinesPrice *
* 参数:stLine:线路信息结构体. *
* sttwoPrice:联票价格. *
* szPriceFile:票价文件名. *
* 返回值:返回TRUE表示读取文件成功,FALSE失败. *
* 功能:读取票价. *
*****************************************************************************/
BOOL ReadLinesPrice(Line *stLine, TwoPrice *sttwoPrice,
INT8 *szPriceFile)//获取线路价格
{
FILE *fp;
INT8 c;
INT32 line1, line2;
INT32 price;
INT32 i, n = 0;
sttwoPrice[0].num1 = -1; //结束标志
if(NULL == (fp=fopen(szPriceFile, "r")))
{
printf("Err: Do not open %s\n", szPriceFile);
return FALSE;
}
while((FLAG1 == (c=fgetc(fp))) && EOF!=c)
{
// fPos = ftell(fp);
if((FLAG2 != (c=fgetc(fp))))
{
// if(0 != fseek(fp, fPos, SEEK_SET)) //文件指针向前移动1字节(返回判断该值是否等于FLAG1)
// {
// printf("Err: seek file err...\n");
// return FALSE;
// }
if(EOF != c) //文件指针向前移动1字节(返回判断该值是否等于FLAG1)
ungetc(c, fp);
else
{
fclose(fp);
return TRUE;
}
continue;
}
找到“线”字,读完这一行在返回
//读线号
line1 = 0;
while((' ' != (c=fgetc(fp))) && ','!=c)
{
line1 = line1 * 10 + c - '0';
}
price = 0;
if(' ' == c) //此行为单线价格?
{
while('\n' != (c=getc(fp)))
price = price * 10 + c - '0';
//记录单线价格
for(i=0; i<MAX_LINES; i++)
{
if(line1 == stLine[i].ln)
{
stLine[i].price = price;
break;
}
}
}
else //联票价格行
{
while(FLAG1 != (c=getc(fp)));
c = getc(fp); //FLAG2
//读第二个线号
line2 = 0;
while(' ' != (c=getc(fp)))
line2 = line2*10 + c - '0';
while('\n' != (c=getc(fp)) && EOF != c)
{
if(isdigit(c))
price = price * 10 + c- '0';
}
//记录联票价格
sttwoPrice[n].num1 = line1;
sttwoPrice[n].num2 = line2;
sttwoPrice[n].price = price;
n++;
sttwoPrice[n].num1 = -1;
}
}
fclose(fp);
// for(i=0; -1!=stLine[i].ln; i++)
// printf("Line:%d Price:%d\n", stLine[i].ln, stLine[i].price);
// for(i=0; -1!=sttwoPrice[i].num1; i++)
// printf("Line1:%d Line2:%d Price:%d\n", sttwoPrice[i].num1,
// sttwoPrice[i].num2, sttwoPrice[i].price);
return TRUE;
}
/*****************************************************************************
* 函数:OutputRes *
* 参数:aRes:结果. *
* stLine:线路信息结构体. *
* sttwoPrice:联票价格. *
* 返回值:无. *
* 功能:输出结果. *
*****************************************************************************/
VOID OutputRes(INT32 aRes[][2], Line *stLine, TwoPrice *sttwoPrice) //输出结果
{
INT32 i, j;
INT32 aPrice[MAX_RES_NUM];
INT32 nMinNum = -1; //最小价格序号(aRes中)
INT32 nMinPrice = ~(1<<(sizeof(INT32)*8 - 1)) ; //最小价格
for(i=0; i<MAX_RES_NUM; i++)
aPrice[i] = -1;
//找出最便宜的结果
for(i=0; -1!=aRes[i][0]; i++)
{
if(-1 == aRes[i][1]) //单线?
{
aPrice[i] = stLine[aRes[i][0]].price;
if(stLine[aRes[i][0]].price < nMinPrice)
{
nMinPrice = stLine[aRes[i][0]].price;
nMinNum = i;
}
}
else //双线
{
for(j=0; -1!=sttwoPrice[j].num1; j++)
{
if((stLine[aRes[i][0]].ln==sttwoPrice[j].num1 && stLine[aRes[i][1]].ln==sttwoPrice[j].num2)
|| (stLine[aRes[i][0]].ln==sttwoPrice[j].num2 && stLine[aRes[i][1]].ln==sttwoPrice[j].num1))
{
aPrice[i] = sttwoPrice[j].price;
if(sttwoPrice[j].price < nMinPrice)
{
nMinPrice = sttwoPrice[j].price;
nMinNum = i;
}
}
}
}
}
//输出
for(i=0; -1!=aRes[i][0]; i++)
{
if(i != nMinNum && -1 != aPrice[i])
{
if(-1 == aRes[i][1]) //单线
{
printf("-线%d", stLine[aRes[i][0]].ln);
}
else//双线
{
printf("-(线%d,线%d)", stLine[aRes[i][0]].ln, stLine[aRes[i][1]].ln);
}
}
}
if(-1 != nMinNum)
{
if(-1==aRes[nMinNum][1])
printf("-线%d=%d\n", stLine[aRes[nMinNum][0]].ln, nMinPrice);
else
printf("-(线%d,线%d)=%d\n", stLine[aRes[nMinNum][0]].ln, stLine[aRes[nMinNum][1]].ln, nMinPrice);
}
}
BOOL f()
{
INT32 aRes[MAX_RES_NUM][2]; //结果(aRes[i][],i为stLine中序号)
INT8 *pStaTable[ALL_STATIONS_NUM]; //站名(字符串)-序号(stLine.ns,stLine.cro)表
INT8 szStart[INPUT_BUF_NUM], szDes[INPUT_BUF_NUM]; //起点,终点
INT8 *szLinesFile = "stations.txt";
INT8 *szPriceFile = "price.txt";
Line stLine[MAX_LINES]; //线
TwoPrice stTwoPrice[MAX_TWO_PRICE_NUM]; //2个线路的合票价格
if(!ReadLines(stLine, pStaTable, szLinesFile)) //获取线路信息
return FALSE;
if(!ReadLinesPrice(stLine, stTwoPrice, szPriceFile)) //获取线路价格
return FALSE;
GetInput(szStart, szDes); //获取用户输入
CalcLines(stLine, pStaTable, szStart, szDes, aRes); //计算结果
OutputRes(aRes, stLine, stTwoPrice); //输出结果
FreeReadLines(pStaTable); //释放已分配空间
return TRUE;
}
INT32 main(INT32 argc, INT32 *argv[])
{
f();
return 0;
}