约瑟夫环问题『js实现』

传说罗马人占领了乔塔帕特,41 个犹太人被围堵在一个山洞里。他们拒绝被俘虏,而决定集体自杀,大家决定了一个自杀方案,41 个人围成一个圈,由第 1 个人开始顺时针报数,每报数为 3 的人立刻自杀,然后再由下一个人重新从 1 开始报数,依旧是每报数为 3 的人立刻自杀,依次循环下去。其中两位犹太人并不想自杀,是数学家约瑟夫和他的朋友,他们发现了自杀方案的规律,选了两个特定的位置,最后只剩下他们两人,而活了下来。那么这两个位置分别是什么?

这个问题转化成要解决的通用问题:即 n 个人围成一个圈,这 n 个人的编号从 0——(n-1), 第一个人(编号为0的人)从 1 开始报数,报数为 m 的人离开,再从下一个开始从 1 开始报数,报数为 m 的人离开,依次循环下去,直到剩下最后一个人(也可以剩最后两个,少循环一次就是了),那么,把最后一个人的编号打印出来。

类似问题:有30个小孩儿,编号从1-30,围成一圈依次报数,1、2、3 报到 3 的小孩儿退出这个圈, 然后剩下的小孩儿重新报数 1、2、3,问最后剩下的那个小孩儿的编号是多少?(看到这题几次了0.0)

方法一:数组

function countOff(num,m){
  let players=[];
  for(let i=1;i<=num;i++){
    players.push(i);
  }
  let flag=0;
  while(players.length>1){// 剩下一人,结束条件
    let outPlayerNum=0,len=players.length;
    for(let i=0;i<len;i++){
      flag++;
      if(flag===m){
        flag=0;
        console.log("出局:"+players[i-outPlayerNum]);
        players.splice(i-outPlayerNum,1);
        outPlayerNum++;
      }
    }
  }
  // return players[0];
  console.log("剩下:"+players[0]);
}
// console.log("剩下:"+find(100,5))
countOff(30,3) // 剩下:29

方法二:循环队列实现

队列数据结构是遵循先进先出(也可以说成先来先服务)原则的一组有序的项。队列的尾部添加新元素,并从顶部(头部)移除元素。最新添加的元素必须排在队列的末尾。

function MyCircularQueue() {
  var items = [];
  //向队列插入元素
  this.enQueue = function (value) {
    return items.push(value);
  }
  //删除元素
  this.deQueue = function () {
    return items.shift();
  }
  //查看队列长度
  this.size = function () {
    return items.length;
  }
}

function countOff(m, n) {
  var queue = new MyCircularQueue();
  //将名单存入队列
  for (var i = 1; i <= m; i++) {
    queue.enQueue(i);
  }
  var loser = '';
  while (queue.size() > 1) {
    for (var i = 0; i < n - 1; i++) {
      queue.enQueue(queue.deQueue());
    }
    loser = queue.deQueue();
    console.log('被淘汰的人为:' + loser);
  }
  // return queue.deQueue();
  console.log('获胜者:' + queue.deQueue());
}
countOff(30, 3) // 获胜者:29

方法三:循环链表实现

链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称为指针或链接)组成。双向链表和普通链表的区别在于,在链表中,一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向下一个元素。循环链表可以像链接一样只是单向引用,也可以像双向链表一样有双向引用。循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针,不是引用 null,而是指向第一个元素。

首先将循环双链表的构造函数实现出来,只写了要用到的方法,将文件存为 circularLinkedList.js

