火车票余票问题的算法解析
作者: Phill King
原创文章,转载请注明出处。
我们每次购买火车票,在查询的时候都可以及时的看到余票数。这个余票数是怎么计算出来的?有没有好的算法可以很快的计算出余票数目。本文对这个问题做了详细的分析,并给出了具体的代码。
声明:
本文详细分析了火车票的余票的算法,不考虑现实中一列火车包含不同等级座位和站票,以及预分配等情况,采用一个简化的模型来做基本分析。最后用一个高效率的算法来获取所有站点的余票数。
本文和12306无关。
-----------------------------------------------------------------------------------------------------
火车票余票问题的描述:
假设有一列火车,一共有M个座位,沿途经过S个站点。在出售一定数量的车票后,求该火车在沿途各站点的余票数。
首先我们对购票的情况和余票的关系做一个分析。
以M=5个座位, S=6站 为例
该表中的列代表所购票对应的站点段,一共有6站,对应的站点段数为5. 用户购买某张票如(3-6)站,即意味者占用了(3-4),(4-5),(5-6)的站点段。
该表中的行代表了座位号,对应购票时的座位。下文的总票数即为总的座位数。
对于(1-2)段来说,余票数就是总票数减去被占用的座位数。 示例中是 5-2 = 3;
对于(1-3)段来说,余票数就是总票数减去 (1-2) ∪ (2-3)的占用座位数。 示例中是 5-3 = 2;
对于(2-4)段来说, 余票数就是总票数减去 (2-3) ∪ (3-4)的占用座位数。 示例中是 5-4 = 1;
...
对应的余票表:
(行表头对应出发站,列表头对应抵达站)
因为余票即为总票数减去占用座位数,下面我们用更直观的占用座位表格来分析。
座位占用表: ( Si代表 从i到i+1站的座位占用集合)
我们可以看到对于某两站的座位占用集合有相应的规律,如
S(1-3) = S1 ∪ S2;
S(2-5) = S2 ∪ S3 U S4
S(i-j) = Si ∪ S(i+1) ... ∪ S(j-1)
我们可以根据座位占用的情况推导出余票计算的公式:
对于某两站i,j的余票计算可推出公式:
T(i,j) = 总票数 – (Si ∪ S(i+1) ∪ … ∪ S(j -1))
或者 T(i,j) = 总票数 – (S左 ∪ S下)
测试代码采用stl的Set来存储座位号,在100个站点和1000个座位下,更新购买2000张票计算整张余票表大约需要0.8秒。
可以看出性能不够理想。
性能优化:
由于集合运算比较耗时,我们考虑用位图法提升效率。
如果某个座位被出售,既标识相应的位(bit)为1.
比如(1-2)段的2座和5座已售,则可以用10010来表示。
假设火车共有1000个座位,如果用64位整数代表,则只需 大小为 1000/64 +1 = 16的数组即可表示。
uint64_t seat[16]
每两个站点之间的座位占用情况都需要一个数组来存储。一个数组的大小为128bytes。
这样计算集合只需要做或运算。大大提高效率。
用bit来表示购票的座位占用情况:
(1-2)站, 座位的分布对应的值是 二进制 10010
(2-3)站, 座位的分布对应的值是 二进制 10110
(3-4)站, 座位的分布对应的值是 二进制 10111
(4-5)站, 座位的分布对应的值是 二进制 01011
(5-6)站, 座位的分布对应的值是 二进制 01101
(1-3)站, 座位的分布对应的值是 二进制 10010 | 10110 = 10110 。 占用的座位数是3, 所以余票数是5-3= 2
以此类推
对应的余票计算公式:
每站对应的座位占用用一组64位整形数组表示,设为Bi
某两站i,j的余票数T(i,j) = 总票数 – Bi|B(i+1) | B(j-1) 中1的个数。
座位占用示意表:
采用位图(bitmap)法,运行效率提高很多,同时占用的内存空间也大大降低。
在100个站点,1000个座位的情况下测试更新购买2000张票之后的余票表格只需要5毫秒。
如果只是计算某两个站点的余票数更是只需要几微秒。
算法复杂度分析:
设购票数为T, 站点数为S, 时间复杂度为 T*S + S*(S+1)/2. 因为S是固定的,所以时间复杂度是O(N)
T*S的部分是更新购票信息,如果单独计算更新余票的时间复杂度,则为O(1);
空间复杂度:
设总座位数为M, 每两个站点的售票信息占用M/64*8 bytes. 整个个余票表占用M/8 * S(S+1)/2 bytes空间。
结合实际情况的一些测试数据(在本人的机器上):
50个站点,1000个座位的火车,批量更新2000张票,共耗时1.2毫秒。任意两个站点的座位占用数据可以压缩到大小为16的64位整形数组里,占用空间为128bytes。 整张表格的占用空间为128*50*49/2 = 154k bytes.
所以火车票更新余票按照此算法从时间和空间上分析,是非常快和省空间的。
最后是示例代码:
#include <vector>
#include <iostream>
#include <unordered_set>
#include <iomanip>
#include <chrono>
#include <ctime>
#include <cstdlib>
#include <set>
#include <fstream>using namespace std;#define BITS_WIDTH 64const int MAX_STATION = 100;
const int TOTAL_SEAT = 1000; // 总座位数,即每一站的总票数struct ticket_info
{int start = -1;int end = -1;int number = -1;
};typedef struct seat_type
{uint64_t & operator[](int i){return seat[i];};uint64_t seat[32] = {0};int get_bits(uint64_t n){int bits=0;while(n>0){n= n&(n-1);bits++;}return bits;}int get_count(){int count = 0;for(int i=0; i<32;i++)count += get_bits(seat[i]);return count;}}seat_type;class ticket_box
{std::vector< std::vector<seat_type> > tickets_count;const int ticket_total = TOTAL_SEAT ;const int station_number = MAX_STATION;
public://逐票更新int buy_ticket(ticket_info& new_ticket){int start = new_ticket.start;int end = new_ticket.end ;int number = new_ticket.number;int base = number/BITS_WIDTH;int offset = number%BITS_WIDTH;uint64_t update_bit = 1<<offset;for(int i=0; i<=start; i++)for(int j=start;j<station_number;j++){tickets_count[i][j][base] |= update_bit;}for(int i=start+1;i<=end;i++)for(int j=i;j<station_number;j++){tickets_count[i][j][base] |= update_bit;}return 0;}//批量更新余票int buy_tickets(std::vector<ticket_info>& tickets){for(auto ticket:tickets){int base = ticket.number/BITS_WIDTH;int offset = ticket.number%BITS_WIDTH;uint64_t update_bit = 1<<offset;for(int i=ticket.start; i<=ticket.end;i++){tickets_count[i][i][base] |= update_bit;}}for(int i=0; i<station_number-1; i++)for(int j=i+1, index=1; j<station_number; j++){for(int k=0; k<32; k++){tickets_count[i][j][k] = tickets_count[i][j-1][k] | tickets_count[i+index][j][k];}index++;}return 0;}//输出余票int output_remain_tickets(){std::cout<<std::setw(4)<<" ";for(int i=0; i< station_number; i++){std::cout<<std::setw(4)<<i+2<<" ";}std::cout<<endl;for(int i=0; i< station_number; i++){std::cout<<std::setw(4)<<"-----";}std::cout<<endl;for(int i=0; i< station_number&&i<12; i++){std::cout<<std::setw(3)<<i+1<<" ";for(int k=0; k<i; k++){std::cout<<std::setw(4)<<" ";}for(int j=i; j<station_number&&j<12; j++){std::cout<<std::setw(4)<<TOTAL_SEAT - tickets_count[i][j].get_count()<<" ";}std::cout<<endl;}return 0;}};
以下给出一个test case和对应的输出
此case中站点数为12,总座位数为1000。
(起始站,终点站,座位号)
7,10,1
10,12,1
3,6,2
6,8,2
2,4,3
4,6,3
1,5,4
5,8,4
2,4,5
4,5,5
8,9,6
4,6,7
6,7,7
5,7,8
7,9,8
10,11,9
1,3,10
3,4,102 3 4 5 6 7 8 9 10 11 12
-------------------------------------------------------1 998 996 995 994 993 993 992 991 991 990 990 2 996 995 994 993 993 992 991 991 990 990 3 995 994 993 993 992 991 991 990 990 4 995 994 994 993 992 992 991 991 5 995 995 994 993 993 992 992 6 996 995 994 994 993 993 7 996 995 995 994 994 8 997 997 996 996 9 999 998 998 10 998 998 11 999
本文考虑了有座位号的余票计算,可以涵盖不同类型有座位号的余票更新, 比如卧铺,一等座,二等座等。但是对于站票等没有座位号的情况,余票计算的方式会略有不同。如果用户购买的是没有座位的站票,购票只有出发站和到达站的信息,相应的余票应该如何计算呢?这个简单的问题就留给读者思考吧。
火车票余票问题的算法解析相关推荐
- 火车票余票问题的算法解析(续)
作者: Phill King 邮箱: phillking1982@163.com 原创文章,转载请注明出处. 在之前的文章中,我分析了火车票余票的问题,提供了一个高效的算法.在本文中,我们继续讨论无座 ...
- Python爬虫入门(一)火车票余票实时提醒
Python爬虫入门(一)火车票余票实时提醒 火车票余票实时提醒 最近开始学习爬虫了,参考的教材是<Python网络爬虫从入门到精通>吕云翔,张扬,韩延刚等,机械工业出版社.本篇博客是实战 ...
- python爬虫实现火车票余票查询
python爬虫实现火车票余票查询 获取终端输入的命令行参数 重构请求url,解析返回的json数据 获取终端输入的命令行参数 例如:python3 tickets.py -dg 成都 南京 2016 ...
- 项目实战一 12306火车票余票查询软件
1.安装docopt.urllib.requests 2.实现程序基础框架 # -*- coding:utf-8 -*-""" Train tickets query p ...
- Python爬虫----12306火车票余票查询器
12306火车票余票查询器 文章同步更新:http://www.riba2534.cn/?p=305 今天写了一个12306火车票余票查询器的爬虫,在这里记录一下过程. 首先先看一下最终效果: 比如想 ...
- [置顶] 火车票余票接口API使用方法
之前有很多人和我要火车票余票接口的api,铁道部频繁升级程序,导致余票的数据一直失败,参看文章: 快春运了,做个火车余票查询接口,余票来源12306,图是百度地图, 这个虽然叫做火车余票接口,但是我要 ...
- 火车票余票接口和火车票接口查询出来喽
火车票余票接口 经过本人无数次的尝试终于成功获取到12306官网的余票数据.
- 火车票余票接口API使用方法
之前有很多人和我要火车票余票接口的api,铁道部频繁升级程序,导致余票的数据一直失败,参看文章: 快春运了,做个火车余票查询接口,余票来源12306,图是百度地图, 这个虽然叫做火车余票接口,但是我要 ...
- 实验楼 python 火车票余票查询
Python 实现火车票查询工具 核心的几个地方: 1:正则表达式的构建 2:json数据的解析 一.实验简介 当你想查询一下火车票信息的时候,你还在上 12306 官网吗?或是打开你手机里的 APP ...
最新文章
- GitHub换帅!开源大神辞任CEO,竞品GitLab刚完成IPO
- NFV — 高性能 NFVI
- vue中怎么清空tab选项卡的缓存_vue Tab切换以及缓存页面处理的几种方式
- 实现串匹配的并行算法_5-1-KMP模式匹配
- aspnet登录界面代码_SPA+.NET Core3.1 GitHub第三方授权登录
- 如何通过Port-isolate实现二层网络相互隔离
- js 字符转换,小驼峰转大写字母开头并且加空格 changeDate -》 Change Date
- Spring3.0包描述
- 如何用firebug调试js
- [Python] 维度交换函数:transpose(m,n,r)和permute(m,n,r)
- 关于项目部署到外网后,访问域名失败的原因之一
- php显示jquery未定义,运行PHP脚本时,jQuery函数表示未定义
- 基于Paddle Serving百度智能边缘BIE的边缘AI解决方案
- ABC分析做法、步骤、Pareto图制作方法解说
- 谷歌退出中国谁对谁错
- 哈雷haley教你如何用你的手机测试你的移动端项目
- 同宇新材再更新招股书:继续冲刺创业板上市,计划募资13亿元
- html浏览器兼容性问题总结,常见的浏览器兼容性问题(小结)
- python解一元二次方程ax^2+bx_python 练习题:定义一个函数quadratic(a, b, c),接收3个参数,返回一元二次方程ax^2+bx+c=0的两个解...
- 旅游景区|“沉浸式夜游”如何玩?深圳光语数字