基于Node.js实现的地铁线路规划——参考广度优先搜索算法

369 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

参考算法链接

代码工具

VSCode

源代码

地铁线路信息,只执行一次,存储到subway.txt
const subMessage = {
    '1号': '广州东站@3号北延段=体育中心=体育西路@3号@3号北延段=杨箕@5号=东山口@6号=烈士陵园=农讲所=公园前@2号=西门口=陈家祠=长寿路=黄沙@6号=芳村=花地湾=坑口=西朗@广佛',
    '2号': '广州南站@7号=石壁@7号=会江=南浦=洛溪=南洲=东晓南=江泰路=昌岗@8号=江南西=市二宫=海珠广场@6号=公园前@1号=纪念堂=越秀公园=广州火车站@5号=三元里=飞翔公园=白云公园=白云文化广场=萧岗=江夏=黄边=嘉禾望岗@3号北延段',
    '3号': '天河客运站@6号=五山=华师=岗顶=石牌桥=体育西路@1号@3号北延段=珠江新城@5号=广州塔@APM=客村@8号=大塘=沥滘=厦滘=大石=汉溪长隆=市桥=番禺广场',
    '3号北延段': '机场南=人和=龙归=嘉禾望岗@2号=白云大道北=永泰=同和=京溪南方医院=梅花园=燕塘@6号=广州东站@1号=林和西@APM=体育西路@1号@3号',
    '4号': '黄村=车陂=车陂南@5号=万胜围@8号=官洲=大学城北=大学城南=新造=石碁=海傍=低涌=东涌=黄阁汽车城=黄阁=蕉门=金洲',
    '5号': '滘口=坦尾@6号=中山八=西场=西村=广州火车站@2号=小北=淘金=区庄@6号=动物园=杨箕@1号=五羊邨=珠江新城@3号=猎德=潭村=员村=科韵路=车陂南@4号=东圃=三溪=鱼珠=大沙地=大沙东=文冲',
    '6号': '浔峰岗=横沙=沙贝=河沙=坦尾@5号=如意坊=黄沙@1号=文化公园=一德路=海珠广场@2号=北京路=团一大广场=东湖=东山口@1号=区庄@5号=黄花岗=沙河顶=天平架=燕塘@3号北延段=天河客运站@3号=长湴=植物园=龙洞=柯木塱=高塘石=黄陂=金峰=暹岗=苏元=萝岗=香雪',
    '7号': '广州南站@2号=石壁@2号=谢村=钟村=汉溪长隆@3号=南村万博=员岗=板桥=大学城南',
    '8号':'万胜围@4号=琶洲=新港东=磨碟沙=赤岗=客村@3号=鹭江=中大=晓港=昌岗@2号=宝岗大道=沙园=凤凰新村=同福西=文化公园@6号=华林寺=陈家祠@1号=彩虹桥=西村@5号=鹅掌坦=同德=上步=聚龙=石潭=小坪=石井=亭岗=滘心',
    '9号': '飞鹅岭=花都汽车城=广州北站@8号=花城路=花果山公园=花都广场=马鞍山公园=莲塘村=清埗=白鳝塘=高增@3号',
    '广佛': '新城东=东平=世纪莲=澜石=魁奇路=季华园=同济路=祖庙=普君北路=朝安=桂城=南桂路=礌岗=千灯湖=金融高新区=龙溪=菊树=西朗@1号=鹤洞=沙涌=沙园@8号=燕岗=石溪=南洲=沥滘',
    'APM': '林和西@3号北延段=体育中心南=天河南=黄埔大道=妇儿中心=花城大道=大剧院=海心沙=广州塔@3号',
    '13号': '鱼珠@5号=裕丰围@7号=双岗=南海神庙=夏园@5号=南岗=温涌=白江=新塘@16号=官湖=复昌桥',
    '14号': '嘉禾望岗=白云东平=夏良=太和=竹料=钟落潭=马沥=新和=太平=神岗=邓村=从化客运站=东风',
    '14号支线': '新和=知识城北=马头庄=枫下村=知识城=知识城南=旺村=康大=镇龙北=镇龙@21号',
    '21号':'员村@5号=天河公园=棠东=黄村@4号=大观南路=天河智慧城=神舟路=科学城=苏元@6号=水西@7号=长平=金坑=镇龙西=镇龙@14号支线=中新=坑贝=凤岗=朱村=山田=钟岗=增城广场'
}
for (let key in subMessage) {
    // console.log(key);
    // console.log(subMessage[key]);
    fs.appendFileSync('./subway.txt', key + ':' + subMessage[key]+'\n','utf-8');
}

image.png

