C 数据结构 大中小三种轮渡计算过河时间问题实现
最近一个朋友找我帮忙看看他们C数据结构最后的考试题目—-大中小三种轮渡计算乘客过河时间问题,之前数据结构我就是没有学好的,出于好奇打算试试。
具体需求没说的特别清楚,给过一张图片如下:
加上跟他的沟通,大致需求按照我的理解如下:有A,B,C三种轮船,最大载客人数分别为50,40,20,每1小时一趟,如果提前装满可以提前发,如果1小时内每人坐船,空船也走,船过到对岸10分钟,回来8分钟,每个人上船20秒,下船为10秒,先上后下,小于10岁大于70岁的可以优先上船,从文件中读入上船人信息,算出每个人的到达时间,写到文件中,文件格式如下:
序号 姓名 性别 年龄 时间
1 房利 男 69 9:51:51
2 樊慕薇 女 15 21:13:34
3 嵇念霄 女 17 10:04:02
4 苗怜瑞 女 40 17:56:23
5 冷乐雁 女 70 18:03:49
6 柴冷燕 女 15 23:22:31
7 苍思华 女 59 11:25:31
8 郎怡 女 19 7:04:57
9 欧淑 女 9 3:23:30
10 别君富 男 8 7:54:20
…..
这个时间是来坐船的时间,最后要计算到达对岸的时间,并且写入文件中按如下格式:
序号 姓名 性别 年龄 时间 时间
不知道我说清楚没,废话不多说,直接上代码,我不保证我实现是正确的,有问题望指出!
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "dlist.h"
#include "queue.h"
#define __DEBUG__
#ifdef __DEBUG__
#define DEBUG(format, ...) printf("FUNC: %s LINE: %d "format,__FUNCTIONW__,__LINE__,##__VA_ARGS__)
#else
#define DEBUG(format,...)
#endif
// 指定输入输出的文件名
#define PERSON_INFO_FILE "./person.txt"
#define RESULT_INFO_FILE "./result.txt"
#define PERSON_READ_FMT "%d %s %s %d %s"
#define PERSON_WRITE_FMT "%d\t%s\t%s\t%d\t%s\t%s\n"
// 定义港口最多的船只数
#define BOAT_MAX_NUM 3
// 定义第一艘船准备离开的时间为早上八点,即八点乘客可以开始上船
#define FIRST_BOAT_LEAVE_TIME (8*3600+0*60+0)
// 定义船只等待超时时间为一个小时
#define BOAT_WAIT_EXPIRE_TIME (1*3600+0*60+0)
// 定义排队间隔,20秒
#define LINE_UP_INTERVAL 20
// 定义人上下船时间
#define PERSON_GO_BOARD_TIME 20
#define PERSON_GO_ASHORE_TIME 10
// 定义船只一个来回行驶的间隔18分钟,去10分钟,回来8分钟
#define BOAT_GO_OVER_TIME (10*60)
#define BOAT_CAME_BACK_TIME (8*60)
// 用来判断是否有优先上船的权利
#define CHECK_LINE_PRIORITY(AGE) ((AGE)<=10 || (AGE)>=70)
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
/**
* 主要目的是计算每个乘客到达对岸的时间
*/
typedef struct _boat_s_{
uint32_t max_customers; // 船最大载客人数
uint32_t customers_num; // 实际载客人数
uint32_t board_time; // 乘客可以上船的时间,可以此推算乘客到达时间
Queue * personq; // 用来记录这个船只上的乘客,记录
}boat_s;
/**
* 用来存放船只的队列,这个可以理解成是一个循环队列,因为船只的数目固定,
* 每次出港口再回来就加入到队列的后面如此循环,这样就不用每次出列都遍历
* 一遍其他所有的队列元素
*/
typedef struct _boat_q_{
boat_s *boat_queue[BOAT_MAX_NUM];
uint32_t bq_cur; // 指向队列的第一个元素
}boat_q;
static boat_q *initBoatQueue()
{
boat_q *pqueue = NULL;
boat_s *max_boat = NULL;
boat_s *mid_boat = NULL;
boat_s *min_boat = NULL;
Queue *max_boatq = NULL;
Queue *mid_boatq = NULL;
Queue *min_boatq = NULL;
// 默认初始化所有数据为0
max_boat = (boat_s*)calloc(1,sizeof(boat_s));
mid_boat = (boat_s*)calloc(1,sizeof(boat_s));
min_boat = (boat_s*)calloc(1,sizeof(boat_s));
pqueue = (boat_q*)calloc(1,sizeof(boat_q));
max_boatq = (Queue*)calloc(1,sizeof(Queue));
mid_boatq = (Queue*)calloc(1,sizeof(Queue));
min_boatq = (Queue*)calloc(1,sizeof(Queue));
if( !pqueue || !max_boat || !mid_boat ||
!min_boat || !max_boatq || !mid_boatq || !min_boatq )
{
DEBUG("No Memory!\n");
return NULL;
}
initqueue(max_boatq);
initqueue(mid_boatq);
initqueue(min_boatq);
max_boat->max_customers = 50;
// 默认这个是最先开始离开
max_boat->board_time = FIRST_BOAT_LEAVE_TIME;
max_boat->personq = max_boatq;
mid_boat->max_customers = 40;
mid_boat->personq = mid_boatq;
min_boat->max_customers = 20;
min_boat->personq = min_boatq;
pqueue->boat_queue[0] = max_boat;
pqueue->boat_queue[1] = mid_boat;
pqueue->boat_queue[2] = min_boat;
pqueue->bq_cur = 0;
return pqueue;
}
static void destoryBoatQueue(boat_q *pqueue)
{
int i = 0;
if( pqueue ){
for(i=0;i<ARRAY_SIZE(pqueue->boat_queue);++i){
if( pqueue->boat_queue[i] ){
if( pqueue->boat_queue[i]->personq )
free(pqueue->boat_queue[i]->personq);
free(pqueue->boat_queue[i]);
}
}
}
free(pqueue);
}
/**
* 每次船装满离开,或者时间到离开,就指向下一个船,因为船到达对岸回来的时间都是固定,所以可以通过
* bq_cur 变量来模拟船只的运行情况,没有实际意义上的入列之说,因为一直就是这3种不同类型的船在这里
* 所以可以通过队列出列来模拟船只出行
*/
static void boat_dequeue(boat_q *pqueue){
pqueue->bq_cur = (pqueue->bq_cur + 1) % ARRAY_SIZE(pqueue->boat_queue);
}
static boat_s* getCurrentBoat(boat_q *pqueue)
{
return pqueue->boat_queue[pqueue->bq_cur];
}
static inline uint32_t strtotime(const char *time)
{
uint32_t h,m,s;
uint32_t unixtime;
sscanf_s(time, "%d:%d:%d", &h,&m,&s);
unixtime = 3600*h+60*m+s;
return unixtime;
}
static inline uint32_t timetostr(uint32_t time,char *timestr)
{
uint32_t h,m,s;
h = time / 3600;
m = (time - 3600*h)/60;
s = time % 60;
sprintf_s(timestr,31,"%02d:%02d:%02d",h,m,s);
return 0;
}
/**
* 按照时间顺序来插入
*/
static int insert_person_node_by_time(DList *plist,person_s *person)
{
PNode p = NULL;
PNode pos = NULL;
if( !plist || !person )
return -1;
p = (PNode)malloc(sizeof(*p));
if( !p ){
DEBUG("No Memory!\n");
return -1;
}
p->person = person;
p->previous = NULL;
p->next = NULL;
if( !IsEmpty(plist) ){
return InsFirst(plist,p);
}
pos = GetHead(plist);
while( pos ){
if( pos->person->come_time >= person->come_time )
{
InsBefore(plist,pos,p);
return 0;
}
pos = pos->next;
}
InsAfter(plist,GetTail(plist),p);
return 0;
}
static int load_person_info(const char* filename,DList *plist)
{
FILE *fp = NULL;
uint32_t index = 0;
char name[64] = {0};
char sex[16] = {0};
uint32_t age = 0;
char timebuf[32] = {0};
person_s *person = NULL;
if( !filename || !plist )
return -1;
fopen_s(&fp, filename, "r");
if (!fp)
{
DEBUG("Open file %s error!\n",filename);
return -1;
}
while(( person = (person_s *)calloc(1,sizeof(person_s)) )
&& fscanf(fp, PERSON_READ_FMT,&person->index,person->name,person->sex,&person->age,timebuf ) != EOF)
{
person->come_time = strtotime(timebuf);
person->arrived_time = 0;
if( -1 == insert_person_node_by_time(plist,person) ){
DEBUG("insert person node Error!\n");
free(person);
fclose(fp);
return -1;
}
memset(name,0,64);
memset(sex,0,16);
memset(timebuf,0,32);
}
// 删除上面跳出循环件时没有被使用的person指向的空间
if( person && !person->name[0] && !person->sex[0] ){
free(person);
}
fclose(fp);
return 0;
}
static int save_person_info(const char* filename,DList *plist)
{
Position p = NULL;
FILE *fp = NULL;
char cometime_buf[32]= {0};
char arrivedtime_buf[32]= {0};
if( !filename || !plist )
return -1;
fopen_s(&fp, filename, "a+");
if (!fp)
{
DEBUG("Open file %s error!\n",filename);
return -1;
}
if( !IsEmpty(plist) ){ //空链表无数据就退出
DEBUG("The list is Empty!\n");
return -1;
}
p = GetHead(plist);
while (p)
{
memset(cometime_buf,0,32);
memset(arrivedtime_buf,0,32);
timetostr(p->person->come_time,cometime_buf);
timetostr(p->person->arrived_time,arrivedtime_buf);
fprintf(fp,PERSON_WRITE_FMT,p->person->index,p->person->name,
p->person->sex,p->person->age,cometime_buf,arrivedtime_buf);
p = p->next;
}
fclose(fp);
return 0;
}
/**
* 评估计算船只超时之前可以装多少人,超过船只可装的最大人数则返回最大人数
* 其中max是船只最大的载客人数,expire是船只必须离开的时间
*/
static int calculate_customers_num(DList *plist,uint32_t expire,uint32_t max)
{
uint32_t num = 0;
Position p = NULL;
p = GetHead(plist);
while (p){
// 如果超时,则不能继续上船
if( p->person->come_time > expire )
{
return num;
}
else{
num++;
// 如果船上人超过最大载客人数,则返回最大载客人数
if( num >= max )
return max;
}
p = p->next;
}
return num;
}
/**
* 交换两个指针变量指向的值
*/
static void swap(void**x,void**y)
{
void *temp = NULL;
temp = *x;
*x = *y;
*y = temp;
}
/**
* 上船之前进行排序,如果A先上去,在他上船的20秒内,可以让老人小孩先上去
* 既可按此对队列里面的数据进行重新排序,以此来模拟排队
*/
static int lineup_go_board(Queue* pqueue)
{
Link *prev = NULL;
Link *next = NULL;
if( !pqueue )
return -1;
// 如果队列里面的人的个数小于3
// 第一个是直接默认上去,当第一个上去的这20秒内后面的老人小孩可以插队
// 所以最少得3个人,否则插队没意义
if( pqueue->count < 3){
return 0;
}
// 从第二个开始,因为第一个上去之后才考虑排队
prev = pqueue->head->next;
while(prev && (next = prev->next) ){
if( CHECK_LINE_PRIORITY(((person_s *)(next->item))->age) ){
swap(&prev->item,&next->item);
}
prev = next;
}
return 0;
}
/**
* 遍历队列,计算到达时间,第一上去的乘客也要等到最后一个乘客上去才计算到达时间
* board_time 为下一个船可以准备装载乘客的时间
*/
static int calculate_arrived_time(boat_s *pboat,uint32_t *board_time)
{
uint32_t leave_time = 0; // 船离岸时间
uint32_t count = 0;
person_s *person = NULL;
Queue* pqueue = pboat->personq;
if( !pqueue )
return -1;
// 乘客满就开走,船没到超时时间,所以列表里最后一个乘客
// 上船后才开船
count = pqueue->count;
if( count == pboat->max_customers ){
// 可以上船的最后一个人
person = (person_s *)(pqueue->tail->item);
// 如果所有排队的乘客都是在船准备登船的时间点到达,比如早上8点前就聚集很多人排队
if( person->come_time <= pboat->board_time ){
// 这个时候我们要等待所有人的排队上船的时间间隔
leave_time = pboat->board_time + (count*PERSON_GO_BOARD_TIME);
}else{
// 如果8点开船,刚好最后一个人是8点半到来,则前面到的人需要等到8点半到的人上船才可以
leave_time = person->come_time + PERSON_GO_BOARD_TIME;
}
}else{ // 船超时开走,即船预期之后直接开走,考虑船预期的时间
// 这个时候如果是八点开船,则到九点船才走
leave_time = pboat->board_time + BOAT_WAIT_EXPIRE_TIME;
}
while( pqueue->count ){
// 每次上船的人
person = (person_s *)dequeue(pqueue);
// 人到达时间为船离岸时间加上船到达对岸的时间加上每个人下船的时间间隔
person->arrived_time = leave_time + BOAT_GO_OVER_TIME +
(count - pqueue->count )*PERSON_GO_ASHORE_TIME;
}
// 上一个船离开的时间即为下一个船进入港口准备装载乘客的时间
*board_time = leave_time;
return 0;
}
/**
* 模拟船只的离开,计算乘客到达对岸的时间
*/
int simulate_boat_leave(DList *plist,boat_q *pqueue)
{
Position p = NULL;
boat_s *cur_boat = NULL;
uint32_t next_boat_board_time; // 用来记录下一个船只准备装载乘客的时间
uint32_t latest_leave_time; // 用来记录船只最迟离开时间
uint32_t most_leave_person_num; // 用来记录每次搭载最多的乘客
if( !plist || !pqueue || !IsEmpty(plist) )
return -1;
p = GetHead(plist);
cur_boat = getCurrentBoat(pqueue);
while (p){
latest_leave_time = cur_boat->board_time + BOAT_WAIT_EXPIRE_TIME;
most_leave_person_num = calculate_customers_num(plist,latest_leave_time,
cur_boat->max_customers);
// DEBUG("## most_leave_person_num %d##\n",most_leave_person_num);
// 如果这个时间点没人乘船,空船离开
if( !most_leave_person_num ){
// 计算船下次到达港口的时间,由于空船,等待一个小时离开,没乘客下车则只需
// 考虑船来回的时间
cur_boat->board_time = latest_leave_time + BOAT_GO_OVER_TIME + BOAT_CAME_BACK_TIME;
clearQueueExceptData(cur_boat->personq);
cur_boat->customers_num = 0;
// 船离岸,准备下个船
boat_dequeue(pqueue);
cur_boat = getCurrentBoat(pqueue);
// 下一个船准备离岸时间,上个船离开下个船即可准备离开
cur_boat->board_time = latest_leave_time;
cur_boat->customers_num = 0;
}else{
// 如果人到达的时间,没超过船离开的时间,且船只没有装满
if( p->person->come_time <= latest_leave_time &&
cur_boat->customers_num < cur_boat->max_customers ){
// 把这些可以上船的人全部压入队列中,准备排队
enqueue(cur_boat->personq,p->person);
cur_boat->customers_num++;
p = p->next;
}
else{
// 达到开船要求的人数或者开船时间到,这个时候可以微调队列里面的人,模拟排队
// 按照老人小孩优先
if( lineup_go_board(cur_boat->personq) != 0 ){
DEBUG("lineup_go_board Error!\n");
}
if( calculate_arrived_time(cur_boat,&next_boat_board_time) != 0 ){
DEBUG("calculate_arrived_time Error!\n");
}
// 计算船下次到达港口的时间,由于有人要等到所有乘客下船之后才能回来,所以要加上乘客下船时间
cur_boat->board_time = next_boat_board_time + BOAT_GO_OVER_TIME + BOAT_CAME_BACK_TIME +
(cur_boat->customers_num*PERSON_GO_ASHORE_TIME);
clearQueueExceptData(cur_boat->personq);
cur_boat->customers_num = 0;
// 船离岸,准备下个船
boat_dequeue(pqueue);
cur_boat = getCurrentBoat(pqueue);
// 下一个船准备离岸时间,上个船离开下个船即可准备离开
cur_boat->board_time = next_boat_board_time;
cur_boat->customers_num = 0;
}
}
}
// 如果最后还剩下几个人,则不考虑再其他直接运过去
if( cur_boat->customers_num ){
if( lineup_go_board(cur_boat->personq) != 0 ){
DEBUG("lineup_go_board Error!\n");
}
if( calculate_arrived_time(cur_boat,&next_boat_board_time) != 0 ){
DEBUG("calculate_arrived_time Error!\n");
}
// 后面调度船的处理不用再考虑
cur_boat->customers_num = 0;
}
return 0;
}
int main(int argc, char* argv[])
{
int ret = -1;
DList *person_list = NULL;
boat_q *boat_queue = NULL;
person_list = InitList();
boat_queue = initBoatQueue();
DEBUG("Before load_person_info !\n");
if( load_person_info(PERSON_INFO_FILE,person_list) )
{
DEBUG("Load person info Error!\n");
goto exit;
}
if( simulate_boat_leave(person_list,boat_queue) != 0 ){
DEBUG("simulate_boat_leave Error!\n");
goto exit;
}
if( save_person_info(RESULT_INFO_FILE,person_list) != 0 ){
DEBUG("save_person_info Error!\n");
goto exit;
}
//ListTraverse(person_list,print);
ret = 0;
exit:
DEBUG("Before DestroyList !\n");
DestroyList(person_list);
destoryBoatQueue(boat_queue);
return ret;
}
主要的逻辑代码就这上面,还有一个链表和队列的实现我就不贴代码了,否则会很长,如果感兴趣可以到—C 数据结构 大中小三种轮渡计算过河时间问题实现 下载,欢迎拍砖。