【数据结构与算法JavaScript实现与应用】链表 —— 幸存者游戏(约瑟夫环)

987 阅读3分钟

前言

最近在看《数据结构与算法JavaScript描述》一书,在链表一章最后有个关于谁能幸存下来的习题比较有趣,现实中经常会有类似的场景,故将此题分享出来

什么是约瑟夫环

问题描述如下:

传说在公元1世纪的犹太战争中,犹太历史学家弗拉维奥•约瑟夫斯和他的40个同胞被罗马士兵包围。犹太士兵决定宁可自杀也不做俘虏,于是商量出了一个自杀方案。他们围成一个圈,从一个人开始,数到第三个人时将第三个人杀死,然后再数,直到杀光所有人。约瑟夫和另外一个人决定不参加这个疯狂的游戏,他们快速地计算出了两个位置,站在那里得以幸存。写一段程序将n个人围成一圈,并且第m个人会被杀掉,计算一圈人中哪些人(m-1个)最后会存活。使用循环链表解决该问题。

我们暂且就把由这n个人围成,每次淘汰第m个人的动态圈称为约瑟夫环吧。

介绍解题思路前我们先简单了解下什么是链表:

链表

链表是由一组节点组成的集合,每个节点都使用一个对象的引用指向它的后继。指向另一个节点的引用叫做链。 下面介绍下链表相较于数组的有点,这也是此题为什么要用链表结构解题的原因:

1.JavaScript中数组被实现成对象,与其他语言的数组相比,效率很低;2.在数组中添加和删除元素很麻烦,因为需要将数组中的其他元素向前或向后平移

链表主要有以下三类:

单向链表

双向链表

循环链表

解题思路和代码

因为n个人是围成一圈,很明显我们要用到循环链表,所以我们要先创建一个循环链表(注意除了这n个人外,需要添加一个Head节点指向第一个人,也方便我们在遍历节点时避免陷入死循环),每干掉一个人就相当于从链表中删除一个节点,因为每干掉一个人后要重新从1开始数,所以需要将原先的Head节点移动至刚才被干掉的那个人的位置,就这样每次都干掉Head节点后的第m个人,如此循环,直至链表剩下的节点数小于m,此时人数不够,游戏结束。

注:本文不对链表的实现做详细解释,具体可参考学习JavaScript数据结构(二)——链表

本题具体代码如下:

//创建链表节点
function Node(name){
    this.name=name;
    this.next=null;
}
//链表类
function Ilist(){
    this.head=new Node("head");
    this.head.next=this.head;
    this.find=find;
    this.insert=insert;
    this.display=display;
    this.findPrevious=findPrevious;
    this.remove=remove;
    this.count=count;
    this.back=back;
}
//根据节点内容找到当前节点
function find(ele){
    var currentNode=this.head;
    while(currentNode.name!=ele&&!(currentNode.next.name=="head")){
        currentNode=currentNode.next;
    }
    return currentNode;
}
//根据节点内容找到当前节点的前一个节点
function findPrevious(ele){
    var currentNode=this.head;
    while((currentNode.next.name!=ele)&&!(currentNode.next==null)&&!(currentNode.next.name=="head")){
        currentNode=currentNode.next;
    }
    return currentNode;
}
//在内容为ele的节点后插入内容为newEle的新节点
function insert(newEle,ele){
    var node=new Node(newEle);
    var oldNode=this.find(ele);
    node.next=oldNode.next;
    oldNode.next=node;
}
//删除节点
function remove(ele){
    var preNode=this.findPrevious(ele);
    if(!(preNode.next==null)){
        preNode.next=preNode.next.next;
    }
}
//显示链表所有节点的内容
function display(){
    var currNode=this.head;
    while(!(currNode.next==null)&&!(currNode.next.name=="head")){
        console.log(currNode.next.name);
        currNode=currNode.next;
    }
}
//计算链表节点个数
function count(){
    var qty=0;
    var currNode=this.head;
    while(!(currNode.next==null)&&!(currNode.next.name=="head")){
        qty++;
        currNode=currNode.next;
    }
    return qty;
}
//内容为ele的节点向后移动step步
function back(ele,step){
    var preNode=this.findPrevious(ele);
    var currNode=preNode.next;
    var prebackNode=currNode;
    for(i=1;i<step;i++){
        prebackNode=prebackNode.next;
    }
    var backnode=prebackNode.next;
    preNode.next=currNode.next;
    currNode.next=prebackNode.next.next;
    backnode.next=currNode;   
}
//计算最终的幸存者,manQty为参加游戏的人数,killNo为被干掉者编号
function survive(manQty,killNo){
    var people=new Ilist();
    for(i=1;i<=manQty;i++){
        var person="位置"+i;
        var prePerson;
        if(i==1){
            prePerson="head";
        }else{
            prePerson="位置"+(i-1);
        }
        people.insert(person,prePerson);      
    }
    people.display();
    console.log('游戏总人数:'+people.count());
    console.log('开始游戏');
    while(people.count()>=killNo){
        var killedNode=people.head;
        for(i=1;i<=killNo;i++){
            killedNode=killedNode.next;
        }
        var killedPerson=killedNode.name;
        var killedPreNode=people.findPrevious(killedPerson);
        people.back("head",killNo);
        people.remove(killedPerson);
        console.log('干掉:'+killedPerson);
        console.log('还剩:'+people.count()+"人");        
    }
    console.log('最终幸存'+people.count()+"人");
    people.display();
}

调用方法survive(40,3),得出最终的幸存者所处位置:

顺便再来看下链表结构