第六节:手撸“循环链表”及约瑟夫问题的解决

1. 约瑟夫问题

  加斯帕·蒙日是法国数学家,他在《数学的游戏问题》中讲了一个故事:15个教徒和15个非教徒在深海上遇险,必须将一半的人投入海中,其余人才能幸免于难,于是想了一个方法,30个人 围城一圈,从第一个人开始依次报数,每数到第九个的人就将他扔向大海,如此循环进行直到仅剩余15个人为止。问怎样的排法,才能使每次投入大海的都是非教徒?这就是著名的约瑟夫问题,又称约瑟夫环。 

  我们将问题进行一下抽象:N个人进行手尾排列,从第一个开始数数,数到第X个停止,然后把第X个剔除;然后接着数,数到第X个停止,并将其移除,以此类推,直到达到要剔除的个数数目,把这些剔除位置的记录下来,就是我们所需要的位置。很显然,循环链表非常适合解决这个问题。

2. 解决思路

(1). 先抽象出一个单向的循环链表,要有尾节点,尾节点指向头节点;可以用当前节点的前驱节点来表示当前节点。

 代码如下:

 节点

 1     /// <summary>
 2     /// 循环链表节点
 3     /// </summary>
 4     public class CircleNode<T>
 5     {
 6         /// <summary>
 7         /// 存放数据
 8         /// </summary>
 9         public T Data { get; set; }
10 
11         /// <summary>
12         /// 下一个节点
13         /// </summary>
14         public CircleNode<T> Next { get; set; }
15 
16         public CircleNode()
17         {
18                 
19         }
20 
21         public CircleNode(T data)
22         {
23             Data = data;
24         }
25     }
CircleNode

循环链表

  1  /// <summary>
  2     /// 手写单链表
  3     /// </summary>
  4     public class MySingleLinkedList<T>
  5     {
  6         private Node<T> _header;  //头结点
  7         private int _count;  //元素个数
  8         public MySingleLinkedList()
  9         {
 10 
 11         }
 12         public MySingleLinkedList(T data)
 13         {
 14             _header = new Node<T>(data);
 15             _count++;
 16         }
 17         //只能读
 18         public int Count
 19         {
 20             get
 21             {
 22                 return _count;
 23             }       
 24         }
 25         /// <summary>
 26         /// 根据索引获取元素
 27         /// </summary>
 28         /// <param name="index"></param>
 29         /// <returns></returns>
 30         public Node<T> GetNodeByIndex(int index)
 31         {
 32             if (index < 0 || index>=_count)
 33             {
 34                 throw new ArgumentOutOfRangeException("索引越界");
 35             }
 36             Node<T> tempNode = _header;
 37             //当index为0的时候,不进入for循环
 38             for (int i = 0; i < index; i++)
 39             {
 40                 tempNode = tempNode.Next;
 41             }
 42             return tempNode;
 43         }
 44 
 45         //索引器获取和设置数据
 46         public T this[int index]
 47         {
 48             get
 49             {
 50                 return GetNodeByIndex(index).Data;
 51             }
 52             set
 53             {
 54                 GetNodeByIndex(index).Data = value;
 55             }
 56         }
 57 
 58         /// <summary>
 59         /// 添加元素(最后)
 60         /// </summary>
 61         /// <param name="data"></param>
 62         public void Add(T data)
 63         {
 64             Node<T> newNode = new Node<T>(data);
 65             if (_header==null)  //表示添加的是第一个节点
 66             {
 67                 _header = newNode;
 68             }
 69             else
 70             {
 71                 var lastNode = GetNodeByIndex(_count - 1);
 72                 lastNode.Next = newNode;
 73             }       
 74             _count++;
 75         }
 76 
 77         /// <summary>
 78         /// 插入元素
 79         /// </summary>
 80         /// <param name="index">索引</param>
 81         /// <param name="data">数据</param>
 82         public void Insert(int index,T data)
 83         {
 84             if (index < 0||index>_count)
 85             {
 86                 throw new ArgumentOutOfRangeException("索引越界");
 87             }
 88             var newNode = new Node<T>(data); //新节点
 89             if (index==0)
 90             {
 91                 //头结点为空,直接插入头结点即可
 92                 if (_header==null)
 93                 {
 94                     _header = newNode;
 95                 }
 96                 //头结点有元素,需要把头结点的位置让出来
 97                 else
 98                 {
 99                     newNode.Next = _header;
100                     _header = newNode;
101                 }
102             }
103             else
104             {
105                 var preNode = GetNodeByIndex(index-1);  //查找插入位置的前驱节点
106                 var nextNode = preNode.Next;            //插入位置的后继节点
107                 preNode.Next = newNode;        //前驱结点的后继节点为新节点
108                 newNode.Next = nextNode;       //新节点的后继节点执行原来前驱的后继
109             }
110             _count++;             
111         }
112 
113         /// <summary>
114         /// 根据索引删除元素
115         /// </summary>
116         /// <param name="index">索引</param>
117         public void RemoveAt(int index)
118         {
119             if (index < 0 || index >= _count)
120             {
121                 throw new ArgumentOutOfRangeException("索引越界");
122             }
123             if (index==0)  //删除头结点
124             {
125                 if (_header==null)
126                 {
127                     throw new ArgumentOutOfRangeException("索引越界");
128                 }
129                 _header = _header.Next;
130             }
131             else
132             {
133                 var preNode = GetNodeByIndex(index - 1); //删除节点的前驱节点
134                 if (preNode.Next==null)
135                 {
136                     throw new ArgumentOutOfRangeException("索引越界");
137                 }
138                 preNode.Next = preNode.Next.Next;     //如果删除的是最后一个节点,那么它的前一个节点指向null
139             }
140             _count--;
141         }
142         /// <summary>
143         /// 元素输出
144         /// </summary>
145         /// <returns></returns>
146         public override string ToString()
147         {
148             string s = "";
149             Node<T> temp = _header;
150             while (temp != null)
151             {
152                 s += temp.ToString() + " ";
153                 temp = temp.Next;
154             }
155             return s;
156         }
157 
158     }
MySingleLinkedList