function CircularLinkedList() {
  let Node = function(element) {
  this.element = element;
  this.next = null;
  this.prev = null;
  };
  let head = null;
  let tail = null;
  let length = 0;
  
  //从链表尾部添加一个新的项
  this.append = function(element) {
  let node = new Node(element);
  let current;
  
  if(!head) {
  head = node;
  tail = node;
  }else {
  current = tail;
  current.next = node;
  node.prev = current;
  node.next = head;
  tail = node;
  head.prev = tail;
  }
  length++;
  };
  
  //返回元素在链表中的索引,如果链表中没有该元素则返回 -1
  this.indexOf = function(element) {
  let current = head,
  index = 0;
  
  do{
  if(element == current.element) {
  return index;
  }
  index++;
  current = current.next;
  }while(current.prev !== tail);
  
  return -1;
  };
  
  //从链表的特定位置移除一项
  this.removeAt = function(position) {
  if(position > -1 && position < length) {
  let current = head;
  let previous;
  let index = 0;
  
  if(position === 0) {
  head = current.next;
  
  if(length === 1) {
  tail = null;
  }else {
  head.prev = tail;
  tail.next = head;
  }
  }else if(position === length-1) {
  current = tail;
  tail = current.prev;
  tail.next = head;
  head.prev = tail;
  }else {
  if(0 < position < length/2) {
  while(index++ < position) {
  previous = current;
  current = current.next;
  }
  }else {
  let current = tail;
  let index = length-1;
  while(position < index--) {
  previous = current;
  current = current.prev
  }
  }
  previous.next = current.next;
  current.next.prev = previous;
  }
  length--;
  return current.element;
  }else {
  return null;
  }
  };
  
  //根据元素,从链表中移除一项
  this.remove = function(element) {
  let index = this.indexOf(element);
  return this.removeAt(index);
  };
  
  //由于链表项使用了 Node类,就需要重写继承自 JS 对象默认的 tostring 方法,让其只输出元素的值
  this.toString = function() {
  let current = head;
  let string = '';
  
  do{
  string += current.element + ' ';
  current = current.next;
  }while(current.prev !== tail);
  
  return string;
  };
  
  //返回链表包含的元素个数。与数组的 length 属性类似
  this.size = function() {
  return length;
  };
  
  //tail 变量是该构造函数的私有变量(这意味着它不能被构造函数外部访问和更改)。但是,如果我们需要在外部循环访问链表,就需要提供一种获取链表的第一个或最后一个元素的方法
  this.getTail = function() {
  return tail;
  };
  }

有了循环链表这种数据结构,我们就可以拿来解决问题了:

function josephRing(n, m) {
if(n<=1 || m<1) {
console.log("you can't play Joseph's game. n must be bigger than 1, m must be bigger than 0");
return;
}
let circular = new CircularLinkedList();

for(let i=0;i<n;i++) {
circular.append(i);
}

//给实例化对象添加属性,为当前项,将链表的最后一个元素引用赋值给它
circular.current = circular.getTail();

//给实例化对象添加方法,将 current 往前移动 m 次,移动第一次时指向的是第一个元素,因为 current 最开始指向的是最后一个元素
circular.advance = function(m) {
while(m>0) {
this.current = this.current.next;
m--;
}
};

//当链表只剩最后一项时,循环结束
while(circular.size() > 1) {
circular.advance(m);
//以 current 元素为参数删除该项
circular.remove(circular.current.element)}
console.log(parseInt(circular.toString()) + 1 + ' is the winner.');
}

let start = new Date().getTime();
josephRing(10000,3);
let end = new Date().getTime();
console.log('====' + (end - start) + '====');

方法四:递归

function countOff(N, M) {
  if (N < 1 || M < 1) {
    return;
  }
  let source=[];
  for(let i=1;i<=N;i++){
    source.push(i);
  }
  // const source = Array(...Array(N)).map((_, i) => i + 1);
  let index = 0;
  while (source.length>1) {// 剩下一人,结束条件
    index = (index + M - 1) % source.length;
    console.log('出局:'+source[index]);
    source.splice(index, 1);
  }
  console.log('剩下:'+source[0])
}
countOff(30,3) // 剩下:29

文章参考:
https://zhuanlan.zhihu.com/p/52993728
https://blog.csdn.net/qq_43043859/article/details/100987897

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值