您的位置:首页 > 编程语言 > Python开发

链表问题---环形单链表的约瑟夫问题

2017-10-13 12:45 387 查看
【题目】

  据说著名犹太历史学家Josephus有过如下故事:在罗马人占领乔塔帕特后,39个犹太人和Josephus及他的朋友躲进一个洞里,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第一个人开始报数,报数到3的人就自杀,再由下一个人重新报1,报数到3的人就自杀,这样依次下去,知道剩下最后一个人时,那个人可以自由选择自己的命运。这就是著名的约瑟夫问题。现在请用单向链表描述该结构并呈现整个自杀过程。

  

  输入:一个环形单向链表的头节点head和报数的值m

  返回:最后生存下来的节点,且这个节点自己组成环形单向链表,其他节点都删掉。

  进阶:如果链表节点数为N,想在时间复杂度为O(N)时完成原问题的要求,该怎么实现?

【基本思路】

  普通解法。

  1、在环形链表中遍历每个节点,不断转圈,不断让每个节点报数。

  2、当报数为m时,就删除当前报数的节点。

  3、删除节点后把剩下的节点继续连成一个环,继续转圈报数,继续删除。

  4、不断的删除,直到只留下一个节点,过程结束。

  普通解法删除一个节点需要遍历m次,一共要删除n-1个节点,所以总的时间复杂度为O(n*m)。

  进阶解法。

  普通解法之所以复杂度高,是因为我们不知道到底哪个节点会留下来。所以依靠不断的遍历删除,直到只留下一个节点。如果我们能不通过遍历,而是直接算出最后活下来的节点是哪个,就可以降低时间复杂度。

  如何计算呢?首先如果环形链表的节点数为n,那么从头节点开始编号,头节点编号为1,下一个编号为2……最后一个节点编号为n。然后考虑如下:

  如果只剩下一个节点,那么幸存的节点就是该节点,编号为1,即Live(1) = 1

  如果剩下两个节点,幸存的节点为Live(2)

  如果剩下三个节点,幸存的节点为Live(3)

  如果剩下i-1个节点,幸存的节点为Live(i-1)

  如果剩下i个节点,幸存的节点为Live(i)

  如果剩下n个节点,幸存的节点为Live(n)

  

  已经知道live(1) = 1,如果再确定Live(i-1)和Live(i)到底是什么关系,我们就可以通过递归过程求出Live(n)。

  首先我们分析如下问题:如果一个节点数为n的链表,编号从头节点到末尾为1~n,如果删除编号为s的一个节点,那么剩下的节点的编号将会如何变化,如图所示。

  


  设原链表的编号为y,删除一个节点后的编号为x,我们可以得到如下的公式 y = (x + s - 1) % n + 1。因此,我们可以根据Live(i-1)以及被删除的节点编号求得Live(i)的值。那么现在的问题就变成了如何求被删除节点的编号。

  


  如图所示。对于每一个节点,如果报数值还不到m,就会一直报数下去,1~n~2n……由图我们可以得到一个报数值A与编号B的关系,即B = (A-1) % n + 1。如果报数报到m,报数的节点就是要删除的节点,那么该节点的编号根据公式可以得出 s = (m-1) % n + 1。

  得到被删除节点的编号s,我们就可以得到Live(i-1)与Live(i)之间的关系:Live(i) = (Live(i-1) + s - 1) % i + 1。其中Live(i-1)可以通过向上递归得到,s = (m-1) % i + 1。两式合并后,结果为 Live(i) = (Live(i-1) + m - 1) % i + 1。

  整个过程总结如下:

  1、遍历链表,得到链表的节点数n,O(n)

  2、根据n和m的值,以及上文推导的Live(i)与Live(i-1)的关系,递归求得幸存节点的编号。该递归是单决策递归且递归为n层,所以时间复杂度为O(n)

  3、根据得到的幸存节点的编号,遍历链表找到该节点,O(n)

  所以总体时间复杂度为O(n)。

【代码实现】

#python3.5
def josephusKill1(head, m):
if head == None or head.next == None or m < 1:
return head
pre = head
while pre.next != head:
pre = pre.next
count = 1
while head != pre:
if count != m:
head = head.next
pre = pre.next
count += 1
else:
pre.next = head.next
head = pre.next
count = 1
return head

def josephusKill2(head, m):
def getLive(n, m):
if n == 1:
return 1
return (getLive(n-1, m) + m - 1) % n + 1

if head == None or head.next == None or m < 1:
return head
n = 1
cur = head
while cur.next != head:
n += 1
cur = cur.next
n = getLive(n, m)
while n-1 != 0:
n -= 1
head = head.next
head.next = head
return head
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  链表 python 约瑟夫