约瑟夫问题

著名的法国数学家加斯帕•蒙日,曾在《数学的游戏问题》中讲了一个故事:有15个教徒和15个非教徒在海上遇险,必须将一半的人投入海中,其他的人才能幸免于难,于是想了一个办法,30个人围成一个圆圈,从第一个人开始依次报数,每数到9的人,就被扔入大海,之后从被扔入海中的下一个开始数,依次循环,直到剩余15人,问 怎么排位置,才能让扔入大海的人全为非教徒。

这就是著名的约瑟夫问题。

这么一想,一圈人依次报数,有点和循环链表类似,而且还是单向的。

所以我们可以考虑采用一个单向的循环链表来解决这个问题。因为涉及到元素的移除,我们可以设置一个前驱结点,来减少链表的遍历。

 private int count;
        public int Count
        {
            get
            {
                return count;
            }
        }
        //尾节点
        private Node tailNode;
        //当前结点的前驱结点
        private Node currentPrev;

        //当前结点的值
        public object Current
        {
            get
            {
                return currentPrev.next.item;
            }
        } 

添加元素首先:

 public void Add(object value)
        {
            Node newNode = new Node(value);
            if (tailNode==null)
            {
                tailNode = newNode;
                tailNode.next = newNode;
                currentPrev = newNode;
            }
            else
            {
                newNode.next = tailNode.next;
                tailNode.next = newNode;
                //如果只有一个元素的时候
                if (currentPrev==tailNode)
                {
                    currentPrev = newNode;
                }
                tailNode = newNode;
            }
            count++;
        }

移除当前元素:

 public void RemoveCurrentNode()
        {
            //空表
            if (tailNode == null)
            {
                throw new NullReferenceException("Current List Null");
            }
            else if (count == 1) 
            {
                tailNode = null;
                currentPrev = null;
            }
            else
            {
                if (currentPrev.next==tailNode)
                {
                    //删除的是尾结点
                    tailNode = currentPrev;
                }
                currentPrev.next = currentPrev.next.next;
            }
            count--;
        }

一定要注意指针指向的设置顺序。

数数的方法:

 public void Move(int step)
        {
            if (step < 0) 
            {
                throw new ArgumentOutOfRangeException("step", "不能小于0");
            }
            if (tailNode==null)
            {
                throw new NullReferenceException("list is null");
            }
            //移动指针
            for (int i = 0; i < step; i++)
            {
                currentPrev = currentPrev.next;
            }
        }

输出当前集合中的元素:

 public  string ConsoleContent()
        {
            if (tailNode==null)
            {
                return "list is null";
            }
            string str = "";
            Node temp = tailNode.next;
            for (int i = 0; i < count; i++)
            {
                str += temp.item.ToString() + " ";
                temp = temp.next;
            }
            return str;
        }

接下来来测试一下,假如有10个人,每当数到6的人扔进海中,那么我们来看一下最后的顺序:

 SingleCircleLinkList list = new SingleCircleLinkList();
            string ss = string.Empty;
            Console.WriteLine("请输入总的人数");
            int count = int.Parse(Console.ReadLine());
            //假设当数到M的时候,出队
            Console.WriteLine("请输入M的值");
            int num = int.Parse(Console.ReadLine());
            Console.WriteLine("现在开始");
            for (int i = 1; i <= count; i++)
            {
                list.Add(i);
            }
            Console.WriteLine("一共有:" + list.ConsoleContent());
            while (list.Count > 1) 
            {
                list.Move(num - 1);
                //记录出去的顺序
                ss += list.Current.ToString() + " ";
                list.RemoveCurrentNode();
                Console.Write("\r\n剩余的人:"+list.ConsoleContent());
                Console.Write("  开始报数的是:"+list.Current);
            }
            Console.WriteLine("\n\r 顺序为:" + ss + list.Current);
            Console.ReadKey();

最后的顺序为 6  2  9  7  5  8  1  10  4   3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值