为解决交通难题,某城市修建了若干条交错的地铁线路,线路名及其所属站名如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;
- }