字符串数组的全排列到八皇后问题详解

1.记得在一次面试终要求写一个程序,程序题目为:

输入字符串的全排列:例如输入字符串为abc,那么他的全排列有abc,bac等六种。尝试确定输入字符串的全排列,并输出。

字符串全排其实是一个数学问题,根据数学计算可以知道,程序的复杂度为O(n!),并且没有比较明显的改善算法。比较常见的算法都是将问题变为一个子问题,然后对子问题进行反复迭代,得出最终的结果。

而子问题就是采用字母固定的方法,即先在字符串中选择一个字母(一般为第一个字母),选择一个固定的位置(第一个位置),然后对剩下的字母挨个选择并在剩余的位置上选择一个位置,如此继续,便可以得出将第一个字母在第一个位置上的剩余字符串的全排列;变换第一个位置上的字母,重复上面的过程,直到所有字母都已经到了第一个位置上呆过位置。

#include <iostream>
#include <string>
using namespace std;
void permute(string str);
void doPermute(const string &in, string &out, bool *isUsed, int len, int level);
int main()
{
    permute("abc");
    return 0;
}
void permute(string str) {
    int len = str.size();
    bool *isUsed = new bool[len];
    for(int i = 0; i < len; i++)
        isUsed[i] = false;
    string out;
    string in = str;
    doPermute(in, out, isUsed, len, 0); //调用递归部分
    delete isUsed;
}
void doPermute(const string &in, string &out, bool *isUsed, int len, int level) {
    if(level == len) {
        cout << out << endl;
        return ;
    }
    for(int i = 0; i < len; ++i) {
        if(isUsed[i])
            continue;
        out.push_back(in[i]);
        isUsed[i] = true;
        doPermute(in, out, isUsed, len, level + 1);
        isUsed[i] = false;
        out.erase(out.size() - 1, 1); //去除已加上的这个字符,为接下来的继续递归做好准备
    }
}


从上面的简单的分析可以看出有个明显的缺点,就是如果字符串有重复的字符,如aab,那么不能在第一个位置上重复选择a了吧!因此需要采用一种新的办法,来标记已经选择过的字符,下次出现了同一字符,则需要跳过。不多说了,直接上代码。

void Premutation(char* pStr)
{
    //if(pStr.size()==0) 
    if(pStr==NULL)
        return;
    //char* a=pStr; 
    //Pre(a,a); 
    Pre(pStr,pStr);
    return;
}
void Pre(char* pStr,char* pBegin) 
{ 
    if(*pBegin=='\0')
        cout<<pStr<<endl;
    map<char,bool> isUsed;//利用map来查重
    for(char* ch=pBegin;*ch!='\0';ch++)
    {
        if(isUsed[*ch])
            continue;
        else
            isUsed[*ch]=true; 
        char temp=*ch;
        *ch=*pBegin;
        *pBegin=temp;
        Pre(pStr,pBegin+1);
        *pBegin=*ch;
        *ch=temp;
    } 
} 


其实字符串全排列问题是8皇后问题的一个小问题,先说说8皇后问题吧!

皇后问题,是一个古老而著名的问题,是回溯算法的典型例题。该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种方法可以解决此问题。


根据8皇后摆放位置的要求,可以看出就是一个字符串重排的问题。可以将8*8象棋格子认为是2个包含8个元素的数组。其中一个数组表示水平方向的位置,而另外一个表示竖直方向上的位置。而将行看成不变的数组来标记皇后放的位置,因此对其中一个数组进行全排列就能够保证任意两个皇后肯定不同行不同列。接下来,我们只需要判断得到的每一个排列对应的八个皇后是不是在同一对角斜线上,也就是数组的两个下标i和j,是不是i-j==ColumnIndex[i]-Column[j]或者j-i==ColumnIndex[i]-ColumnIndex[j]。 下面是代码实现:

首先修改全排列的函数实现:

int number = 0;//记录满足要求八皇后排列次数
void Permutation(int ColumnIndex[], int index, int length=8)
{
    if(index == length)//如果排列完成,就检测数组是否满足要求
    {
        if(CheckQueen(ColumnIndex, length))
        {
            ++ number;
            PrintQueen(ColumnIndex, length);//打印出符合要求的八皇后数组排列
        }
    }
    else
    {
        for(int i = index; i < length; ++ i)
        {
            int temp = ColumnIndex[i];
            ColumnIndex[i] = ColumnIndex[index];
            ColumnIndex[index] = temp;
 
            Permutation(ColumnIndex, index + 1, length);
 
            temp = ColumnIndex[index];
            ColumnIndex[index] = ColumnIndex[i];
            ColumnIndex[i] = temp;
        }
    }
}
//检查是否符合八皇后的位置要求
bool CheckQueen(int ColumnIndex[], int length)
{
    for(int i = 0; i < length; ++ i)
    {
        for(int j = i + 1; j < length; ++ j)
        {
            if((i - j == ColumnIndex[i] - ColumnIndex[j])
                || (j - i == ColumnIndex[i] - ColumnIndex[j]))
            return false;
        }
    }
 
    return true;
}
 
void PrintQueen(int ColumnIndex[], int length)
{
    printf("满足八皇后的第%d个摆放位置为:\n", number);
 
    for(int i = 0; i < length; ++i)
        printf("%d\t", ColumnIndex[i]);
    printf("\n");
}
void EightQueen()
{
    const int queens = 8;
    int ColumnIndex[queens];
    for(int i = 0; i < queens; ++ i)
        ColumnIndex[i] = i;//初始化皇后在每一列的位置
    Permutation(ColumnIndex, 0, queens);
}

更多8皇后的解法参见:http://blog.csdn.net/hackbuteer1/article/details/6657109




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值