约瑟夫环数据结构实验报告(约瑟夫问题之数据结构探索)
约瑟夫问题之数据结构探索
引言:约瑟夫问题(Josephus problem)是一种经典的问题,它源于一个古老的传说:名叫Josephus的大学生在与罗马军队作战中被困在了洞穴里,面对无法逃脱的命运,Josephus提出了一个问题:由于人数过多无法逃脱,那么任选一个人开始,依次报数,报数到m的人出列,将其杀掉,然后从下一个人重新开始报数,重复上述过程直到只剩下一个人,这个人即为胜利者。本次实验旨在利用数据结构解决这个问题,并对不同的数据结构解决该问题的效率进行对比。
实验过程:
解法一:循环链表
循环链表是一种链表结构,并且最后一个结点指向首结点形成环形结构。因此,循环链表结合于本问题,可以不用真的删除结点,当删除结点时,只需要改变指向即可。以下为该解法的核心代码:
``` python class Node(object): def __init__(self, item): self.item = item self.next = None class Josephus(object): def __init__(self, start, step, people): self.start = start # 起始编号 self.step = step # 报数的步长 self.people = people # 总人数 self.__link = None # 私有成员变量,表示循环链表 def __createLink(self): # 构造循环链表 head = Node(1) pre = head for i in range(2, self.people+1): cur = Node(i) pre.next = cur pre = cur pre.next = head self.__link = head def findTheWinner(self): self.__createLink() # 找到起始编号对应的结点位置 for i in range(1, self.start): self.__link = self.__link.next # 只有一个人存活时终止循环 while self.__link != self.__link.next: # 找到要删除的结点前驱结点,逐个删除 for i in range(1, self.step): self.__link = self.__link.next print(\"编号为{}的人出列\".format(self.__link.item)) self.__link.item = self.__link.next.item self.__link.next = self.__link.next.next print(\"胜利者编号为:{}\".format(self.__link.item)) ```解法一中主要使用了循环链表来实现,其思路简单,代码易于实现。但是当总人数庞大时,会使得删除操作的效率变低,因为需要逐个定位要删除的结点的前驱结点。因此有必要寻找其他高效的解法。
解法二:数组
解法二中我们尝试使用数组来进行模拟。记数组a[i]表示原始数组中第i个出局的人的编号,n表示初始人数,k表示报的数,m表示出局的人数,f表达式为f[i]=(f[i-1]+k)%n,i表示出局的人数。以下为该解法的核心代码:
``` python class Josephus(object): def __init__(self, start, step, people): self.start = start # 起始编号 self.step = step # 报数的步长 self.people = people # 总人数 def findTheWinner(self): a = [i+1 for i in range(self.people)] # 构造原始数组 i = self.start-1 # 定位首个删除的位置 for j in range(self.people-1): i = (i + self.step - 1) % self.people # 计算下一个被删除的位置 print(\"编号为{}的人出列\".format(a[i])) a.pop(i) # 弹出该结点 self.people -= 1 # 总人数-1 print(\"胜利者编号为:{}\".format(a[0])) ```解法二中我们使用数组随机访问的速度较快,删除操作也变得容易。但是若采用弹出数组元素的方式,则可能因为底层数组的移动操作导致效率降低。同时,本解法对于负数的运算没有处理,当k<0时也会出现bug。另外,当人数过大时,数组存储所需的空间也会非常庞大。
解法三:双向循环链表
解法三中,我们尝试使用双向循环链表,其与前两种解法不同的是,双向链表可以方便地定位到要删除的结点的前驱结点,并实现删除操作。以下为该解法的核心代码:
``` python class Node(object): def __init__(self, item): self.item = item self.next = None self.prev = None class Josephus(object): def __init__(self, start, step, people): self.start = start # 起始编号 self.step = step # 报数的步长 self.people = people # 总人数 self.__link = None # 私有成员变量,表示双向循环链表 def __createLink(self): # 构造双向循环链表 head = Node(1) pre = head for i in range(2, self.people+1): cur = Node(i) pre.next = cur cur.prev = pre pre = cur pre.next = head head.prev = pre self.__link = head def findTheWinner(self): self.__createLink() # 找到起始编号对应的结点位置 for i in range(1, self.start): self.__link = self.__link.next # 只有一个人存活时终止循环 while self.__link != self.__link.next: # 找到要删除的结点前驱结点,逐个删除 for i in range(1, self.step): self.__link = self.__link.next # 找到要删除的结点的前驱结点 self.__link.prev.next = self.__link.next self.__link.next.prev = self.__link.prev print(\"编号为{}的人出列\".format(self.__link.item)) self.__link = self.__link.next print(\"胜利者编号为:{}\".format(self.__link.item)) ```解法三中使用了双向循环链表解决了循环链表定位的问题,而且可以方便地从前或后移动。同时,本解法与数组相比,内存占用非常小,而且可以实现高效的删除操作。但是,本解法不同于解法二中的方便随机访问,导致定位过程中的空间复杂度较大。
结论:
总之,在本次实验中,我们分别尝试了三种不同的数据结构来解决约瑟夫问题,即循环链表、数组和双向循环链表,它们各自有其优势和不足。其中,循环链表简单易懂,但是效率较低;数组速度非常快,但是空间占用较多,删除操作不够方便;双向循环链表可以方便地移动,实现高效的删除操作,但是定位元素的时间较长。从效率、代码复杂度以及空间复杂度三个方面出发,建议采用双向循环链表解决约瑟夫问题较为合适。
版权声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。