引入的包
const fs = require('fs')
const readline = require('readline')
const lodash = require('lodash')
封装的部分方法
读取文件,获得对象数组
function readSubwayFlie() {
    let showLine = fs.readFileSync('./subway.txt', 'utf-8');
    let subStr = showLine.split('\n');
    subStr.pop();
    showLine = [];
    let stred = [];
    for (let i = 0; i < subStr.length; i++) {
        stred = subStr[i].split(':');
        showLine.push({
            name: stred[0],
            line: stred[1]
        })
    }
    return showLine;
}

image.png

读取某一个站名对应的路线对象数组
function stationToLine(station) {
    let subMessage = readSubwayFlie();
    let Lines = [];
    for (let i = 0; i < subMessage.length; i++) {
        if (subMessage[i].line.includes(station)) Lines.push(subMessage[i])
    }
    return Lines;
}
stationToLine('嘉禾望岗')

image.png

传入name,和已经检查的name数组,判断该路线是否被检查过

image.png

function isChecked(line, lineStr) {
    for (let i = 0; i < lineStr.length; i++) {
        if (lineStr[i] === line) return true
    }
    return false;
}
传入路线对象数组和被检查过的name数组,获得changeStation数组,数组每个元素均为对象,{start: ,changeToLine: }
function LineToChangeStation(Lines, checked) {
    let changeStation = [];
    let t;
    for (let i = 0; i < Lines.length; i++) {
        if (typeof (Lines[i].line) == 'string') {
            t = Lines[i].line.split('=');
            for (let j = 0; j < t.length; j++) {
                if (t[j].indexOf('@') > 0) {
                    if (t[j].split('@').length === 2) {
                        if (!isChecked(t[j].split('@')[1], checked)) {
                            changeStation.push({
                                start: t[j].slice(0, t[j].indexOf('@')),
                                changeToLine: t[j].slice(t[j].indexOf('@') + 1)
                            })
                        }
                    } else {
                        if (!isChecked(t[j].split('@')[1], checked)) {
                            changeStation.push({
                                start: t[j].slice(0, t[j].indexOf('@')),
                                changeToLine: t[j].split('@')[1]
                            })
                        }
                        if (!isChecked(t[j].split('@')[2], checked)) {
                            changeStation.push({
                                start: t[j].slice(0, t[j].indexOf('@')),
                                changeToLine: t[j].split('@')[2]
                            })
                        }
                    }
                }
            }
        }
    }
    return changeStation;
}

image.png

判断路线是不是目标路线数组中的,传入name和路线路线对象数组
function lineIsRequest(line, str) {
    for (let i = 0; i < str.length; i++) {
        if (line === str[i].name) return true
    }
    return false
}
  • 获取名称为name的路线line
function getOneLine(lineName) {
    let subMessage = readSubwayFlie();
    for (let i = 0; i < subMessage.length; i++) {
        if (subMessage[i].name == lineName) return subMessage[i].line
    }
    return null;
}
  • 把缓冲区的路线放到“内存”,缓冲区清空
function bufferToRAM(buffer, checked) {
    for (let i = 0; i < buffer.length; i++) {
        checked.push(buffer[i])
    }
    buffer = [];
}
算法实现函数

传入始站和终点站

