例如,我有一系列砝码
[1, 0, 3, 5]
两个字符串之间的距离定义为不同位的权重之和,如下所示:
size_t distance(const std::string& str1, const std::string& str2, const std::vector<size_t>& weights) {
size_t result = 0;
for (size_t i = 0; i < str1.size(); ++i) {
if (str1[i] != str2.at(i))
result += weights.at(i);
}
return result;
}
,例如起始字符串
'1101'
我需要以与原始距离最小的字符串优先出现的方式生成排列,如下所示:
'1001' # changed bits: 2nd. Because it has lowest weight. Distance is 0
'0101' # changed bits: 1st. Distance is 1
'0001' # changed bits: 1st, 2nd. Distance is 1
'1011' # changed bits: 2nd, 3rd. Distance is 3
'1111' # changed bits: 3rd. Distance is 3
'0111' # changed bits: 1st, 3rd. Distance is 4
'0011' # changed bits: 1st, 2nd, 3rd. Distance is 4
'1100' # changed bits: 4th. Distance is 5
'1000' # changed bits: 2nd, 4th. Distance is 5
'0100' # changed bits: 1st, 4th. Distance is 6
'0000' # changed bits: 1st, 2nd, 4th. Distance is 6
'1110' # changed bits: 3rd, 4th. Distance is 8
'1010' # changed bits: 2nd, 3rd, 4th. Distance is 8
'0110' # changed bits: 1st, 3nd, 4th. Distance is 9
'0010' # changed bits: 1st, 2nd, 3rd, 4th. Distance is 9
我不需要代码,我只需要一个算法,该算法将获取长度为N的字符串,长度相同且权重为i的权重数组作为输入并生成第i个排列,而无需生成整个列表并对其进行排序。
答案 0 :(得分:1)
在现代C ++中,按照您的要求去做的方法是使用std::bitset
表示所有可能的 multiset s位,然后用{ comparer functor struct以呼叫std::sort()
。我强调可能的multisets 位,不permutations ,因为后者仅允许更改顺序。然后,您的代码将如下所示:
distance()
输出:
#include <string>
#include <array>
#include <cmath>
#include <bitset>
#include <vector>
#include <algorithm>
#include <iostream>
constexpr size_t BITSET_SIZE = 4;
size_t distance(const std::string& str1, const std::string& str2, const std::array<size_t, BITSET_SIZE>& weights) {
size_t result = 0;
for (size_t i = 0; i < str1.size(); ++i) {
if (str1[i] != str2.at(i))
result += weights.at(i);
}
return result;
}
struct of_lesser_distance
{
const std::bitset<BITSET_SIZE>& originalBitSet;
const std::array<size_t, BITSET_SIZE>& distanceVec;
inline bool operator() (const std::bitset<BITSET_SIZE>& lhs, const std::bitset<BITSET_SIZE>& rhs)
{
return distance(originalBitSet.to_string(), lhs.to_string(), distanceVec) < distance(originalBitSet.to_string(), rhs.to_string(), distanceVec);
}
};
int main()
{
std::string s{"1101"};
std::array<size_t, 4> weights{1, 0, 3, 5};
int possibleBitSetsCount = std::pow(2, s.length());
std::vector<std::bitset<BITSET_SIZE>> bitSets;
// Generates all possible bitsets
for (auto i = 0; i < possibleBitSetsCount; i++)
bitSets.emplace_back(i);
// Sort them according to distance
std::sort(bitSets.begin(), bitSets.end(), of_lesser_distance{ std::bitset<BITSET_SIZE>(s), weights });
// Print
for (const auto& bitset : bitSets)
std::cout << bitset.to_string().substr(BITSET_SIZE - s.length(), s.length()) << " Distance: " << distance(s, bitset.to_string(), weights) << "\n";
}
实时版本here。
注意:这样一来,您最好将1001 Distance: 0
1101 Distance: 0
0001 Distance: 1
0101 Distance: 1
1011 Distance: 3
1111 Distance: 3
0011 Distance: 4
0111 Distance: 4
1000 Distance: 5
1100 Distance: 5
0000 Distance: 6
0100 Distance: 6
1010 Distance: 8
1110 Distance: 8
0010 Distance: 9
0110 Distance: 9
更改为在distance()
而不是std::bitset
上运行,因为这样可以节省所有不必要的转换。
我不需要代码,我只需要一个算法
对我来说,提供代码更容易,但是如果您需要其他东西,请告诉我。
答案 1 :(得分:1)
听起来像困难的问题。
如果您将size_t用作排列索引,则您的字符串将被限制为32个或64个字符,否则,您将需要更大的整数作为排列索引。因此,您可以从字符串切换到size_t位掩码。
这样,您的算法就不再依赖于字符串,您可以找到第i个位掩码,然后将其与输入字符串位掩码(在C ++中为^
运算符)进行XOR运算,即可得到结果。困难的部分是找到第i个位掩码,但是以这种方式,即在算法的内部循环中不使用字符串的情况下,代码将更快(一个数量级)。
现在最困难的部分是如何找到面具。对于一般情况,我能想到的唯一算法是广泛搜索,也许可以使用memoisation来提高性能。对于较小的排列索引,这将很快,而对于较大的排列索引,这将很慢。
如果您知道在编译时的权重,则可以将索引预先计算到搜索树中,但是最好在C ++之外完成,对于这种复杂的算法,很难使用模板元编程。
P.S。有一种特殊情况可能对您有用。对权重进行排序,并针对所有1
顺便说一句,问题中的权重满足该条件,因为1> = 0、3> = 0 + 1和5> = 0 + 1 + 3,因此此简单算法可以对您的特定权重正常运行。
更新:这是一个完整的解决方案。它打印出的结果与您的样品略有不同,例如在您的示例中,您输入的是“ 1011”,然后是“ 1111”,我的代码会在“ 1111”之后立即显示“ 1011”,但是它们之间的距离是相同的,即我的算法仍然可以正常运行。
#include <string>
#include <vector>
#include <algorithm>
#include <stdio.h>
struct WeightWithBit
{
size_t weight, bit;
};
// Sort the weights while preserving the original order in the separate field
std::vector<WeightWithBit> sortWeights( const std::vector<size_t>& weights )
{
std::vector<WeightWithBit> sorted;
sorted.resize( weights.size() );
for( size_t i = 0; i < weights.size(); i++ )
{
sorted[ i ].weight = weights[ i ];
sorted[ i ].bit = ( (size_t)1 << i );
}
std::sort( sorted.begin(), sorted.end(), []( const WeightWithBit& a, const WeightWithBit& b ) { return a.weight < b.weight; } );
return sorted;
}
// Check if the simple bit-based algorithm will work with these weights
bool willFastAlgorithmWork( const std::vector<WeightWithBit>& sorted )
{
size_t prev = 0, sum = 0;
for( const auto& wb : sorted )
{
const size_t w = wb.weight;
if( w == prev || w >= sum )
{
prev = w;
sum += w;
continue;
}
return false;
}
return true;
}
size_t bitsFromString( const std::string& s )
{
if( s.length() > sizeof( size_t ) * 8 )
throw std::invalid_argument( "The string's too long, permutation index will overflow" );
size_t result = 0;
for( size_t i = 0; i < s.length(); i++ )
if( s[ i ] != '0' )
result |= ( (size_t)1 << i );
return result;
}
std::string stringFromBits( size_t bits, size_t length )
{
std::string result;
result.reserve( length );
for( size_t i = 0; i < length; i++, bits = bits >> 1 )
result += ( bits & 1 ) ? '1' : '0';
return result;
}
// Calculate the permitation. Index is 0-based, 0 will return the original string without any changes.
std::string permitation( const std::string& str, const std::vector<WeightWithBit>& weights, size_t index )
{
// Reorder the bits to get the bitmask.
// BTW, if this function is called many times for the same weights, it's a good idea to extract just the ".bit" fields and put it into a separate vector, memory locality will be slightly better.
size_t reordered = 0;
for( size_t i = 0; index; i++, index = index >> 1 )
if( index & 1 )
reordered |= weights[ i ].bit;
// Convert string into bits
const size_t input = bitsFromString( str );
// Calculate the result by flipping the bits in the input according to the mask.
const size_t result = input ^ reordered;
// Convert result to string
return stringFromBits( result, str.length() );
}
int main()
{
const std::vector<size_t> weights = { 1, 0, 3, 5 };
using namespace std::literals::string_literals;
const std::string theString = "1101"s;
if( weights.size() != theString.length() )
{
printf( "Size mismatch" );
return 1;
}
if( weights.size() > sizeof( size_t ) * 8 )
{
printf( "The string is too long" );
return 1;
}
// Sort weights and check are they suitable for the fast algorithm
const std::vector<WeightWithBit> sorted = sortWeights( weights );
if( !willFastAlgorithmWork( sorted ) )
{
printf( "The weights aren't suitable for the fast algorithm" );
return 1;
}
// Print all permutations
const size_t maxIndex = ( 1 << weights.size() ) - 1;
for( size_t i = 0; true; i++ )
{
const std::string p = permitation( theString, sorted, i );
printf( "%zu: %s\n", i, p.c_str() );
if( i == maxIndex )
break; // Avoid endless loop when the string is exactly 32 or 64 characters.
}
return 0;
}
答案 2 :(得分:0)
此问题无法有效解决。可以将其多项式化为子集和问题,而子集和问题本身就是一个NP完全问题。
如果您不介意详尽的解决方案,则可以仅迭代与基础字符串相同长度的所有可能字符串,然后使用distance
计算其距离并跟踪最大i
距离。
由于对问题的误解而导致的原始错误答案:
听起来像一个简单的问题。由于您已经必须生成所有这些字符串,因此您的解决方案相对于基本字符串将是指数级的(在空间和时间上)。您基本上不受约束。
您可以尝试类似 [1] :
1.生成所有长度与基本字符串相同的字符串。
这很简单。只需从0循环到(2 | base_str | -1),然后使用sprintf(&strs[loop_counter]"%b", loop_counter)
2.使用strs
对qsort
进行排序,并使用distance
作为编译器。类似于qsort(str, 1 << strlen(base_str)-1, sizeof(char*), comp)
的事物,其中comp
是一个接受两个字符串的函数,如果第一个到base_str的距离小于第二个字符串,则返回-1,如果两个具有相等的距离,则返回0,如果第一个更远则返回1来自base_str而不是第二个参数。
[1] 我是C而不是C ++程序员,所以我确定还有其他(也许更好)的方法可以完成我在C ++中建议的工作,但我的示例在C中。
答案 3 :(得分:0)
如果只需要第i个排列,那么您只需要查看权重即可。
如果权重是反向排序的,例如[5,3,1,0]
,而您想进行第5次置换,那么您就需要将0, 1, 0, 1
翻转为5 = 0101
的二进制文件。
因此,您需要从权重到原始索引的很小映射。然后,按从大到小的顺序排序,基于N的二进制表示形式获取第N个置换,然后翻转原始字符串的映射位。