修订日期:2009年10月17日23:00
我已经在大数字上运行过(例如,2000万个字符串),现在我相信这个算法不是O(n logn)。尽管如此,它是一个非常酷的实现,包含了许多优化,使它运行得非常快。它可以在不到25秒的时间内评估24位或更少位的二进制字符串的所有排列。
我已更新代码,包括今天早些时候提出的0 <= L < M < U <= X-1
观察结果。
原文
这个概念与我回答的另一个问题类似。那段代码也查看了一系列中的三个值,并确定三元组是否满足条件。以下是从那里改编的C#代码:
using System;
using System.Collections.Generic;
namespace StackOverflow1560523
{
class Program
{
public struct Pair<T>
{
public T Low, High;
}
static bool FindCandidate(int candidate,
List<int> arr,
List<int> pool,
Pair<int> pair,
ref int iterations)
{
int lower = pair.Low, upper = pair.High;
while ((lower >= 0) && (upper < pool.Count))
{
int lowRange = candidate - arr[pool[lower]];
int highRange = arr[pool[upper]] - candidate;
iterations++;
if (lowRange < highRange)
lower -= 1;
else if (lowRange > highRange)
upper += 1;
else
return true;
}
return false;
}
static List<int> BuildOnesArray(string s)
{
List<int> arr = new List<int>();
for (int i = 0; i < s.Length; i++)
if (s[i] == '1')
arr.Add(i);
return arr;
}
static void BuildIndexes(List<int> arr,
ref List<int> even, ref List<int> odd,
ref List<Pair<int>> evenIndex, ref List<Pair<int>> oddIndex)
{
for (int i = 0; i < arr.Count; i++)
{
bool isEven = (arr[i] & 1) == 0;
if (isEven)
{
evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count+1});
oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count});
even.Add(i);
}
else
{
oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count+1});
evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count});
odd.Add(i);
}
}
}
static int FindSpacedOnes(string s)
{
List<int> arr = BuildOnesArray(s);
List<int> odd = new List<int>(), even = new List<int>();
List<Pair<int>> evenIndex = new List<Pair<int>>(),
oddIndex = new List<Pair<int>>();
BuildIndexes(arr,
ref even, ref odd,
ref evenIndex, ref oddIndex);
int iterations = 0;
for (int i = 1; i < arr.Count-1; i++)
{
int target = arr[i];
bool found = FindCandidate(target, arr, odd, oddIndex[i], ref iterations) ||
FindCandidate(target, arr, even, evenIndex[i], ref iterations);
if (found)
return iterations;
}
return iterations;
}
static IEnumerable<string> PowerSet(int n)
{
for (long i = (1L << (n-1)); i < (1L << n); i++)
{
yield return Convert.ToString(i, 2).PadLeft(n, '0');
}
}
static void Main(string[] args)
{
for (int i = 5; i < 64; i++)
{
int c = 0;
string hardest_string = "";
foreach (string s in PowerSet(i))
{
int cost = find_spaced_ones(s);
if (cost > c)
{
hardest_string = s;
c = cost;
Console.Write("{0} {1} {2}\r", i, c, hardest_string);
}
}
Console.WriteLine("{0} {1} {2}", i, c, hardest_string);
}
}
}
}
主要差异如下:
- 解决方案的彻底搜索
该代码生成一组数据的幂集,以找到此算法最难解决的输入。
- 所有解决方案与最难解决的方案
上一个问题的代码使用Python生成器生成了所有解决方案。此代码仅显示每个模式长度的最难解决方案。
- 评分算法
该代码检查中间元素到其左右边缘的距离。Python代码测试是否总和高于或低于0。
- 收敛到候选项
当前代码从中间向边缘工作以查找候选项。以前的问题中的代码是从边缘向中间工作的。这个改变大大提高了性能。
- 使用偶数和奇数池
基于本文末尾的观察结果,该代码搜索一对偶数或一对奇数,以查找L和U,保持M不变。这通过预先计算信息来减少搜索次数。因此,代码在FindCandidate的主循环中使用两个间接级别,并且每个中间元素需要进行两次FindCandidate调用:一次为偶数,一次为奇数。
一般思路是处理索引,而不是原始数据的表示。计算出现1的数组可以使算法运行时间与数据中1的数量成比例,而不是与数据长度成比例。这是一个标准的转换:创建一个数据结构,允许更快的操作,同时保持问题等效。
结果已过时:已移除。
编辑:2009年10月16日18:48
对于yx提供的数据,其他回答认为这是代表可计算硬数据的数据,我得到了以下结果... 我已将其删除。它们已经过时。
我要指出的是,这些数据并不是对我的算法来说最难的数据,因此我认为认为yx的分形图案是最难解决的假设是错误的。特定算法的最坏情况,我预计将取决于算法本身,并且可能在不同算法之间不一致。
编辑:2009年10月17日13:30
对此的进一步观察。
首先,将0和1的字符串转换为每个位置的1的索引数组。假设该数组A的长度为X。那么目标就是找到
0 <= L < M < U <= X-1
这样的
A[M] - A[L] = A[U] - A[M]
或者
2*A[M] = A[L] + A[U]
由于A [L]和A [U]的和为偶数,它们不能是(偶数,奇数)或(奇数,偶数)。通过将A []分成奇数和偶数池,并依次在奇数和偶数候选池中搜索匹配项,可以改进匹配的搜索。但我认为这更多是性能优化而不是算法改进。比较的数量应该会减少,但算法的顺序应该是相同的。
编辑时间 2009-10-18 00:45
我想到了另一个优化方案,与将候选人分为偶数和奇数的方案类似。由于三个索引必须加起来是3的倍数(a、a+x、a+2x - 不考虑a和x时mod 3等于0),因此您可以将L、M和U分成它们的mod 3值:
M L U
0 0 0
1 2
2 1
1 0 2
1 1
2 0
2 0 1
1 0
2 2
事实上,您可以将这个与奇偶观察相结合,并将它们分成它们的模6值:
M L U
0 0 0
1 5
2 4
3 3
4 2
5 1
等等。这将提供进一步的性能优化,但不会提高算法速度。