分治法找假币
介绍
有一堆金币,其中有一个假币。编写程序在其中找到假币。
根据题目先写出控制台程序实现的找假币。之后在考虑使用unity做可视化仿真。
控制台程序
这一个题目使用分治法来解。
代码结构
成员类Coin
Main函数
获取用户输入的数组大小,调用函数生成硬币数组,调用函数查找假币,输出找到的假币的下标。
static void Main(string[] args)
{
//声明一个List数组
List<Coin> coins = new List<Coin>();
Console.WriteLine("请输入硬币数组大小");
var size = int.Parse(Console.ReadLine());
//创建一个硬币数组
CreateCoinArray(size,out coins);
//输出找到的假币序号
Console.WriteLine("找到的假币下标为:" + SearchCoin(coins));
}
CreateCoinArray函数
这个函数内容简单,就是创建一个硬币类的List数组
static void CreateCoinArray(int nums, out List<Coin> coins)
{
//返回的数组
List<Coin> cs = new List<Coin>();
//生成一个随机数作为假币的序号
Random random = new Random();
var CounterfeitCoinId = random.Next(0, nums - 1);
//生成硬币币数组
for (int i = 0; i < nums; i++)
{
if (i == CounterfeitCoinId)
{
Coin c = new Coin(1, i);
cs.Add(c);
continue;
}
cs.Add(new Coin(2, i));
}
//赋值传回
coins = cs;
//输出假币id,与找到的假币id做对比
Console.WriteLine("初始定义的假币下标:" + CounterfeitCoinId);
}
SearchCoin函数
这是该程序的核心函数,自己设定一个5枚硬币数组按照分治的思想一步步写。不纠结if判断,到对应数目的情况时再考虑对应的if判断。
static int SearchCoin(List<Coin> coins)
{
//参数定义
int delta, len;
len = coins.Count;
//数组等于1,基本已经找到
if (coins.Count == 1)
{
if (coins[0].weight == 1) return coins[0].index;
else return -1;
}
//如果数组元素的数目不是偶数的情况
if (coins.Count % 2 != 0)
{
//比较末尾两枚硬币的重量,这里设定的是假币比真币轻
delta = coins[len - 2].weight - coins[len - 1].weight;
//根据情况进行不同的操作
if (delta > 0)
{
return coins[len - 1].index;
}
else if (delta < 0)
{
return coins[len - 2].index;
}
else
{
coins.RemoveAt(len - 1);//移除最后一位
len = coins.Count;
}
}
//经过上一个if的处理后,到达这里的必是偶数数量的数组
//对两边的元素求和做差对重的一方进行裁剪,之后递归调用
delta = Sum(coins, 0, (len / 2) - 1) - Sum(coins, len / 2, len - 1);
if (delta > 0)
{
RemoveData(coins, 0, (len / 2) - 1);
}
else RemoveData(coins, len / 2, len - 1);
return SearchCoin(coins);
}
Sum函数
一个简单的求和函数。
static int Sum(List<Coin> coins,int s,int e)
{
var sum = 0;
for (int i = s; i <= e; i++)
{
sum += coins[i].weight;
}
return sum;
}
RemoveData函数
裁剪数组的函数。由于List数组删除元素后会自动挤掉对应的位置。
这里只需要每次删除固定位置的元素并循环需要次数就够了。
static void RemoveData(List<Coin> coins, int s, int e)
{
for (int i = s; i <= e; i++)
{
coins.RemoveAt(s);
}
}
Unity仿真
实现思路
首先确定输入。我需要获得
金币数组的尺寸,
假币是比真币重还是轻
再是如何演示步骤。这个实现过程由于最初的思路问题走了不少弯路,这里就不赘述。讲讲最后成功的实现方式。
设置一个数组,每当执行删除操作或者找到结果时用字符串将需要删除的金币序号保存为字符串存储在数组中。
之后根据数组来一步步的删除对应金币,这就实现了找假币的过程演示。
UI结构
InputPanel
获取用户的输入。
一个标题、一个输入框、一个提示按钮、一个开始按钮、两个Toggle
InputPanel
WeightSelectTG
两个Toggle
CoinPanel
主要是使用GridLayoutGroup组件实现金币的有序排布。
对Content里添加生成的金币初始化金币,删除金币演示步骤。
成员
CoinPanel
Content
脚本
一共五个脚本,实现主要功能的是SCoinCoinPanel脚本。
这里对Panel类型和假币重量设置了两个枚举。
Panel类型的枚举作为索引值,Panel面板的canvasGroup作为组件。两者成为一个字典的元素。
Toggle变换当前假币重量的枚举类型。
Coin.cs
using UnityEngine;
public class Coin : MonoBehaviour
{
//硬币的重量
internal int weight;
//硬币在数组中的序号
internal int index;
public UnityEngine.UI.Text IndexText;
public void Init(int w, int id)
{
weight = w;
index = id;
IndexText.text = index.ToString();
}
}
SCoinBasePanel.cs
using UnityEngine;
public class SCoinBasePanel : MonoBehaviour
{
public SearchCoinCanvasCtrl.PanelType Type;//当前Panel的类型
protected CanvasGroup myCanvasGroup;//当前panel的CanvasGroup组件
protected void Awake()
{
//获取自身的Canvas组件
if (myCanvasGroup == null)
{
myCanvasGroup = this.GetComponent<CanvasGroup>();
}
}
public void Start()
{
//初始化SearchCoinCanvasCtrl里的字典,并将自身注册到字典里去
SearchCoinCanvasCtrl.Instance.InitSCoinPanels();
SearchCoinCanvasCtrl.Instance.SCoinPanels.Add(Type, this.GetComponent<CanvasGroup>());
}
}
SCoinInputPanel.cs
using UnityEngine;
using UnityEngine.UI;
public class SCoinInputPanel : SCoinBasePanel
{
//输入组件
public InputField input;
//提示输入正确与否的图片
public Image debugimg;
//检查输入,合法则开始找假币算法的步骤演示
public void OnConfirmBD()
{
if (input.text == "")
{
debugimg.color = Color.yellow;
return;
}
var GoldNum = int.Parse(input.text);
if (GoldNum < 2 || GoldNum > 42)
{
debugimg.color = Color.red;
//audioSource.PlayOneShot(errorSound);
return;
}
debugimg.color = Color.green;
//隐藏自身
myCanvasGroup.alpha = 0;
myCanvasGroup.blocksRaycasts = false;
CanvasGroup CoinPanel;
SearchCoinCanvasCtrl.Instance.SCoinPanels.TryGetValue(SearchCoinCanvasCtrl.PanelType.CoinPanel, out CoinPanel);
//显示CoinPanel
CoinPanel.alpha = 1;
CoinPanel.blocksRaycasts = true;
//生成金币
SCoinCoinPanel.Instance.CreateCoins(GoldNum);
print("csnum:" + SCoinCoinPanel.Instance.Coins.Count);
//查找假币
var cs = SCoinCoinPanel.Instance.Coins;
SCoinCoinPanel.Instance.SearchCoin(cs);
//调用协程函数演示过程
StartCoroutine(SCoinCoinPanel.Instance.StepByStep());
}
//变换假币的重量条件,Toggle的绑定事件
public void SwitchWeightType()
{
SearchCoinCanvasCtrl.Instance.weightType = (SearchCoinCanvasCtrl.Instance.weightType == CoinWeightType.heavy) ?
CoinWeightType.light : CoinWeightType.heavy;
}
}
SCoinCoinPanel.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SCoinCoinPanel : SCoinBasePanel
{
public Transform Cotent;//生成金币的父物体
public GameObject CoinPre;//金币预制体
internal List<Coin> Coins = new List<Coin>();//金币数组,这个有点多余,一开始写的,到后面就感觉用处不大了
/// <summary>
/// 存放硬币序号与对应游戏体的字典
/// </summary>
internal Dictionary<int, GameObject> CoinGODic = new Dictionary<int, GameObject>();
bool GoAhead = false;//前进bool变量
List<string> RemoveList = new List<string>();//存储删除金币序号的字符串数组
public static SCoinCoinPanel Instance;//单例模式变量
public GameObject RestartBt;//重新开始按钮
private void Awake()
{
//继承使用父类的Awake方法
base.Awake();
//单例赋值
if (Instance == null)
Instance = this;
}
private void Start()
{
//继承使用父类的Atart方法
base.Start();
//如果自己的Type是CoinPanel,将自己的透明度设为0,遮罩设false
if (Type == SearchCoinCanvasCtrl.PanelType.CoinPanel)
{
myCanvasGroup.alpha = 0;
myCanvasGroup.blocksRaycasts = false;
}
//初始不显示重新开始按钮
RestartBt.SetActive(false);
}
/// <summary>
/// 变换前进bool变量的值达到动态演示删除步骤的效果
/// </summary>
public void GoAheadBD()
{
if (!GoAhead) GoAhead = true;
}
internal int deltaFactor = 1;//因为假币的重量不定,需要一个差值因数乘以算法
//里重量做差的值,使算法能同时处理两种类型的问题
internal int CounterfeitCoinW = 3;//假币重量,默认为3
/// <summary>
/// 初始生成金币
/// </summary>
/// <param name="size"></param>
internal void CreateCoins(int size)
{
//差值因数赋值,根据用户选择的假币类型(比真币重或轻)
deltaFactor = (SearchCoinCanvasCtrl.Instance.weightType == CoinWeightType.heavy)
? -1 : 1;
//获取假币的轻重
int CounterfeitCoinW = (SearchCoinCanvasCtrl.Instance.weightType == CoinWeightType.heavy)
? 3 : 1;
//随机一个序号为假币
int CounterfeitCoinId = Random.Range(0, size - 1);
//生成金币
for (int i = 0; i < size; i++)
{
//实例化
var c = Instantiate(CoinPre, Cotent);
var CoinClass = c.GetComponent<Coin>();
//假币的情况
if (i == CounterfeitCoinId)
{
//初始化Coin脚本里的参数
CoinClass.Init(CounterfeitCoinW, i);
//金币设置黑色作为区分
c.GetComponent<UnityEngine.UI.Image>().color = Color.black;
}
else
{
//初始化Coin脚本里的参数
CoinClass.Init(2, i);
}
//注册
Coins.Add(CoinClass);//注册脚本
CoinGODic.Add(i, c);//注册游戏体
}
}
//找假币函数
public int SearchCoin(List<Coin> coins)
{
int delta, len;
len = coins.Count;
if (coins.Count == 1)
{
if (coins[0].weight == CounterfeitCoinW)
return coins[0].index;
else
return -1;
}
if (coins.Count % 2 != 0 && coins.Count != 1)
{
delta = coins[len - 2].weight - coins[len - 1].weight;
if (delta * deltaFactor > 0)
{
//获取id
int id = coins[len - 1].index;
//移除自身
coins.RemoveAt(len - 1);
//添加进删除数组
AddRemoveList(coins);
return id;
}
else if (delta * deltaFactor < 0)
{
//获取id
int id = coins[len - 2].index;
//移除自身
coins.RemoveAt(len - 2);
//添加进删除数组
AddRemoveList(coins);
return id;
}
else
{
//获取id
string id = coins[len - 1].index.ToString();
//移除自身
coins.RemoveAt(len - 1);
//添加进删除数组
RemoveList.Add(id);
len = coins.Count;
}
}
//求两边的和值差
delta = Sum(coins, 0, (len / 2) - 1) - Sum(coins, len / 2, len - 1);
if (delta * deltaFactor > 0)
{
RemoveData(coins, 0, (len / 2) - 1);
}
else RemoveData(coins, len / 2, len - 1);
return SearchCoin(coins);
}
//求和函数
int Sum(List<Coin> coins, int s, int e)
{
var sum = 0;
for (int i = s; i <= e; i++)
{
sum += coins[i].weight;
}
return sum;
}
//移除数组元素函数
void RemoveData(List<Coin> coins, int s, int e)
{
string removeStr = "";
for (int i = s; i <= e; i++)
{
if (i == s)
{
print("in");
removeStr = coins[s].index.ToString();
}
else
removeStr += "," + coins[s].index.ToString();
//删除算法中的存储信息
coins.RemoveAt(s);
}
RemoveList.Add(removeStr);
}
//奇数情况下,假币在末尾两个之一时调用,将假币之外的所有金币移除
public void AddRemoveList(List<Coin> coins)
{
print("inre");
string str = "";
int i = 1;
foreach (var c1 in coins)
{
if (i == 1)
str = c1.index.ToString();
else
{
str += "," + c1.index.ToString();
}
i++;
}
RemoveList.Add(str);
}
//实现用户控制步骤演示的函数
public IEnumerator StepByStep()
{
foreach (var rstr in RemoveList)
{
yield return new WaitUntil(() => GoAhead == true);
//解字符串
var indexs = rstr.Split(',');
foreach (var id in indexs)
{
yield return new WaitForSeconds(0.5f);
//删除字典中对应金币id的游戏体
Destroy(GetValue<int, GameObject>(CoinGODic, int.Parse(id)));
}
GoAhead = false;
}
//演示过程结束后才能重新开始
RestartBt.SetActive(true);
}
//重新开始函数
public void Restart()
{
//清空数据
foreach (var g in CoinGODic)
Destroy(g.Value);
RemoveList.Clear();
Coins.Clear();
CoinGODic.Clear();
//隐藏自身
myCanvasGroup.alpha = 0;
myCanvasGroup.blocksRaycasts = false;
CanvasGroup InputPanel;
SearchCoinCanvasCtrl.Instance.SCoinPanels.TryGetValue(SearchCoinCanvasCtrl.PanelType.InputPanel, out InputPanel);
//显示CoinPanel
InputPanel.alpha = 1;
InputPanel.blocksRaycasts = true;
}
//获取字典value值的封装函数,不在需要out。
public T2 GetValue<T1, T2>(Dictionary<T1, T2> dic, T1 key)
{
T2 value;
dic.TryGetValue(key, out value);
return value;
}
}
SearchCoinCanvasCtrl.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum CoinWeightType
{
light, heavy
}
public class SearchCoinCanvasCtrl : MonoBehaviour
{
public enum PanelType
{
InputPanel,CoinPanel,TipPanel
}
public GameObject TipPanel;//提示面板
/// <summary>
/// 假币是比真币重还是轻。默认是重
/// </summary>
internal CoinWeightType weightType = CoinWeightType.heavy;
public static SearchCoinCanvasCtrl Instance;//单例变量
internal Dictionary<PanelType, CanvasGroup> SCoinPanels;//各个面板的注册字典
private void Awake()
{
//初始化
if (Instance == null)
Instance = this;
}
//初始化字典
public void InitSCoinPanels()
{
if (Instance.SCoinPanels == null)
{
Instance.SCoinPanels = new Dictionary<PanelType, CanvasGroup>();
}
}
//提示按钮的响应函数
public void ShowTip()
{
StartCoroutine(ShowTip(0.5f));
}
public IEnumerator ShowTip(float time)
{
TipPanel.SetActive(true);
yield return new WaitForSeconds(time);
TipPanel.SetActive(false);
}
}
演示图片
输入面板
初始面板和查找过程演示