golang解决约瑟夫问题,单向循环链表实现

据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39
个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus
和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。——摘自百度百科

这是经典的约瑟夫环,并且期望剩余的人数是2,我们这里也做了一个通配,可以输入期望剩余的人数,比如期望剩余1个,那么31这个位子可以存活。当然解决关键就是得用到单向循环链表,语言自然选择的是golang,当然这算是第一版本以实现功能为主,还有很多待改进的地方。

Let’s go

第一.我们先演绎一下,我们从1开始报数,到三的时候自杀,然后以此类推6、9、12…可以看得出来第一圈是以3的倍数自杀,最后到39跑完一圈。
在这里插入图片描述
第二.我们再跑一圈,第一圈的最后一个是39,那么+3以后这次应该是1自杀,以此类推+3的偏移量,如果中间遇到已经死亡的,我们不计数。
在这里插入图片描述
这样第二圈跑完下来最后一个自杀的应该是41。我们就按照以上思路进行编写,其实程序也就是我们的思考过程,只不过我们只是做了前期归纳演算,最后的规律以及实现方式还得用计算机来实现这就是计算机存在的意义。

第三.我们可以从上面的图里看出,这是一个环状的结构,那么我们要实现这样的结构,就可以用一种数据结构来表示——单向循环链表。我们用一张图来解释这个链表:
在这里插入图片描述
从图中可以看到,链表的尾部元素的Next是永远指向Head的,这样就形成了一个环。

好了,废话不多说了,上代码。

1.定义结构体,这里Index表示表示元素的下标,Killed表示是否已经被杀,false表示存活。

type Node struct {
	Index  int
	Killed bool
	Next   *Node
}

type LinkedList struct {
	Head *Node
	Len  int
	*Node
}

2.插入元素,这里只讨论尾部插入,其余的不做研究。当Head为nil的时候,插入第一个元素就是Head,同时Head的Next指向的是Head。其余新增的元素的Next一定得指向Head,形成闭环。

func (list *LinkedList) insert(index int) {
	node := &Node{Index: index}
	list.Len++

	if list.Head == nil {
		list.Head = node
		list.Head.Next = node
		list.Node = node
		return
	}

	curr := list.Head
	for curr.Next != list.Head {
		curr = curr.Next
	}

	curr.Next = node
	node.Next = list.Head
}

3.实现报数自杀。这里可输入期望存活的人数,返回的是一个数组,数组里存的是下标。offset作为偏移,计数到3就自杀一个,同时重置offset,这里值得注意的是寻找下一个存活的人,每次寻找都会做一个循环查找,直到找到还没自杀的人为止。

func (list *LinkedList) selfKilled(aliveNums int) []int {
	curr := list.Head
	offset := 1
	killNum := list.Len
	for killNum > aliveNums {
		if offset%3 == 0 {
			curr.Killed = true
			offset = 0
			killNum--
		}
		offset++
		curr = curr.Next
		for curr.Killed {
			curr = curr.Next
		}
	}

	aliveArr := make([]int, 0)
	h := list.Head
	for h.Next != list.Head {
		h = h.Next
		if !h.Killed {
			aliveArr = append(aliveArr, h.Index)
		}
	}

	return aliveArr
}

4.遍历插入元素,实现41个人排队手拉手。

func josephusProblem(aliveNums int) []int {
	list := &LinkedList{}
	for i := 1; i < 42; i++ {
		list.insert(i)
	}
	return list.selfKilled(aliveNums)
}

5.最后必不可少的main函数入口。

func main() {
	expectedAliveNums := 2
	fmt.Printf("real alive indexs:%v\n", josephusProblem(expectedAliveNums))
}

6.我们期望的值是16和31,这里不做test表格驱动测试了,就用简单的main输出。

real alive indexs:[16 31]

7.收工,整体下来主要逻辑在于selfKilled方法,这里循环遍历有点啰嗦,还有待改进,留着以后再做调整吧。
最后是完整代码:

package main

import (
	"fmt"
)

func main() {
	expectedAliveNums := 2
	fmt.Printf("real alive indexs:%v\n", josephusProblem(expectedAliveNums))
}

type Node struct {
	Index  int
	Killed bool
	Next   *Node
}

type LinkedList struct {
	Head *Node
	Len  int
	*Node
}

func (list *LinkedList) insert(index int) {
	node := &Node{Index: index}
	list.Len++

	if list.Head == nil {
		list.Head = node
		list.Head.Next = node
		list.Node = node
		return
	}

	curr := list.Head
	for curr.Next != list.Head {
		curr = curr.Next
	}

	curr.Next = node
	node.Next = list.Head
}

func josephusProblem(aliveNums int) []int {
	list := &LinkedList{}
	for i := 1; i < 42; i++ {
		list.insert(i)
	}
	return list.selfKilled(aliveNums)
}

func (list *LinkedList) selfKilled(aliveNums int) []int {
	curr := list.Head
	offset := 1
	killNum := list.Len
	for killNum > aliveNums {
		if offset%3 == 0 {
			curr.Killed = true
			offset = 0
			killNum--
		}
		offset++
		curr = curr.Next
		for curr.Killed {
			curr = curr.Next
		}
	}

	aliveArr := make([]int, 0)
	h := list.Head
	for h.Next != list.Head {
		h = h.Next
		if !h.Killed {
			aliveArr = append(aliveArr, h.Index)
		}
	}

	return aliveArr
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水番丘山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值