测试链表

 1  {
 2                 MyCircleLinkedList<int> cList = new MyCircleLinkedList<int>();
 3                 cList.Add(1);
 4                 cList.Add(2);
 5                 cList.Add(3);
 6                 cList.Add(4);
 7                 cList.Add(5);
 8                 cList.Add(6);
 9                 cList.Add(7);
10                 Console.WriteLine($"当前节点值为:{cList.currentNodeValue}");
11                 cList.Move(2);
12                 Console.WriteLine($"当前节点值为:{cList.currentNodeValue}");
13                 cList.RemoveCurrentNode();
14                 cList.Move(3);
15                 Console.WriteLine($"当前节点值为:{cList.currentNodeValue}");
16                 var str1 = cList.ToString();
17                 Console.WriteLine(str1);
18                 Console.ReadKey();
19             }

运行结果:

(2). 利用循环链表的环解决该问题, 需要记录当前节点,默认当前节点为第一个,所以它的前驱节点为尾节点,然后设计移动算法,将当前节点移动n步,然后删除当前节点,那么下一个节点就变为当前节点了,然后接着移动,直到人数符合要求为止。

 1  {
 2                 MyCircleLinkedList<int> cList = new MyCircleLinkedList<int>();
 3                 string result = string.Empty;
 4                 Console.WriteLine("请输入总人数:");
 5                 int count = int.Parse(Console.ReadLine());
 6                 Console.WriteLine("请输入出队的位置:");
 7                 int outPosition = int.Parse(Console.ReadLine());
 8                 Console.WriteLine("请输入出队的个数:");
 9                 int outPerson = int.Parse(Console.ReadLine());
10 
11                 Console.WriteLine("游戏开始");
12                 for (int i = 1; i <= count; i++)
13                 {
14                     cList.Add(i);
15                 }
16                 Console.WriteLine($"所有人的人:{cList.ToString()} ");
17                 //此处的业务用来表示剩下多少个人
18                 while (cList.Count > (count - outPerson))
19                 {
20                     //移动的步数=位置数-1
21                     cList.Move(outPosition-1);
22                     result += cList.currentNodeValue.ToString() + " ";
23                     cList.RemoveCurrentNode();      //删掉当前节点出队
24                     if (cList.Count == (count - outPerson))
25                     {
26                         Console.WriteLine($"剩余的人为:{cList.ToString()} ");
27                     }
28                     else
29                     {
30                         Console.WriteLine($"剩余的人为:{cList.ToString()} 开始报数的人为:{cList.currentNodeValue.ToString()} ");
31                     }
32 
33                 }
34                 Console.WriteLine("出队位置的顺序: " + result);
35             } 

运行结果:

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

猜你喜欢

转载自www.cnblogs.com/yaopengfei/p/12720965.html