剑指offer 28- 字符串的排列

一 、全排列的递归实现

为方便起见,用123来示例下。123的全排列有123、132、213、231、312、321这六种。首先考虑213和321这二个数是如何得出的。显然这二个都是123中的1与后面两数交换得到的。然后可以将123的第二个数和每三个数交换得到132。同理可以根据213和321来得231和312。因此可以知道——全排列就是从第一个数字起每个数分别与它后面的数字交换。找到这个规律后,递归的代码就很容易写出来了:

#include<stdio.h>
#include<iostream>
using namespace std;

void Permutation(char* pStr, char* pBegin)
{	
    if(*pBegin == '\0')
    {
        cout<< pStr<<endl;		
    }
    else
    {
        for(char* pCh = pBegin; *pCh != '\0'; ++ pCh)
        {
            swap(*pCh, *pBegin);         
			Permutation(pStr, pBegin + 1);		
		    swap(*pCh, *pBegin);			
        }
    }
}

// ====================测试代码====================
void Test(char* pStr)
{
    if(pStr == NULL)
        printf("Test for NULL begins:\n");
    else
        printf("Test for %s begins:\n", pStr);

	 if(pStr == NULL)
        return;
    Permutation(pStr, pStr);

    printf("\n");
}

int main(int argc, char* argv[])
{
   Test(NULL);

    char string1[] = "";
    Test(string1);

    char string2[] = "a";
    Test(string2);

    char string3[] = "ab";
    Test(string3);

    char string4[] = "abc";
    Test(string4);

    return 0;
}

2.另外一种写法  且适于int数组

#include<iostream>
using namespace std;

void  Permutation(char A[], int start, int end)
{  
    if (start >= end)   //结束条件
    {
       cout<<A<<endl;        
    }
    else
    {
        for (int i = start; i <= end; ++i)
        {
            swap(A[start], A[i]);
            Permutation(A, start + 1, end);
            swap(A[start], A[i]);
        }
    }   
}
int main(int argc, char *argv[])
{
    char A[] = "abc";
    Permutation(A, 0, strlen(A)-1);
    return 0;
}

二、去掉重复的全排列的递归实现

对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。
这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。下面给出完整代码:

#include<iostream>
using namespace std;
#include<assert.h>

//在[nBegin,nEnd)区间中是否有字符与下标为pEnd的字符相等
bool IsSwap(char* pBegin , char* pEnd)
{
	for(char *pCh = pBegin;pCh<pEnd; pCh++)
	{
		if(*pCh == *pEnd)
			return false;
	}
	return true;
}
void Permutation(char* pStr , char *pBegin)
{
	assert(pStr && pBegin);
	if(*pBegin =='\0')
		cout<<pStr<<endl;
	else
	{
		for(char *pCh = pBegin;*pCh!='\0';pCh++)
		{
			if(IsSwap(pBegin,pCh))
			{
				swap(*pCh,*pBegin);
				Permutation(pStr,pBegin+1);
				swap(*pCh,*pBegin);
			}
		}
	}
}

int main(void)
{
	char str[] = "baa";
	Permutation(str , str);
	return 0;
}

三、全排列的非递归实现

#include<iostream>
#include<algorithm>
using namespace std;
#include<assert.h>
void Reverse(char *pBegin,char *pEnd)
{
	while(pBegin < pEnd)
		swap(*pBegin++,*pEnd--);
}
int cmp(const void * a,const void * b)
{
	return int(*(char *)a -*(char *)b );
}
bool Next_Permutation(char  a[])
{
	char *p,*q,*pEnd;
	pEnd = a+strlen(a)-1;
	if(a==pEnd)
		return false;
	p=pEnd;
	while(p!=a)
	{
		q=p;
		p--;
		if(*p<*q)
		{
			char *pFind = pEnd;
			while(*pFind<*p)
				pFind--;
			swap(*pFind,*p);
			Reverse(p+1,pEnd);
			return true;
		}
	}
	Reverse(a , pEnd); 
	return false;
}

int main(void)
{
	char str[] = "4321";
	qsort(str,strlen(str),sizeof(char),cmp);
	int number =1;
	do
	{
		cout<<number++<<"  "<<str<<endl;
	}while(Next_Permutation(str));
	
	return 0;
}

四 、字符串的组合

题目:输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。

上面我们详细讨论了如何用递归的思路求字符串的排列。同样,本题也可以用递归的思路来求字符串的组合。
假设我们想在长度为n的字符串中求m个字符的组合。我们先从头扫描字符串的第一个字符。针对第一个字符,我们有两种选择:第一是把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符;第二是不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。这两种选择都很容易用递归实现。下面是这种思路的参考代码:

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
#include<assert.h>

