约瑟夫环
已知N个人围成一个圆圈,每隔M个元素,就清除该元素,剩下的继续:每隔M个元素,就清除该元素,直到元素只有(M-1)个为止。
n个人排成一圈。从某个人开始,按顺时针方向依次编号。
从编号为1的人开始顺时针“一二一”报数,报到2的人退出圈子。这样不断循环下去,圈子里的人将不断减少。由于人的个数是有限的,因此最终会剩下一个人。
试问最后剩下的人最开始的编号。
约瑟夫环是一个循环列表问题。我们用三种线性数据结构来分别实现,同时打印算法用时。
第一种:移除指定位置的元素,每隔N个元素就清除,使用List<T>的RemoveAt(index)
第二种:使用T[] array,bool[] flagArray,第一个数组代表集合数据,第二个数组代表是否已删除标记
第三种:我们使用元组列表来实现Tuple<T,bool> 第一项代表集合数据,第二项代表是否已删除标记
新建.net 5.0控制台应用程序JosephRingDemo,输入测试程序如下:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
namespace JosephRingDemo
{
class Program
{
static void Main(string[] args)
{
int divide = 3;
//列表
List<string> stringlist = new List<string>()
{
"大魔法师", "山丘之王", "圣骑士", "血魔法师",
"剑圣", "先知", "牛头人酋长", "暗影猎手",
"死亡骑士", "巫妖", "地穴领主", "恐惧魔王",
"恶魔猎手", "丛林守护者", "守望者", "月之女祭司",
"驯兽师", "黑暗游侠", "深渊魔王", "娜迦女海巫",
"火焰领主", "炼金术士", "地精修补匠", "熊猫酒仙",
"AA","BB","CC","DD","EE","FF","GG","HH","II","JJ","KK","LL","MM",
"NN","OO","PP","QQ","RR","SS","TT","UU","VV","WW","XX","YY","ZZ"
};
//数组
string[] array = stringlist.ToArray();
bool[] flagArray = new bool[array.Length];
//元组的第一项代表姓名[string],第二项代表是否已删除标志[bool](默认都未删除)
List<Tuple<string, bool>> tupleList = new List<Tuple<string, bool>>();
for (int i = 0; i < stringlist.Count; i++)
{
tupleList.Add(Tuple.Create(stringlist[i], false));
}
JosephRing(stringlist, divide);
Console.WriteLine($"【使用List<string>集合】当前集合剩余元素【{string.Join(",", stringlist)}】");
JosephRing(array, flagArray, divide);
List<string> tempList = new List<string>();
for (int i = 0; i < array.Length; i++)
{
if (!flagArray[i])
{
tempList.Add(array[i]);
}
}
Console.WriteLine($"【使用string[]和bool[]集合】当前集合剩余元素【{string.Join(",", tempList)}】");
JosephRing(tupleList, divide);
Console.WriteLine($"【使用List<Tuple<string, bool>>集合】当前集合剩余元素【{string.Join(",", tupleList.FindAll(tuple => !tuple.Item2).Select(tuple => tuple.Item1))}】");
Console.ReadLine();
}
/// <summary>
/// 约瑟夫环一:【使用列表集合】每隔divide个元素就从集合中删除
/// </summary>
/// <param name="list">集合</param>
/// <param name="divide">分割计数,每隔几个移除</param>
static void JosephRing(List<string> list, int divide)
{
if (list == null)
{
throw new ArgumentNullException("集合list不能为空");
}
if (divide <= 0)
{
throw new ArgumentOutOfRangeException("分割计数必须为正整数");
}
Contract.EndContractBlock();
Stopwatch stopwatch = Stopwatch.StartNew();
int cnt = 0;
//记录移除的元素集合
List<string> removeList = new List<string>();
//如果集合的个数 大于 (分割数-1)
while (list.Count > divide - 1)
{
for (int index = 0; index < list.Count; index++)
{
cnt++;
if (cnt >= divide)
{
//Console.WriteLine($"【{list[index]}】被移除...");
removeList.Add(list[index]);
list.RemoveAt(index);
index--;
cnt = 0;
}
}
}
Console.WriteLine($"---约瑟夫环一:依次移除的元素集合【{string.Join(",", removeList)}】");
Console.WriteLine($"---约瑟夫环一:【使用列表集合】,算法用时【{stopwatch.Elapsed.TotalMilliseconds}】ms");
}
/// <summary>
/// 约瑟夫环二:【使用数组】隔divide个元素就将元素状态设置为已删除
/// </summary>
/// <param name="array">姓名数组,必须与是否删除标记数组flagArray长度一致</param>
/// <param name="flagArray">标记数组,必须与姓名数组array长度一致</param>
/// <param name="divide"></param>
static void JosephRing(string[] array, bool[] flagArray, int divide)
{
if (array == null)
{
throw new ArgumentNullException("数组array不能为空");
}
if (flagArray == null)
{
throw new ArgumentNullException("数组flagArray不能为空");
}
if (array.Length != flagArray.Length)
{
throw new ArgumentOutOfRangeException($"对应数组的元素个数不一致:【{array.Length}】vs【{flagArray.Length}】");
}
if (divide <= 0)
{
throw new ArgumentOutOfRangeException("分割计数必须为正整数");
}
Contract.EndContractBlock();
Stopwatch stopwatch = Stopwatch.StartNew();
int cnt = 0;
//记录移除的元素集合
List<string> removeList = new List<string>();
//如果存在未删除状态的集合个数 大于 (分割数-1)
while (flagArray.ToList().FindAll(element => !element).Count > divide - 1)
{
for (int index = 0; index < array.Length; index++)
{
//如果某个元素没有被删除
if (!flagArray[index])
{
cnt++;
}
if (cnt >= divide)
{
//Console.WriteLine($"【{array[index]}】被标记为已删除状态...");
removeList.Add(array[index]);
flagArray[index] = true;
cnt = 0;
}
}
}
Console.WriteLine($"---约瑟夫环二:依次移除的元素集合【{string.Join(",", removeList)}】");
Console.WriteLine($"---约瑟夫环二:【使用数组】,算法用时【{stopwatch.Elapsed.TotalMilliseconds}】ms");
}
/// <summary>
/// 约瑟夫环三:【使用元组集合】每隔divide个元素就将元素状态设置为已删除
/// </summary>
/// <param name="tupleList"></param>
/// <param name="divide"></param>
static void JosephRing(List<Tuple<string, bool>> tupleList, int divide)
{
if (tupleList == null)
{
throw new ArgumentNullException("集合tupleList不能为空");
}
if (divide <= 0)
{
throw new ArgumentOutOfRangeException("分割计数必须为正整数");
}
Contract.EndContractBlock();
Stopwatch stopwatch = Stopwatch.StartNew();
int cnt = 0;
//记录移除的元素集合
List<string> removeList = new List<string>();
//如果存在未删除状态的集合个数 大于 (分割数-1)
while (tupleList.FindAll(tuple => !tuple.Item2).Count > divide - 1)
{
for (int index = 0; index < tupleList.Count; index++)
{
//如果某个元素没有被删除
if (!tupleList[index].Item2)
{
cnt++;
}
if (cnt >= divide)
{
//Console.WriteLine($"【{tupleList[index].Item1}】被标记为已删除状态...");
removeList.Add(tupleList[index].Item1);
//注意 不能使用 tupleList[index].Item2 = true; 元组的第几项是只读的
tupleList[index] = new Tuple<string, bool>(tupleList[index].Item1, true);
cnt = 0;
}
}
}
Console.WriteLine($"---约瑟夫环三:依次移除的元素集合【{string.Join(",", removeList)}】");
Console.WriteLine($"---约瑟夫环三:【使用元组集合】,算法用时【{stopwatch.Elapsed.TotalMilliseconds}】ms");
}
}
}
运行结果如图:
算法性能:元组----->数组----->列表