如何解决过滤不包含所有类型元素的组的有效方法
我有3个这样的数组:
var blues = new int[] {10,100,200};
var reds = new int[] {50,105,150};
var greens = new int[] {80,110,250};
每个数字表示一条横线的点。
如果我将所有内容放在一个数组中,它将看起来像这样:
{ 10,50,80,150,200,250}
b r g b r g r b g
| group 1 |
我需要找到一个组,其中每个组具有三种颜色(蓝色,红色和绿色),并且组中各项之间的距离在blue
和{{1}之间不大于20 },并且在red
和red
之间不大于25。
这种算法是否有已知名称?如果是的话,那是什么?
在C#中实现此算法的最佳方法是什么?
该算法需要考虑以下几点:
-
可以是1到一千种颜色
-
颜色是有序的,并且每种颜色必须根据指定的最大距离足够接近其前面的颜色
-
到上一个颜色的距离可以为正或负,除非明确声明该距离必须为正
-
每种颜色都有其自己的独特最大距离,该距离可以与前面的颜色相距遥远
-
每种颜色的点数在1到一百万之间,并且每种颜色可以不同。
-
每个组必须包含所有颜色
,除非明确说明特定的可选颜色, 或者有人说,组中有40%的颜色或60%等就足够了。
我试图这样实现:
green
在以上测试数据中,预期结果是100105110是一个好组,所有其他得分均落在该组之外并被取消比赛资格。
使用此算法的一个示例可能是在文本搜索中。
如果用户要搜索N个不同的单词,则单词之间的距离不超过X距离。这称为class ColorPoints
{
public string Name; // the color name
public int[] Points;
public int MaxDistance;
public bool CanBeNegativeDistance;
public int[] FinalPoints; // Points that did not fall out of the group
}
public static void GetFinalPoints(ColorPoints[] colorPoints)
{
if (colorPoints.Length == 1)
{
colorPoints[0].FinalPoints = colorPoints[0].Points;
}
// ....
}
-N个字以内,请参阅here。
Here是一个处理主题的项目,并具有算法,但仅适用于两种颜色。
这是另一个示例:
W/N operator
在此示例中,我在蓝色上添加了20,它说明每种颜色可以具有不同数量的项目。
另一个说明是,将所有颜色的水平线放在一起,只取所有颜色中的所有数字并对它们进行排序,并记住每个颜色所属的数字。 并且只有在所有数字都按升序排序之后,才可以开始根据距离和其他条件寻找组。
另一个说明2,组内的顺序没有关系,我提到的红色,蓝色和绿色只是一个例子,可以是世界上任何颜色,也可以是白色和任何颜色。
编辑
在Konstantin Borisov问题之后,我删除了部分要求6。 现在,我想有可能更快,更好地带来一种算法。
负距离示例:
var blues = new int[] {10,20,250};
{ 10,250}
b b r g b r g r b g
| group 1 |
在此示例中,var blues = new int[] {10,200};
var reds = new int[] {50,250}
b r g r b g r b g
| group 1 |
是第一个,blue
是第二个,但是它们之间的距离可以是负数,因此即使red
是105和blue
是他们可以加入100个小组,然后在red
的25个以内拥有green
。
另外,在我的第一个示例中,如果我们允许red
与red
之间的距离为负,那么green
将是有效的组。
解决方法
首先让我以更数学的表达方式重新陈述问题,同时以一种自然的方式对其进行概括(在下面,我使用“ _”来指定索引;不幸的是,SO缺乏对键入公式的良好支持) :
让C_1,...,C_M是整数的有限子集。令I_2,...,I_M为整数区间,即I_j = [a_j,b_j],其中a_j
任务是找到一种有效的算法来确定组的集合{G =(c_k_1,...,c_k_N)| k_1 <...> = pM}。
从数学角度来看,我们可以不失一般性地假设p = 1并因此假设M = N(因为我们可以依次解决具有N个元素且N> = pM的颜色空间的所有子集的问题)。 / p>
我提出的算法非常简单:考虑所有可能的组合(c_k_1,...,c_k_M),并测试它们是否满足所需的属性。
此算法有效吗?当然,有更有效的算法。但是实际上,问题不在于我们是否找到了最有效的算法/实现(几乎从未有过),而是它对于给定任务是否足够有效。让我补充一些想法:
该问题具有令人不愉快的性质,即复杂度随着输入的大小而超指数增长。在最坏的情况下,当距离足够大时,所有组合都是解决方案。在1000种颜色(每百万个点)的情况下,这等于(10 ^ 6)^ 1000 = 10 ^ 6000组。没有任何实现方案能够处理这些数字(宇宙中的原子数估计为10 ^ 80)。因此,每种算法在可行的执行方面都有其局限性(与问题中给出的边界相比,这种局限性很小)。给定一种算法,值得将其改进1000倍吗?如果您很幸运,是的,但是很可能您要解决的问题恰好在弱算法和强算法的限制之间的很小区域内。
因此,我的主张是,以上提出的朴素算法足够有效。它绝对有效,几乎可以立即解决问题中的示例。我的实现几乎立即解决了示例的以下轻微扩展:
颜色:
蓝色:10、20、100、200
红色:50、105、150
绿色:80、110、250
黄色:42、62、82、102、122、142、162距离:
从红色开始:[0,20]
来自绿色:[0,25]
从黄色开始:[0,25]可能会跳过2种颜色。
组:
B:100,R:105
B:100,G:110
B:20,Y:42
B:100,Y:102
B:100,Y:122
R:105,G:110
R:50,Y:62
R:105,Y:122
R:150,Y:162
G:80,Y:82
G:80,Y:102
G:110,Y:122
B:100,R:105,G:110
B:100,R:105,Y:122
B:100,G:110,Y:122
R:105,G:110,Y:122
B:100,R:105,G:110,Y:122
您可以在Arlofin/SO_ColourGroups中找到完整的实现。在下面,我概述了要点。
public class Interval
{
public int LowerBound { get; }
public int UpperBound { get; }
// Details elided
}
public class Color
{
private readonly int[] _points;
public IReadOnlyCollection<int> Points => _points;
public Interval Distance { get; }
public string Name { get; }
// Details elided
}
public struct ColorPoint
{
public int Value { get; }
public Color Color { get; }
// Details elided
}
public class ProblemSpecification
{
private readonly Color[] _colors;
public IReadOnlyCollection<Color> Colors => _colors;
public double Fraction { get; }
// Details elided
}
public class Group
{
private readonly ColorPoint[] _elements;
public IReadOnlyCollection<ColorPoint> Elements => _elements;
// Details elided
}
public static class SetOperations<T>
{
public static IEnumerable<T[]> CrossProduct(IEnumerable<IEnumerable<T>> sets)
{
// Details elided
}
public static IEnumerable<T[]> SubSets(IReadOnlyCollection<T> set,int cardinality)
{
// Details elided
}
}
public static class ProblemSolver
{
private static bool IsGroupValid(Group group)
{
return group.Elements.Zip(group.Elements.Skip(1),(pre,el) => el.Color.Distance.Contains(el.Value - pre.Value)).All(b => b);
}
private static IEnumerable<Group> NaiveSolverFull(IEnumerable<Color> colors)
{
var colourPointsPerColor = from color in colors
select color.Points.Select(colorValue => new ColorPoint(colorValue,color));
var groupCandidates = from colorPointCombination in SetOperations<ColorPoint>.CrossProduct(colourPointsPerColor)
select new Group(colorPointCombination);
return groupCandidates.Where(group => IsGroupValid(group));
}
public static IEnumerable<Group> NaiveSolver(ProblemSpecification spec)
{
int minimalNumberOfColors = (int)Math.Ceiling(spec.Fraction * spec.Colors.Count);
return Enumerable.Range(minimalNumberOfColors,spec.Colors.Count - minimalNumberOfColors + 1)
.SelectMany(n => SetOperations<Color>.SubSets(spec.Colors,n)
.SelectMany(NaiveSolverFull));
}
}
,
由于存在有关负距离处理的其他信息,因此使用递归对算法进行了彻底的重新设计。
一些注意事项:
- 就点数增长而言,这是非常快的。时间复杂度受sortig限制(这非常快,O(ln * log n));
- 距离会严重影响性能。如果您的距离覆盖了整个阵列,则需要检查所有的点组合。这无济于事。希望不是这种情况,并且小组有些紧凑。
- 我添加了1M种随机RGB颜色,并且在我的桌面上可以工作30秒;
class Program
{
class ColorPoints
{
public string Name; // the color name
public int[] Points;
public int MaxDistance;
public bool CanBeNegativeDistance;
}
class IndexesRange
{
public int indexMin { get; set; }
public int indexMax { get; set; }
}
class Item
{
public string Color { get; set; }
public int Number { get; set; }
}
class GroupFinder
{
public List<Item[]> groups { get; set; } = new List<Item[]>();
Item[] array;
List<ColorPoints> colors;
public GroupFinder()
{
Random rnd = new Random();
var blues = /*Enumerable.Range(0,333333).Select(s => rnd.Next(1000000)).ToArray();*/new int[] { 10,20,100,200 };
var reds = /*Enumerable.Range(0,333333).Select(s => rnd.Next(1000000)).ToArray();*/ new int[] { 50,105,150/*,76,82*/ };
var greens = /*Enumerable.Range(0,333333).Select(s => rnd.Next(1000000)).ToArray();*/ new int[] { 80,110,250/*,79,81*/ };
colors = new List<ColorPoints>();
colors.Add(new ColorPoints() { Name = "Blue",Points = blues });
colors.Add(new ColorPoints() { Name = "Red",Points = reds,MaxDistance = 20,CanBeNegativeDistance = true });
colors.Add(new ColorPoints() { Name = "Green",Points = greens,MaxDistance = 25,CanBeNegativeDistance = true });
// Transform input in a one-array form
array = colors.SelectMany(sm => sm.Points.Select(s => new Item() { Color = sm.Name,Number = s })).OrderBy(o => o.Number).ToArray();
//Console.WriteLine("{0}",string.Join(",",array.Select(s => s.Color[0]+s.Number.ToString())));
}
public void FindGroups()
{
var index = 0;
while (index < array.Length)
{
if (array[index].Color == colors[0].Name) // Finde the firtst color
{
var curColor = 0;
IndexesRange range = GetIndexesRange(index,curColor);
for (var i = range.indexMin; i <= range.indexMax; i++)
{
ProcessColor(curColor + 1,i,new List<Item>() { array[index] });
}
}
index++;
}
}
public void ProcessColor(int curColor,int index,List<Item> currentGroup)
{
if (array[index].Color == colors[curColor].Name)
{
currentGroup.Add(array[index]);
if (curColor < colors.Count - 1)
{
IndexesRange range = GetIndexesRange(index,currentGroup);
}
}
else
{
groups.Add(currentGroup.ToArray());
currentGroup.RemoveAt(colors.Count - 1); // Remove the last color since we are moving backward now
return;
}
}
}
/// <summary>
/// Get the possible indexes for the next color.
/// </summary>
/// <param name="index">Current index.</param>
/// <param name="curColor">Current color index.</param>
/// <returns></returns>
private IndexesRange GetIndexesRange(int index,int curColor)
{
var range = new IndexesRange();
// Search for the left side of the indexes range
range.indexMin = index;
var nextColor = colors[curColor + 1];
if (nextColor.CanBeNegativeDistance) // The next color might be bofore this one
{
while (range.indexMin > 0 && array[index].Number - array[range.indexMin].Number <= nextColor.MaxDistance)
{
range.indexMin--;
}
}
else
{
range.indexMin++;
}
range.indexMin++; // We found an element which is already doesn't fit and we need the leftest possible
// Search for the right side of the indexes range
range.indexMax = index;
while (range.indexMax < array.Length && array[range.indexMax].Number - array[index].Number <= nextColor.MaxDistance)
{
range.indexMax++;
}
range.indexMax--; // We found an element which is already doesn't fit and we need the rightest possible
return range;
}
}
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
sw.Start();
var groupFinder = new GroupFinder();
groupFinder.FindGroups();
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds/1000);
foreach (var group in groupFinder.groups)
Console.WriteLine(string.Join(",group.Select(s => $"{s.Color}{s.Number}")));
Console.WriteLine("Done!");
}
}
,
提供2种方法。第一种方法就是使用递归的蛮力。第二种方法使用一些图论并实现了深度优先搜索算法。
编辑:在蛮力方法中添加了“滑动窗口”,以跳过一些不必要的迭代。 Edit2:使用“深度优先”搜索算法创建了第二种Graphed方法。
using System;
using System.Collections.Generic;
using System.Linq;
namespace Color_Finder
{
class Program
{
static void Main(string[] args)
{
//int[] blues = new int[] { 10,200 };
//int[] reds = new int[] { 50,150 };
//int[] greens = new int[] { 80,250 };
//int[] yellows = new int[] { 0,10,101 };
bool IsNegativeDistance = true;
////FindGroup finder = new FindGroup_Windowed();
//FindGroup finder = new FindGroup_Linked();
//finder.AddColor("Blue ",IsNegativeDistance,blues);
//finder.AddColor("Red ",25,reds);
//finder.AddColor("Green ",greens);
//finder.AddColor("Yellow",yellows);
FindGroup finder1 = new FindGroup_Windowed();
FindGroup finder2 = new FindGroup_Linked();
Random r = new Random();
int numColors = 6;
int numPoints = 100;
for (int i = 0; i < numColors; i++)
{
List<int> list = new List<int>();
for (int j = 0; j < numPoints; j++)
{
list.Add(r.Next(0,numPoints * 10)); //for ints
}
int maxDist = r.Next(1,300);
finder1.AddColor($"Color{i.ToString()}",maxDist,list.ToArray());
finder2.AddColor($"Color{i.ToString()}",list.ToArray());
}
DateTime start = DateTime.Now;
finder1.GetColorGroups();
Console.WriteLine($"Window Time: {DateTime.Now - start}");
DateTime start2 = DateTime.Now;
finder2.GetColorGroups();
Console.WriteLine($"Links Time: {DateTime.Now - start2}");
finder1.Print();
finder2.Print();
Console.WriteLine("done");
Console.ReadKey();
}
public interface FindGroup
{
void AddColor(string Name,int MaxDistanceToNext,bool IsNegativeDistance,int[] Points);
List<List<int>> GetColorGroups();
void Print();
}
//Brute Force approach. Not very elegant,but it works
public class FindGroup_Windowed : FindGroup
{
public FindGroup_Windowed(bool IsVerbose = false)
{
Colors = new List<Color>();
this.IsVerbose = IsVerbose;
}
private List<Color> Colors { get; set; }
private List<List<int>> Groups { get; set; }
private int NumSteps { get; set; }
private bool IsVerbose { get; }
public void AddColor(string Name,int[] Points)
{
Colors.Add(new Color(Name,MaxDistanceToNext,Points));
}
public List<List<int>> GetColorGroups()
{
NumSteps = 0;
Groups = FindColorGroups(0);
return Groups;
}
public void Print()
{
if (IsVerbose)
{
Console.Write("Colors:\n");
for (int i = 0; i < Colors?.Count; i++)
{
Console.Write($"Name={Colors[i].Name},MaxDist={Colors[i].MaxDistanceToNext},Points=[{string.Join(",Colors[i].Points)}]\n");
}
Console.Write("\n");
Console.Write("Groups:\n");
for (int i = 0; i < Groups?.Count; i++)
{
for (int j = 0; j < Groups[i].Count; j++)
{
Console.Write(Groups[i][j].ToString());
if (j < Groups[i].Count - 1) Console.Write(",");
else Console.Write("\n");
}
}
}
Console.Write($"Window: Num Steps taken: {NumSteps}\n");
Console.Write($"Window: Num Groups Found: {Groups.Count}\n");
}
private List<List<int>> FindColorGroups(int colorIndex)
{
if (Colors.Count <= colorIndex) return null;
Color current = Colors[colorIndex];
List<List<int>> ret = new List<List<int>>();
int lowerBoundIndex = 0;
for (int i = 0; i < current.Points.Length; i++)
{
int pointA = current.Points[i];
List<int> group = new List<int>();
group.Add(pointA);
List<List<int>> nextPoints = FindNextColor(colorIndex + 1,group,ref lowerBoundIndex);
if (nextPoints != null) ret.AddRange(nextPoints);
}
if (IsVerbose) Console.Write("\n");
return ret;
}
private List<List<int>> FindNextColor(int colorIndex,List<int> group,ref int lowerBoundIndex)
{
if (Colors.Count <= colorIndex) return null; // found end of complete group :)
List<List<int>> ret = new List<List<int>>();
Color prev = Colors[colorIndex - 1];
Color current = Colors[colorIndex];
int pointA = group.Last();
int nextLowerBoundIndex = 0;
for (int i = lowerBoundIndex; i < current.Points.Length; i++)
{
NumSteps++;
int pointB = current.Points[i];
int dist = pointB - pointA;
if (IsVerbose) Console.WriteLine($"{colorIndex - 1}: {pointA},{pointB} = {dist}");
int minDist = prev.IsNegativeDistance ? -prev.MaxDistanceToNext : 0;
//points are in ascending order
if (dist < minDist)
{
lowerBoundIndex = i; //set lower end of window. this will slide forward as the prev Color iterates through its points.
}
else if (minDist <= dist && dist <= prev.MaxDistanceToNext)
{
List<int> newGroup = new List<int>(group);
newGroup.Add(pointB);
List<List<int>> nextPoints = FindNextColor(colorIndex + 1,newGroup,ref nextLowerBoundIndex);
if (nextPoints != null) ret.AddRange(nextPoints);
else ret.Add(newGroup); // found end of complete group :)
}
else //if (prev.MaxDistanceToNext < dist)
{
break; //all points past this are going to be to far away too.
}
}
return ret;
}
private class Color
{
public Color(Color color)
{
this.Name = color.Name;
this.MaxDistanceToNext = color.MaxDistanceToNext;
this.IsNegativeDistance = color.IsNegativeDistance;
this.Points = color.Points;
}
public Color(string Name,int[] Points)
{
Array.Sort(Points);
this.Name = Name;
this.MaxDistanceToNext = MaxDistanceToNext;
this.IsNegativeDistance = IsNegativeDistance;
this.Points = Points;
}
public string Name { get; }
public int MaxDistanceToNext { get; }
public bool IsNegativeDistance { get; }
public int[] Points { get; }
}
}
public class FindGroup_Linked : FindGroup
{
public FindGroup_Linked(bool IsVerbose = false)
{
this.Colors = new List<ColorLinked>();
this.IsVerbose = IsVerbose;
}
private List<ColorLinked> Colors { get; set; }
private List<List<int>> Groups { get; set; }
private int NumSteps { get; set; }
private bool IsVerbose { get; }
public void AddColor(string Name,int[] Points)
{
Colors.Add(new ColorLinked(Name,Points));
}
public List<List<int>> GetColorGroups()
{
NumSteps = 0;
//Build links between colors
BuildLinks();
//iterate through links
Groups = FindColorGroups();
return Groups;
}
public void Print()
{
if (IsVerbose)
{
Console.WriteLine("Colors:");
for (int i = 0; i < Colors?.Count; i++)
{
Console.WriteLine($"Name={Colors[i].Name},Colors[i]._points)}]");
for (int j = 0; j < Colors[i].Points?.Count; j++)
{
Console.WriteLine($"Value={Colors[i].Points[j].Value},Next=[{string.Join(",Colors[i].Points[j].Next.Select(x => x.Value))}]");
}
}
Console.WriteLine("");
Console.WriteLine("Groups:");
for (int i = 0; i < Groups?.Count; i++)
{
for (int j = 0; j < Groups[i].Count; j++)
{
Console.Write(Groups[i][j].ToString());
if (j < Groups[i].Count - 1) Console.Write(",");
else Console.Write("\n");
}
}
}
Console.WriteLine($"Links: Num Steps taken: {NumSteps}");
Console.WriteLine($"Links: Num Groups Found: {Groups.Count}");
}
private void BuildLinks()
{
ColorLinked current;
ColorLinked next;
int lowerBoundIndex = 0;
for (int colorIndex = 0; colorIndex < Colors.Count - 1; colorIndex++) //minus 1 because last color has nowhere to go
{
current = Colors[colorIndex];
next = Colors[colorIndex + 1];
lowerBoundIndex = 0;
for (int i = 0; i < current.Points.Count; i++)
{
Point pointA = current.Points[i];
for (int j = lowerBoundIndex; j < next.Points.Count; j++)
{
NumSteps++;
Point pointB = next.Points[j];
int dist = pointB.Value - pointA.Value;
if (IsVerbose) Console.WriteLine($"{colorIndex}: {pointA.Value},{pointB.Value} = {dist}");
int minDist = current.IsNegativeDistance ? -current.MaxDistanceToNext : 0;
//points are in ascending order
if (dist < minDist)
{
lowerBoundIndex = j; //set lower end of window. this will slide forward as the prev Color iterates through its points.
}
else if (minDist <= dist && dist <= current.MaxDistanceToNext)
{
pointA.Next.Add(pointB);
pointB.Prev.Add(pointA);
}
else //if (prev.MaxDistanceToNext < dist)
{
break; //all points past this are going to be too far away too.
}
}
}
}
if (IsVerbose) Console.WriteLine("");
}
private List<List<int>> FindColorGroups()
{
List<List<int>> ret = new List<List<int>>();
foreach (Point point in Colors[0].Points)
{
List<int> path = new List<int>();
path.Add(point.Value);
List<List<int>> groups = helper(point,path);
if (groups != null) ret.AddRange(groups);
}
return ret;
}
private List<List<int>> helper (Point point,List<int> path)
{
if (point.Next.Count == 0) return null; // found end of grouping
List<List<int>> ret = new List<List<int>>();
foreach (Point next in point.Next)
{
//NumSteps++;
List<int> nextPath = new List<int>(path);
nextPath.Add(next.Value);
List<List<int>> nextGroup = helper(next,nextPath);
if (nextGroup != null) ret.AddRange(nextGroup);
else if(nextPath.Count == Colors.Count) ret.Add(nextPath); // found end of complete group :)
}
return ret;
}
private class ColorLinked
{
public ColorLinked(string Name,int[] Points)
{
Array.Sort(Points);
this.Name = Name;
this.MaxDistanceToNext = MaxDistanceToNext;
this.IsNegativeDistance = IsNegativeDistance;
this._points = Points;
this.Points = new List<Point>();
foreach (var value in Points)
{
this.Points.Add(new Point(value));
}
}
public string Name { get; }
public int MaxDistanceToNext { get; }
public bool IsNegativeDistance { get; }
public int[] _points { get; }
public List<Point> Points { get; }
}
public class Point
{
public Point(int value)
{
this.Prev = new List<Point>();
this.Next = new List<Point>();
this.Value = value;
}
public List<Point> Prev { get; set; }
public List<Point> Next { get; set; }
public int Value { get; set; }
}
}
}
}
,
这是一种利用二进制搜索预先计算的下限的解决方案。我的代码基于Vargo's brute force。
此外,作为回溯的预计算阶段,我删除了所有不能属于完整组的点。这是避免死胡同的必要条件。因此,当只有几个可能的组时,该算法不会以指数方式探索许多可能的组。
using System;
using System.Collections.Generic;
using System.Linq;
namespace Color_Finder
{
class Program
{
static void Main(string[] args)
{
int[] blues = new int[] { 10,200 };
int[] reds = new int[] { 50,150 };
int[] greens = new int[] { 80,250 };
bool AbsoluteDistance = true;
FindGroup finder = new FindGroup_BruteForce();
finder.AddColor(new Color("Blue ",AbsoluteDistance,blues));
finder.AddColor(new Color("Red ",reds));
finder.AddColor(new Color("Green ",greens));
List<List<int>> groups = finder.GetColorGroups();
finder.Print();
Console.WriteLine("done");
Console.ReadKey();
}
public interface FindGroup
{
void AddColor(Color newColor);
List<List<int>> GetColorGroups();
void Print();
}
public class FindGroup_BruteForce : FindGroup
{
public FindGroup_BruteForce()
{
Colors = new List<Color>();
}
private List<Color> Colors { get; set; }
private List<List<int>> Groups { get; set; }
private int[][] LowerBounds;
public void AddColor(Color newColor)
{
Colors.Add(newColor);
}
public List<List<int>> GetColorGroups()
{
Groups = FindColorGroups();
return Groups;
}
public void Print()
{
Console.Write("Colors:\n");
for (int i = 0; i < Colors?.Count; i++)
{
Console.Write($"Name={Colors[i].Name},Colors[i].Points)}]\n");
}
Console.Write("\n");
Console.Write("Groups:\n");
for (int i = 0; i < Groups?.Count; i++)
{
for (int j = 0; j < Groups[i].Count; j++)
{
Console.Write(Groups[i][j].ToString());
if (j < Groups[i].Count - 1) Console.Write(",");
else Console.Write("\n");
}
}
}
private bool InRange(bool AbsoluteDistance,int MaxDist,int p1,int p2)
{
return (AbsoluteDistance && p1 - p2 <= MaxDist && p2 - p1 <= MaxDist)
|| (p1 <= p2 && p2 - p1 <= MaxDist);
}
private bool ExistsInRange(int[] Points,bool AbsoluteDistance,int p)
{
int lower = AbsoluteDistance ? p - MaxDist : p;
int upper = p + MaxDist;
int lowerIdx = Array.BinarySearch(Points,lower);
if (lowerIdx < 0) lowerIdx = ~lowerIdx;
return lowerIdx < Points.Length && Points[lowerIdx] <= upper;
}
private List<List<int>> FindColorGroups()
{
// Eliminate points that do not connect to any point in the next color
for (int i = Colors.Count - 2; i >= 0; i--)
{
Color c = Colors[i];
Color d = Colors[i + 1];
c.Points = Array.FindAll(c.Points,p1 =>
ExistsInRange(d.Points,c.AbsoluteDistance,c.MaxDistanceToNext,p1));
}
LowerBounds = new int[Colors.Count - 1][];
for (int i = 0; i < Colors.Count - 1; i++)
{
Color c = Colors[i];
Color d = Colors[i + 1];
LowerBounds[i] = new int[c.Points.Length];
int k = 0;
for (int j = 0; j < c.Points.Length && k < d.Points.Length; j++)
{
while (k < d.Points.Length && !InRange(c.AbsoluteDistance,c.Points[j],d.Points[k]))
k++;
LowerBounds[i][j] = k;
}
}
Color current = Colors[0];
List<List<int>> ret = new List<List<int>>();
List<int> group = new List<int>(Colors.Count);
for (int i = 0; i < Colors.Count; i++)
group.Add(0);
for (int i = 0; i < current.Points.Length; i++)
{
int pointA = current.Points[i];
group[0] = pointA;
FindNextColor(1,ret);
}
Console.Write("\n");
return ret;
}
private void FindNextColor(int colorIndex,int pointIndex,List<List<int>> ret)
{
if (Colors.Count <= colorIndex) // found end of complete group :)
{
ret.Add(new List<int>(group));
return;
}
Color prev = Colors[colorIndex - 1];
Color current = Colors[colorIndex];
int pointA = group[colorIndex - 1];
// int lower = prev.AbsoluteDistance ? pointA - prev.MaxDistanceToNext : pointA;
// int upper = pointA + prev.MaxDistanceToNext;
// int lowerIdx = Array.BinarySearch(current.Points,lower);
// if (lowerIdx < 0) lowerIdx = ~lowerIdx;
// int upperIdx = Array.BinarySearch(current.Points,upper);
// if (upperIdx < 0) upperIdx = ~upperIdx - 1;
int lowerIdx = LowerBounds[colorIndex - 1][pointIndex];
for (int i = lowerIdx; i < current.Points.Length; i++)
{
int pointB = current.Points[i];
if (!InRange(prev.AbsoluteDistance,prev.MaxDistanceToNext,pointA,pointB))
break;
int dist = pointB - pointA;
Console.WriteLine($"{colorIndex - 1}: {pointA},{pointB} = {dist}");
group[colorIndex] = pointB;
FindNextColor(colorIndex + 1,ret);
}
}
}
public class Color
{
public Color(string Name,int[] Points)
{
Array.Sort(Points);
this.Name = Name;
this.MaxDistanceToNext = MaxDistanceToNext;
this.AbsoluteDistance = AbsoluteDistance;
this.Points = Points;
}
public string Name { get; }
public int MaxDistanceToNext { get; }
public bool AbsoluteDistance { get; }
public int[] Points { get; set; }
}
}
}
上面的代码具有最坏情况的复杂度O(NM + NG) = O(N * (M + G))
,其中N
是颜色的数量,M
是给定颜色的最大点数,而{{1 }}是在给定约束的情况下可以找到的组数。 G
用于预计算,O(NM)
用于实际算法。我认为这是最佳选择。
我不是C#程序员。
但是,如果要查找所有此类组,则应将FinalPoints
替换为以下内容:
class PointOptions
{
public int Point;
public int[] PreviousPointIndexes;
}
class ColorPoints
{
public string Name; // the color name
public int[] Points;
public int MaxDistance;
public bool CanBeNegativeDistance;
public PointOptions[] FinalPointsWithOptions;
}
现在,您用第一种颜色的每个点初始化第一个FinalPointsWithOptions
,并初始化一个空的PointOptionIndexes
。
然后依次针对每种颜色,遍历其点,扫描前一种颜色的FinalPointsWithOptions
,以查找各点,哪些先前的选项可以作为该点的选项。该逻辑类似于以下伪代码:
Lower = 0
for Point in ThisColor.Points:
while PrevColor.PointOptionIndexes[Lower].Point is too small:
Lower++
if Lower = PrevColor.PointOptionIndexes.length:
break
if Lower = PrevColor.PointOptionIndexes.length:
break
i = Lower
Options = []
while PrevColor.PointOptionIndexes[i].Point exists and is not too large:
options.append(i)
if 0 < options.length:
ThisColor.PointOptionIndexes.append(PointOptions(Point,Options))
现在,从最后一种颜色开始的PointOptionIndexes
就像一个链表向后移动,每条向后的路径都为您提供了另一组。
现在,您可以对它们进行计数,退还,全部生成或随机采样。 (可以有很多组。)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。