void Combination(char *string ,int number,vector<char> &result);

void Combination(char *string)
{
	assert(string);
	vector<char> result;
	int length = strlen(string)-1;
	for(int i=1;i<=length;i++)
		Combination(string,i,result);
}

void Combination(char *string ,int number , vector<char> &result)   
{
	assert(string);
	if(number == 0)
	{		
		for(vector<char>::iterator iter = result.begin();iter<result.end();iter++)
			cout<<*iter;
		cout<<endl;
		return;
	}
	if(*string=='\0')
		return ;
	result.push_back(*string);
	Combination(string+1,number-1,result);
	result.pop_back();
	Combination(string+1,number,result);	
}

int main()
{
	char str[] = "abc";
	Combination(str);
	return 0;
}

方法二:用位运算来实现求组合

#include<iostream>
using namespace std;

char str[] = "abcde";

void print_subset(int n , int s) 
{	
	for(int i=0;i<n;i++)
	{
		if(s & (1<<i))
			cout<<str[i]<<" ";
	}
	cout<<endl;
}

void subset(int n)
{
	for(int i=1;i<(1<<n);i++)
		print_subset(n,i);
}

int main(void)
{
	subset(5);	
	return 0;
}

字符串全排列扩展----八皇后问题

    题目:在8×8的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行、同一列或者同一对角斜线上。下图中的每个黑色格子表示一个皇后,这就是一种符合条件的摆放方法。请求出总共有多少种摆法。


    这就是有名的八皇后问题。解决这个问题通常需要用递归,而递归对编程能力的要求比较高。因此有不少面试官青睐这个题目,用来考察应聘者的分析复杂问题的能力以及编程的能力。

由于八个皇后的任意两个不能处在同一行,那么这肯定是每一个皇后占据一行。于是我们可以定义一个数组ColumnIndex[8],数组中第i个数字表示位于第i行的皇后的列号。先把ColumnIndex的八个数字分别用0-7初始化,接下来我们要做的事情就是对数组ColumnIndex做全排列。由于我们是用不同的数字初始化数组中的数字,因此任意两个皇后肯定不同列。我们只需要判断得到的每一个排列对应的八个皇后是不是在同一对角斜线上,也就是数组的两个下标i和j,是不是i-j==ColumnIndex[i]-Column[j]或者j-i==ColumnIndex[i]-ColumnIndex[j]。


#include<iostream>
using namespace std;

int g_number = 0;
void Permutation(int * , int  , int );
void Print(int * , int );

void EightQueen( )
{
	const int number =8;
	int a[number];
	for(int i=0;i<number;i++) //初始化
		a[i]=i;
	Permutation(a,number,0);
}

bool Check(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 Permutation(int ColumnIndex[] , int length , int index) // 01234567   8   0
{
	if(length == index)
	{
		if(Check(ColumnIndex,length))   //检测棋盘当前的状态是否合法
		{
			++g_number;
			cout<<g_number<<endl;
			Print(ColumnIndex,length);
		}
	}
	else
	{
		for(int i=index;i<length;i++) //全排列
		{
			swap(ColumnIndex[i],ColumnIndex[index]);
			Permutation(ColumnIndex,length,index+1);
			swap(ColumnIndex[i],ColumnIndex[index]);
		}
	}
	
}

void Print(int ColumnIndex[] , int length)
{
	for(int i=0;i<length;i++)
		cout<<ColumnIndex[i]<<" ";
	cout<<endl;
}

int main(void)
{
	EightQueen();
	return 0;
}

题目:输入两个整数n和m,从数列1,2,3...n中随意取几个数,使其和等于m,要求列出所有的组合。

#include<iostream>
#include<vector>
using namespace std;

vector<int> result;

void find_factor(int sum,int n)
{
	if(n<=0||sum<=0)
		return;
	if(sum==n)
	{			
		for(vector<int>::iterator iter=result.begin();iter!=result.end();iter++)
			cout<<*iter<<"+";
		cout<<n<<endl;		
	}	
	result.push_back(n);
	find_factor(sum-n,n-1);//n放在里面
	result.pop_back();
	find_factor(sum,n-1);//n不放在里面	
}

int main()
{	
	find_factor(9,9);
	return 0;
}
文字转自 http://blog.csdn.net/hackbuteer1/article/details/7462447



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值