首页 > 代码库 > 全排列与字典序排列

全排列与字典序排列

首先,全排列是一个比较简单的问题,但我却没有真正的去实现过全排列。

让我独自思考全排列的话,如将 “ abcd“ 进行全排列,这种简单的全排列也能将我难住,因为真的没有考虑过这种问题。思考了一会,我只能给出以下比较麻烦的算法:

//字符串全排列
void printRE(char* str,int index,char s[],int length){
	if(index == length)
		printf("%s \n",str);
	else{
		bool exsist = false;
		for(int i = 0 ; i < length;i++){
			exsist = false;
			for(int j= 0; j < index;j++){
				if(s[i] == str[j]){
					exsist = true;
					break;
				}
			}
			if(!exsist){
				str[index] = s[i];
				printRE(str,index+1,s,length);
			}
		}		
	}
}
void PrintAll(char s[],int length){
	char* str = new char[length+1];
	str[length] = '\0';
	printRE(str,0,s,length);
	delete[] str;
}
这里使用一个字符串数组str来当作栈使用,实现了递归中的回溯。这里对字符是否使用过的判断就是遍历栈中元素,看之前部分的字符串中是否已用某些字符。

其实这些天多看了许多算法许多思路后,反而陷入了一种不知道解题方法就完全不去思考的状态,唉,脑子坏掉拉。

这里更简单的一种思路是,全排列即从第1个字母开始,与之后的字母交换,然后递归的输出全部:

void printALLA(char* str,char *begin){
	if('\0' == *begin)
		printf("%s \n",str);
	else{
		for(char* p =  begin;*p != '\0';p++){
			swap(*p,*begin);
			printALLA(str,begin+1);
			swap(*p,*begin);//返回原来的状态。
		}
	}

}

这是递归的方法。


非递归一般来说,字符串全排列可以使用字典序排列来实现。

这里字典序排列的算法很有意思,要记一下:

对于进行字典序排序的字符串 str ,从右向做寻找第一个小于右边元素的点 str[i]; 然后在i到字符串末尾寻找一个大于str[i]的最小字符 str[j], 交换str[i] 和 str[j]. 并将i之后的子串颠倒,得到一个新的字典序排序的结果。之后在这个结果的基础上去寻找下一个排序。
这个算法是很有意思的,这样的精致的思路是如何产生的,我无法知道,但是我了解到这样做的用意。首先,一开始字符串是已经排序好的字典序的开始状态,然后每一次循环的开始,都是将字符串分为两段,左边为 未进行字典排序的子段,而右边是已经进行字典排序的子段,而且这个子段之前的字典序已经被遍历并输出,这里寻找第一个小于右边的元素 str[i] ,而这个顺序之前的字典序已被输出的情况下,且右边字段必定是从大到小排序的,这样右边子段的字典序也被全部输出,这也说明对于str[i] 之前和包括自身已经全部遍历,这时要将str[i]换一个更大的元素来进行排列,而这个数就是右边字段中大于str[i]的最小元素str[j],交换str[i] 和str[j] ,,遍历以str[j] 开始的新的字典序,而这时候将右边字段倒转,因为之前是从大到小排序的,交换后就是从小到大排序,而这也就是str[j]为开始时,右边子段的第一个字典序。如此反复即可输出全部的字典序。


然后简单的写出字典序排列的代码:

void printLexOrder(char s[],int length){
	int charBarrel[128];
	memset(charBarrel,0,128*4);
	for(int i = 0 ; i < length;i++){
		charBarrel[ s[i] ] ++;
	}
	int i,j;
	i = 0;
	char* str = new char[length+1];
	str[length] = '\0';
	for(j = 0 ; j < 128;j++){
		while(charBarrel[j]  > 0){
			charBarrel[j]--;
			str[i++] = j;
		}
	}

	int  min;//最小的大于i的字符 的下标
	char* stack = new char[length];
	int stacktop = 0;//使用一个栈来实现字符串的颠倒
	printf("%s \n",str);
	while(i > -1){
		j = length-1;
		i = j - 1;
		while(str[i] >= str[j]){
			i--;
			j--;
			if(i<0)
				break;
		}
		if(i<0)break;//跳出两层循环
		j = i+1;
		min = j;
		while(j<length){
			if(str[j] > str[i] && str[j] <= str[min]){
				min = j;
			}
			j++;
		}
		j = min;
		stack[stacktop] = str[i];
		str[i] = str[j];
		str[j] = stack[stacktop];
		for(j = i+1;j<length;j++)
			stack[stacktop++] = str[j];
		while(stacktop > 0)
			str[++i] = stack[--stacktop];
		printf("%s \n",str);
	}
	delete[] stack, str;
}




全排列与字典序排列