这里写目录标题
八数码问题的一个小记录
a[]数组需要提前手算(比较简单,写循环没有必要hh)
//有n个数 该排列为str[]数组
bool Cantor(int str[],int n){
long long sum=0;
for(int i=0;i<n;i++){
long long cnt=0;
for(int j=i+1;j<n;j++){
if(str[i]>str[j])
cnt++;
}
//cnt存储在未出现的数字中第几大的数字
//n-i-1 表示倒数第几个,"几个"的阶乘
//a[]数组存的是下标的阶乘
sum+=cnt*a[n-i-1];
}
//sum得出的是该排列的位置
if(!vis[sum]){//如果说没有被访问过
vis[sum]=1;//标记访问
return true;
}
else
return false;//已经访问过
}
逆运算
法一:
在给出第62个排列为??
可以算出起排列组合为34152
(计算时,要用该排列前面的位置去推算)
具体过程如下:
用 61 / 4! = 2余13,说明,说明比首位小的数有2个,所以首位为3。
用 13 / 3! = 2余1,说明,说明在第二位之后小于第二位的数有2个,所以第二位为4。
用 1 / 2! = 0余1,说明,说明在第三位之后没有小于第三位的数,所以第三位为1。
用 1 / 1! = 1余0,说明,说明在第四位之后小于第四位的数有1个,所以第四位为5。
最后一位自然就是剩下的数2。
通过以上分析,所求排列组合为 34152。
const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; // 阶乘
//康托展开逆运算
void dec(int x, int n)
{
vector<int> v; // 存放当前可选数
vector<int> a; // 所求排列组合
for(int i=1;i<=n;i++)
v.push_back(i);
for(int i=n;i>=1;i--)
{
int r = x % FAC[i-1];
int t = x / FAC[i-1];
x = r;
sort(v.begin(),v.end());// 从小到大排序
a.push_back(v[t]); // 剩余数里第t+1个数为当前位
v.erase(v.begin()+t); // 移除选做当前位的数
}
}
法二
:
原理:如果存在 (nx-i)! >= m && x数字没有放过 ,说明该位置应该放数字x,放入位置i中,然后i++;
如果没有放进去,m-=fact(nx-i)! ,x++
(nx-i)!
代表要放入数字位置为nx,的后面仍存在的空位置个数的阶乘
m
为要求的第几个排列
n
为该排列的位数
fact()
为阶乘函数
int get(int x)
{
int k = 1;
while (fact(k) < m) k ++ ;
if (x <= n - k) return x;
memset(st, 0, sizeof st);
int rank = m;
for (int i = n - k + 1; i <= n; i ++ )
{
for (int j = 1; j <= k; j ++ )
if (!st[j])
{
int t = fact(n - i);
if (t < rank) rank -= t;
else
{
if (x == i) return j + n - k;
st[j] = true;
break;
}
}
}
}