实验报告
一、开发环境
程序语言:Java
开发环境:IntelliJ IDEA
二、需求分析
(1)航班基本信息管理
航班基本信息管理包括增加新航班,删除航班,修改航班编号,修改航班可售票数,修改航班所属航空公司,查询航班信息并按照机票价格、飞行时间、剩余数量排序
(2) 航班动态管理
发布航班延误、航班取消的信息;当航班发⽣延误和取消时,通知已经购买该航班机票的乘客, 并推荐与该航班具有相同起⻜降落地点且未延误的最近航班。
(3) 航班推荐
根据机票价格、飞行时间、余票数量综合考虑推荐出的最佳航班
(4) 票务管理
客户可以选择申请购票或者将已经买好的票退掉
(5)预约抢票
在剩余机票不足的情况下,⼀旦该航班发⽣退票,⾃动购买,如有多⼈预约,按照预约时间顺序按照先到先得原则分配
三、 实验方案及实现(数据结构选择、算法设计、关键代码说明等)
(1)类的设计及实现
- 航班类:包含航班编号,用于区分不同的航班; 航班公司;可售机票数;经停地点列表
class Flight{
private String flightNo; // 航班编号
private String flightCompany; // 航班公司
private int leftTickets; // 可售票数
private List<StopLocation> stopList; // 经停地点
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g2FE5svU-1586164280307)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200406101527853.png)]
2) 经停地点类:包含经停城市; 航班公司;可售机票数;经停地点列表
class StopLocation {
private String city; // 经停城市
private LocalDateTime time; // 时间
private double ticketPrice; // 从起始点到当地的价格
private final static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 时间格式
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kpGNt9HR-1586164280309)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200406102002090.png)]
- 客户信息类:包含乘客编号,用于区分不同的乘客; 乘客所在的航班编号;航班起飞时间;航班出发城市;航班终点城市;航班的机票价格
class Client {
private String clientNo; // 乘客ID
private String flightNo; // 航班号
private LocalDateTime time; // 航班时间
private String departureCity; //起飞城市
private String arrivalCity; //到达城市
private double ticketPrice; // 票价
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F8yeBjuD-1586164280310)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200406102128313.png)]
(2)数据结构的设计及实现
- 哈希表节点:实现了Map中的Entry接口,包括键和值两个属性
public class Node<K,V> implements Map.Entry<K,V> {
private K key; //键
private V value; //值
}
- 哈希表:实现了Map接口类,包括哈希表初始尺寸,哈希表中现有节点数量,负载因子以及哈希头链表,当哈希表中现有节点数量
number
> 初始尺寸*重载因子size * loadFactor
时,对哈希表进行扩容,将size
扩充为size * 2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CjJ5YLnO-1586164280311)(C:\Users\Administrator\Desktop\未命名文件.png)]
public class MyHashMap<K,V> implements Map<K,V> {
private int size = 16; //初始尺寸
private int number = 0; //节点数量
private double loadFactor = 0.75; //负载因子
private ArrayList<LinkedList<Node<K, V>>> arrayHead = new ArrayList<>(size); //哈希头链表
}
- 排序:采用堆排序的方法实现
//构建大顶堆
private void adjustHeap(int[] data, int i, int len) {
int temp, j;
temp = data[i];
for (j = 2 * i; j < len; j *= 2) { //沿关键字较大的孩子结点向下筛选
if (data[j] < data[j + 1]) {
++j; //j为关键字中较大记录的下标
}
if (temp >= data[j]) {
break;
}
data[i] = data[j];
i = j;
}
data[i] = temp;
}
//堆排序
private void heapSort(int[] data) {
int i;
for (i = data.length / 2 - 1; i >= 0; i--) { //构建大顶堆
adjustHeap(data, i, data.length - 1);
}
for (i = data.length - 1; i >= 0; i--) { //交换堆顶对象和当前未经排序子序列最后一个对象
int temp = data[0];
data[0] = data[i];
data[i] = temp;
adjustHeap(data, 0, i - 1); //重新调整为大顶堆
}
}
(3)类之间的关系
Flight类和StopLocation类为组合关系,和Client类为依赖关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FFas4bHF-1586164280313)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200406102929080.png)]
MyHashMap类和Node类为组合关系,Node类实现了Entry类的接口,MyHashMap类实现了Map类的接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kVcNL1jh-1586164280314)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200406104015292.png)]
(4)航班信息的存储
将航班信息和客户信息分别存入flight.xml
和clien.xml
文件中, 由SAXReader
进行文件读取,通过parseXML()
进行文件的解析, 文件格式如下:
<?xml version="1.0" encoding="UTF-8"?>
<flights>
<flight flightNo="MU5293">
<flightCompany>东方航空</flightCompany>
<stoplocation stopNo="1">
<city>温州</city>
<time>2019-11-05 15:40:00</time>
<ticketPrice>0.0</ticketPrice>
</stoplocation>
<stoplocation stopNo="2">
<city>北京</city>
<time>2019-11-05 21:40:00</time>
<ticketPrice>1381</ticketPrice>
</stoplocation>
<leftTickets>17</leftTickets>
</flight>
</flights>
(5)航班动态管理
当乘客发出购票申请的时候,判断是否有余票,有余票则购买此航班,没有余票则加入预约抢票名单中
当乘客退票时,查询预约抢票名单中是否含有该航班,有该航班则使最先发出购票申请的人得到票
(6)推荐航班
根据出发城市和终点城市筛选出可能的航班列表,并存入List
中。影响结果的三个因素分别是机票价格、飞行时间、余票数量,三个因素的相对权重为:
价格 | 时间 | 余票数量 | |
---|---|---|---|
价格 | 1 | 0.5 | 2 |
时间 | 2 | 1 | 4 |
余票数量 | 0.5 | 0.25 | 1 |
为了保证三个因素权重的合理性,需要引进一致性指标,进行一致性检验。为了消除数值对于计算带来的影响,对机票价格、飞行时间、余票数量进行归一化处理,然后计算每个航班的相对权重,选择出推荐值最大航班即为推荐最优航班
private List<Double> calculateWeight() {
Double[][] matrix=new Double[3][3];
Double[] column=new Double[3];
Double[][] matrixColumn= new Double[3][3];
for(int i=0;i<3;i++){
matrix[i][i]=1.0;
}
matrix[0][1]=0.5;
matrix[0][2]=2d;
matrix[1][2]=4d;
//填写矩阵剩余项
for(int i=2;i>=0;i--){
for(int j=2;j>=0;j--){
matrix[i][j]=1/matrix[j][i];
}
}
for(int j=0;j<3;j++){
for(int i=0;i<3;i++){
if(column[j]!=null){
column[j]=column[j]+matrix[i][j];
}else{
column[j]=matrix[i][j];
}
}
}
//矩阵归一化
for(int j=0;j<3;j++){
for(int i=0;i<3;i++){
matrixColumn[i][j]=matrix[i][j]/column[j];
}
}
//获得行数组
Double[] line=new Double[3];
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
if(line[i]!=null){
line[i]=line[i]+matrixColumn[i][j];
}else{
line[i]=matrixColumn[i][j];
}
}
}
//行归一化获得特征向量
Double[] w=new Double[3];
Double sum=0.0;
for(int i=0;i<3;i++){
sum=sum+line[i];
}
for(int i=0;i<3;i++){
w[i]=line[i]/sum; //特征向量
}
Double[] bw=new Double[3];
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
if(bw[i]!=null){
bw[i]=bw[i]+matrix[i][j]*w[j];
}else{
bw[i]=matrix[i][j]*w[j];
}
}
}
double sumR=0.0; //最大特征根R
for(int i=0;i<3;i++){
sumR=sumR+bw[i]/(3*w[i]);
}
return new ArrayList<>(Arrays.asList(w).subList(0, 3));
}
(7)Hash函数
使用String哈希函数:
int hashCode(byte[] value) {
int hash = 0;
int length = value.length >> 1;
for (int i = 0; i < length; i++) {
hash = 31 * hash + getChar(value, i);
}
return hash;
}
测试部分数据结果为:
索引:
0有2个 1有4个 2有2个 3有2个 4有2个 5有2个 6有4个 7有5个 8有2个 10有1个 11有3个 12有3个 13有2个 14有3个 15有4个 16有1个 17有3个 18有1个 19有2个 21有2个 22有1个 24有2个 25有3个 26有1个 27有3个 29有2个 30有3个 31有2个 33有3个 37有1个 39有1个 40有1个 43有1个 44有2个 47有2个 49有2个 51有2个 54有2个 55有1个 56有1个 57有2个 60有1个 61有1个 63有1个 65有1个 66有2个 71有1个 73有1个 75有1个 82有1个 84有1个 85有1个 86有1个 88有1个 89有1个 94有2个 95有2个 97有1个 103有1个 108有1个 110有1个 111有2个 113有1个 118有1个 125有2个
查找成功次数为98
使用加法Hash,即把输入元素加起来获得结果:
int additiveHash(String key)
{
int hash, i;
for (hash = key.length(), i = 0; i < key.length(); i++)
hash += key.charAt(i);
return hash;
}
测试部分数据结果为:
索引:
0有1个 1有1个 2有1个 3有2个 4有2个 5有1个 6有2个 7有2个 9有2个 10有2个 11有4个 12有3个 15有1个 16有1个 17有1个 18有1个 19有1个 20有1个 21有4个 22有1个 23有3个 24有1个 25有3个 26有1个 27有4个 29有3个 30有1个 31有2个 33有1个 35有3个 37有1个 42有1个 43有1个 44有1个 46有1个 48有2个 50有2个 51有3个 52有4个 53有4个 54有1个 55有2个 56有1个 57有1个 58有2个 60有1个 61有1个 63有1个 84有1个 85有1个 86有2个 87有2个 89有1个 90有1个 91有2个 93有3个 94有1个 95有1个 97有1个 98有1个 99有4个 106有1个 107有1个 108有4个 109有1个 110有1个 111有4个 112有1个 113有1个 115有4个 116有3个 117有2个 118有4个 119有2个 121有1个 122有1个 124有1个 125有1个 127有1个
查找成功次数累加为96
使用位运算Hash,即通过利用移位和异或充分的混合输入元素
int rotatingHash(String key)
{
int hash, i;
for (hash=key.length(), i=0; i<key.length(); ++i)
hash = (hash<<4)^(hash>>28)^key.charAt(i);
return hash;
}
测试部分数据结果为:
索引:
0有5个 1有3个 2有1个 3有8个 4有2个 5有7个 6有3个 7有6个 8有1个 9有3个 16有3个 17有2个 18有1个 19有4个 20有1个 21有2个 22有3个 23有1个 25有2个 32有3个 33有1个 34有3个 35有4个 36有1个 37有4个 38有3个 39有1个 41有2个 48有3个 49有2个 50有3个 51有1个 52有1个 54有1个 55有2个 56有1个 57有1个 64有2个 69有3个 71有2个 80有2个 81有1个 83有1个 85有1个 89有1个 98有1个 99有1个 101有1个 105有2个 115有1个 118有1个 120有1个
查找成功次数累加为93
使用乘法Hash,利用乘法的不相关性得到Hash值
int FNVHash1(String key)
{
final int p = 16777619;
int hash = (int)2166136261L;
for(int i=0;i<key.length();i++)
hash = (hash ^ key.charAt(i)) * p;
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return hash ;
}
测试部分数据结果为:
索引:
0有5个 1有5个 2有2个 3有2个 4有2个 5有5个 6有1个 7有5个 8有3个 10有2个 11有1个 12有1个 13有3个 14有1个 15有2个 16有2个 17有3个 18有3个 19有3个 20有1个 21有1个 22有1个 23有1个 24有3个 26有2个 28有3个 29有1个 30有1个 32有1个 35有1个 36有2个 37有2个 40有1个 42有1个 43有2个 44有1个 45有2个 47有2个 48有4个 49有2个 51有1个 52有1个 54有2个 55有1个 58有1个 60有1个 62有2个 63有1个 68有2个 69有1个 71有1个 80有1个 81有1个 82有1个 83有2个 84有1个 88有1个 90有1个 96有1个 99有1个 100有2个 101有1个 104有1个 106有1个 109有1个 111有3个 112有2个 113有1个 119有1个
查找成功次数累加为86
所以,根据测试结果选择乘法哈希函数作为哈希函数
四、 测试结果
五、 总结
包括对程序进行分析、评价运行效果,总结遇到的问题及解决办法等。
六、 程序清单
类名清单:
- Flight 航班信息类
- Client 乘客信息类
- StopLocation 航班经停地点类
- Node 哈希表节点类
- MyHashMap 自定义哈希类
- FlightService 航班基础功能接口类
- FlightService 航班基础功能类 实现增删改查基本功能
- ReadFile 读取文件
- ChooseBestFlight 根据价格,时间,余票数量三个因素推荐最优航班
- Sort 推排序
- Recommend 根据出发城市和终点城市查询航班并排序