function mainFn(first, last) {
    let finish = false;
    let firstLine = stationToLine(first);
    let lastLine = stationToLine(last);
    let result = [];
    let checkedLine = [];//检查过的路线,不再需要
    let bufferCheckedLine = [];//上一级检查过的路线,但还能用
    let middleStation = [];//中间站

    //一线到
    for (let i = 0; i < firstLine.length; i++) {
        for (let j = 0; j < lastLine.length; j++) {
            if (firstLine[i].name === lastLine[j].name) {
                result.push({
                    start: first,
                    line: firstLine[i].name
                })
                finish = true;
            }
        }
        bufferCheckedLine.push(firstLine[i].name)
    }

    // 两线到
    if (finish === false) {
        let already = 0;
        let one = LineToChangeStation(firstLine, bufferCheckedLine);
        bufferToRAM(bufferCheckedLine, checkedLine);

        for (let i = 0; i < one.length; i++) {
            if (lineIsRequest(one[i].changeToLine, lastLine)) {
                    middleStation[0] = one[i];
                    finish = true;
                    already = 1;
            }
            bufferCheckedLine.push(one[i].changeToLine)
        }

    }
    //三线到
    if (!finish) {

        let one = LineToChangeStation(firstLine, checkedLine);
        let two = [];
        let chang = [];//无关变量,暂时存储

        bufferToRAM(bufferCheckedLine, checkedLine)
        for (let i = 0; i < one.length; i++) {
            middleStation[0] = one[i];
            chang[0] = {
                name: one[i].changeToLine,
                line: getOneLine(one[i].changeToLine)
            };
            two = LineToChangeStation(chang, checkedLine);

            for (let j = 0; j < two.length; j++) {
                if (lineIsRequest(two[j].changeToLine, lastLine)) {
                    middleStation[1] = two[j];
                    finish = true;
                }
                bufferCheckedLine.push(two[j].changeToLine)
            }
            if (finish) break;
        }

    }
    //四线成,三个中间站
    if (finish === false) {
        checkedLine = [];
        bufferCheckedLine = [];
        let chang = [];
        for (let i = 0; i < firstLine.length; i++) {
            bufferCheckedLine.push(firstLine[i].name)
        }
        bufferToRAM(checkedLine, bufferCheckedLine)
        middleStation = [];
        let one = LineToChangeStation(firstLine, bufferCheckedLine);
  
        for (let i = 0; i < one.length; i++) {
            middleStation[0] = one[i];
            chang[0] = {
                name: one[i].changeToLine,
                line: getOneLine(one[i].changeToLine)
            };
            for (let m = 0; m < one.length; m++) {
                bufferCheckedLine.push(one[m].changeToLine)
            }
            two = LineToChangeStation(chang, bufferCheckedLine);
  
            for (let j = 0; j < two.length; j++) {
                middleStation[1] = two[j];
                chang[0] = {
                    name: two[j].changeToLine,
                    line: getOneLine(two[j].changeToLine)
                }
                for (let m = 0; m < two.length; m++) {
                    bufferCheckedLine.push(two[m].changeToLine)
                }
                three = LineToChangeStation(chang, bufferCheckedLine)
     
                for (let k = 0; k < three.length; k++) {
                    if (lineIsRequest(three[k].changeToLine, lastLine)) {
                        middleStation[1] = three[k];
                        finish = true;
                    }
                   
                }
                if (finish) break;
            }
            if (finish) break;
            bufferCheckedLine = [];
            for (let n = 0; n < checkedLine.length; n++) {
                bufferCheckedLine.push(checkedLine[n])
            }
        }
    }
    if (!finish) {
        console.log('开发中');
    }

    if (result.length !== 0) {
        let i = 0;
        for (i = 0; i < firstLine.length; i++){
            if(firstLine[i].line.includes(last))break
        }
        let f = firstLine[i].line.indexOf(first)
        let l = firstLine[i].line.indexOf(last)
        console.log(firstLine[i].name);
        if (f > l) {
            console.log(firstLine[i].line.slice(l,f+first.length));
        } else {
            console.log(firstLine[i].line.slice(f,l+last.length));
        }
    } else if(finish){
        let i = 0;
        for (i = 0; i < firstLine.length; i++){
            if(firstLine[i].line.includes(middleStation[0].start))break
        }
        let f = firstLine[i].line.indexOf(first)
        let l = firstLine[i].line.indexOf(middleStation[0].start)
        console.log(firstLine[i].name);
        if (f > l) {
            console.log(firstLine[i].line.slice(l,f+first.length));
        } else {
            console.log(firstLine[i].line.slice(f,l+middleStation[0].start.length));
        }
        
        for (let j = 0; j < middleStation.length; j++){
            let sub = getOneLine(middleStation[j].changeToLine);
            console.log(middleStation[j].changeToLine);
            if (j != middleStation.length - 1) {
                f = sub.indexOf(middleStation[j].start)
                l = sub.indexOf(middleStation[j + 1].start)
                
                if (f > l) {
                    
                    console.log(sub.slice(l, f + middleStation[j].start.length));
                    
                } else {
                    console.log(sub.slice(f, l + middleStation[j+1].start.length));
                }
            } else {
                f = sub.indexOf(middleStation[j].start)
                l = sub.indexOf(last)
                if (f > l) {
                    console.log(sub.slice(l, f + middleStation[j].start.length));
                } else {
                    console.log(sub.slice(f,l+last.length));
                }
            }
            
        }
    }
}
主函数
function main(){
    const rl = readline.createInterface(process.stdin, process.stdout);
    rl.question('input now ', answer => {
        let ans = answer.split(' ')
        if (ans.length === 2) {
            showSubLine(ans)
        } else if (ans.length === 6) {
            searchSub(ans)
        } else if (ans.length === 7) {
            mainFn(ans[1],ans[2])
        } else {
            console.log('input error');
        }
        rl.close()
    })
}
main()

实现的功能

只执行一次地铁信息的写入,后面就可以直接从subway.txt里面读取数据来操作,实现的是换乘最少,只利用该算法的话,把main()改就可以,mainFn()可以不改

存在的问题

  • 地铁线路从网上复制的,但线路的逻辑和换乘之间还有问题,导致有些情况输出错误

  • 算法实现过程无法考虑到用户换乘5条及以上线路的实现,这个我node.js还实现不出来

  • 输出的格式有小问题,我偷懒,只输出用户要坐的线,没有一站一站输出