1.1.1. 一、什么是约瑟夫(Josephu)问题:

Josephu 问题为:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1 开始报数,数到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推, 直到所有人出列为止,由此产生一个出队编号的序列。

1.1.2. 二、用单向循环链表解决约瑟夫问题。

1、分析:

用一个不带头结点的单向循环链表来处理 Josephu 问题:先构成一个有 n 个结点的单循环链表,然后由 k 结点起从 1 开始计数,计到 m 时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从 1 开始计数,直到最后一个结点从链表中删除。

2、代码实现:

  • 节点的结构:
// 链表
type Persion struct {
    Num  int
    Next *Persion // 下一个节点
}
  • 创建一个有 num 个节点的单向的环形链表。
// 创建一个有 num 个节点的单向的环形链表
// 返回第一个节点
func AddNodes(num int) *Persion {

    first := &Persion{}   //第一个结点
    curNode := &Persion{} //空结点

    //判断
    if num < 1 {
        fmt.Println("num 的值不对")
        return first
    }
    //循环的构建这个环形链表
    for i := 1; i <= num; i++ {
        newNode := &Persion{Num: i,}
        if i == 1 { //第一个节点
            first = newNode
            curNode = newNode
            curNode.Next = first
        } else {
            curNode.Next = newNode
            curNode = newNode    //curNode往下移动
            curNode.Next = first //构造环形链表
        }
    }
    return first
}
  • 显示链表。
//显示
func ShowPersion(first *Persion) {
    //处理一下如果环形链表为空
    if first.Next == nil {
        fmt.Println("链表为空,没有节点...")
        return
    }

    //从 first 开始遍历
    curNode := first
    for {
        fmt.Printf("编号=%d ->", curNode.Num)
        //遍历到最后
        if curNode.Next == first {
            break
        }
        //动到下一个
        curNode = curNode.Next
    }
}
  • 解决约瑟夫问题。
//约瑟夫问题。
/**
    first :第一个节点
    startNo :从第几个节点开始计数
    countNum :计数多少次。才删除节点。
*/
func PlayGame(first *Persion, startNo int, countNum int) {

    //空的链表我们单独的处理
    if first.Next == nil {
        fmt.Println("空的链表,没有节点")
        return
    }
    //定义辅助变量,帮助我们删除节点。tail 永远设置为 first 的前一个。
    tail := first
    for {
        if tail.Next == first { //说明 tail 到了最后
            break

        }
        tail = tail.Next
    }

    //让 first  移动到 startNo。tail 永远设置为 first 的前一个。
    for i := 1; i <= startNo-1; i++ {
        first = first.Next
        tail = tail.Next
    }
    fmt.Println()
    //开始数 countNum,  然后就删除 first 指向的节点
    for {

        //开始数 countNum 次,找到要删除的元素first
        for i := 1; i <= countNum-1; i++ {
            first = first.Next
            tail = tail.Next

        }
        fmt.Printf("节点编号为%d 出圈 \n", first.Num)
        //删除 first 节点
        first = first.Next // 往下移动一个节点
        tail.Next = first // 删除原来的first
        //判断如果 tail == first,  圈子中只有一个节点.
        if tail == first {
            break
        }
    }

    fmt.Printf("最后一个节点编号为%d 出圈 \n", first.Num)

}

关键点:

1、first 是要删除的节点。

2、tail 永远设置为 first 的前一个(也就是first 往前移动几次,tail 也跟着往前移动几次)。方便删除的时候使用tail。

  • 测试:
func main() {
    //创建链表
    first := AddNodes(5)
    // 显示
    ShowPersion(first)
    //约瑟夫问题
    PlayGame(first, 3, 4)
}

results matching ""

    No results matching ""