算法学习-字符串的全排列
2016-11-16 11:41
141 查看
题目
给定字符串S[0...N-1],设计算法,枚举S的全排列
分析
以1234为例:
1-234
2-134
3-214
4-231
如何保证不遗漏:保证递归前1234的顺序不变
代码如下
其实全排列可用如下图来进行表示
![](https://img-blog.csdn.net/20161116114238048?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
我们可以发现刚才写全排列的代码其实就是该数的一个从跟结点的深度优先搜索,所以可以理解刚才的代码是一颗隐树,也可以知道深度优先搜索DFS用递归实现,可以有下想法:一般递归背后藏着一个隐式的树,该树用深度优先搜索的方式就可以获得结论,递归是实现手段,深搜是方法。
如果字符有重复
义字符1223为例:
1-223
2-123
3-223
带重复字符的全排列就是每个字符分别与它后面非重复出现的字符交换。
即:第i个字符(前)与第j个字符(后)交换时,要求[i,j)中没有与第j个字符相等的数。
代码稍作变化,如下
第一个算法的时间复杂度计算f(N) = N*f(N-1),f(1) = 1所以时间复杂度为O(N)=N!
第二个算法的时间复杂度为f(N)=N*(N + f(N-1)) = (N+1)!
对于第二种情况可以通过空间换时间的方法将其降低到N!的算法复杂度,代码如下
下面考虑一下全排列的非递归算法,理论上每个递归都可以找到一个非递归的替换方法
起点:字典序最小的排列,例如12345
终点:字典序最大的排列,例如54321
过程:从当前排列生成字典序刚好比它大的下一个排列
如:21543的下一个排列是23145
逐位考察哪个能增大:一个数右面有比它大的数最在,他就能增大;那么最后一个能增大的数是---------x=1
1应该增大到多少呢?它增大到它右面比它大的最小的数----------------y=3,应该变为23145
步骤总结:后找,小大,交换,翻转
后找:字符串中最后一个升序位置i,即S[k]>S[k+1](k>i),S[i]<S[i+1]
查找大小:S[i+1...N-1]中比Ai大的最小值Sj;
交换:Si, Sj;
翻转:S[i+1...N-1]。交换操作后,S[i+1...N-1]一定是降序的,从找到i的过程就可以推理出来。以926520可以为例测试一下
非递归代码如下
// suanfa1.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
void Print(const int* a, int size)
{
for (int i = 0; i < size; i++)
{
std::cout<<a[i]<<' ';
}
std::cout<<std::endl;
}
bool IsDuplicate(const int* a, int n, int t)
{
while (n < t)
{
if (a
== a[t])
{
return false;
}
n++;
}
return true;
}
void swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
void Reverse(int* from, int* to)
{
int t;
while(from < to)
{
t = *from;
*from = *to;
*to = t;
from++;
to--;
}
}
bool GetNextPermutation(int* a, int size)
{
int i = size - 2;
while((i >= 0) && (a[i] >= a[i + 1]))
i--;
if (i < 0)
{
return false;
}
int j = size - 1;
while(a[j] <= a[i])
j--;
swap(a[j], a[i]);
Reverse(a+i+1, a+size-1);
return true;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a[] = {1,2,3,4};
while(GetNextPermutation(a, sizeof(a) / sizeof(int)))
Print(a, sizeof(a) / sizeof(int));
system("pause");
return 0;
}
非递归可以直接解决重复问题,而且如果给出的不是升序的,需要先升序处理
全排列问题还有两个有关系的应用一个是Cantor数组,一个八皇后问题,以后再处理
给定字符串S[0...N-1],设计算法,枚举S的全排列
分析
以1234为例:
1-234
2-134
3-214
4-231
如何保证不遗漏:保证递归前1234的顺序不变
代码如下
#include "stdafx.h" #include <iostream> void Print(const int* a, int size) { for (int i = 0; i < size; i++) { std::cout<<a[i]<<' '; } std::cout<<std::endl; } void swap(int& a, int& b) { int tmp = a; a = b; b = tmp; } void Permutation(int* a, int size, int n) { if (n == size - 1) { Print(a, size); return; } for (int i = n; i < size; i++) { swap(a[i], a ); Permutation(a, size, n+1); swap(a[i], a ); } } int _tmain(int argc, _TCHAR* argv[]) { int a[] = {1,2,3,4}; Permutation(a, sizeof(a) / sizeof(int), 0); system("pause"); return 0; }
其实全排列可用如下图来进行表示
我们可以发现刚才写全排列的代码其实就是该数的一个从跟结点的深度优先搜索,所以可以理解刚才的代码是一颗隐树,也可以知道深度优先搜索DFS用递归实现,可以有下想法:一般递归背后藏着一个隐式的树,该树用深度优先搜索的方式就可以获得结论,递归是实现手段,深搜是方法。
如果字符有重复
义字符1223为例:
1-223
2-123
3-223
带重复字符的全排列就是每个字符分别与它后面非重复出现的字符交换。
即:第i个字符(前)与第j个字符(后)交换时,要求[i,j)中没有与第j个字符相等的数。
代码稍作变化,如下
// suanfa1.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> void Print(const int* a, int size) { for (int i = 0; i < size; i++) { std::cout<<a[i]<<' '; } std::cout<<std::endl; } bool IsDuplicate(const int* a, int n, int t) { while (n < t) { if (a == a[t]) { return false; } n++; } return true; } void swap(int& a, int& b) { int tmp = a; a = b; b = tmp; } void Permutation(int* a, int size, int n) { if (n == size - 1) { Print(a, size); return; } for (int i = n; i < size; i++) { if (!IsDuplicate(a, n, i)) { continue; } swap(a[i], a ); Permutation(a, size, n+1); swap(a[i], a ); } } int _tmain(int argc, _TCHAR* argv[]) { int a[] = {1,2,2,4}; Permutation(a, sizeof(a) / sizeof(int), 0); system("pause"); return 0; }
第一个算法的时间复杂度计算f(N) = N*f(N-1),f(1) = 1所以时间复杂度为O(N)=N!
第二个算法的时间复杂度为f(N)=N*(N + f(N-1)) = (N+1)!
对于第二种情况可以通过空间换时间的方法将其降低到N!的算法复杂度,代码如下
void Permutation(int* a, int size, int n) { if (n == size - 1) { Print(a, size); return; } int dup[256] = {0}; for (int i = n; i < size; i++) { if (dup[a[i]] == 1) { continue; } dup[a[i]] = 1; swap(a[i], a ); Permutation(a, size, n+1); swap(a[i], a ); } }
下面考虑一下全排列的非递归算法,理论上每个递归都可以找到一个非递归的替换方法
起点:字典序最小的排列,例如12345
终点:字典序最大的排列,例如54321
过程:从当前排列生成字典序刚好比它大的下一个排列
如:21543的下一个排列是23145
逐位考察哪个能增大:一个数右面有比它大的数最在,他就能增大;那么最后一个能增大的数是---------x=1
1应该增大到多少呢?它增大到它右面比它大的最小的数----------------y=3,应该变为23145
步骤总结:后找,小大,交换,翻转
后找:字符串中最后一个升序位置i,即S[k]>S[k+1](k>i),S[i]<S[i+1]
查找大小:S[i+1...N-1]中比Ai大的最小值Sj;
交换:Si, Sj;
翻转:S[i+1...N-1]。交换操作后,S[i+1...N-1]一定是降序的,从找到i的过程就可以推理出来。以926520可以为例测试一下
非递归代码如下
// suanfa1.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
void Print(const int* a, int size)
{
for (int i = 0; i < size; i++)
{
std::cout<<a[i]<<' ';
}
std::cout<<std::endl;
}
bool IsDuplicate(const int* a, int n, int t)
{
while (n < t)
{
if (a
== a[t])
{
return false;
}
n++;
}
return true;
}
void swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
void Reverse(int* from, int* to)
{
int t;
while(from < to)
{
t = *from;
*from = *to;
*to = t;
from++;
to--;
}
}
bool GetNextPermutation(int* a, int size)
{
int i = size - 2;
while((i >= 0) && (a[i] >= a[i + 1]))
i--;
if (i < 0)
{
return false;
}
int j = size - 1;
while(a[j] <= a[i])
j--;
swap(a[j], a[i]);
Reverse(a+i+1, a+size-1);
return true;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a[] = {1,2,3,4};
while(GetNextPermutation(a, sizeof(a) / sizeof(int)))
Print(a, sizeof(a) / sizeof(int));
system("pause");
return 0;
}
非递归可以直接解决重复问题,而且如果给出的不是升序的,需要先升序处理
全排列问题还有两个有关系的应用一个是Cantor数组,一个八皇后问题,以后再处理
相关文章推荐
- 每天学习一算法系列(1)(定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部)
- 每天学习一算法系列(20)(输入一个表示整数的字符串,把该字符串转换成整数并输出)
- 算法学习之数组和字符串
- 字符串全排列生成算法
- 算法学习笔记(4)----字符串字符唯一
- 算法学习笔记之四:巧妙运用指针解决链表、字符串、数组等问题(同向双指针VS对向双指针)
- 【算法学习】【字符串处理】【原题】【noip 2005 T4】等价表达式
- 算法题:字符串的全排列
- 算法学习七----在一个字符串中找到第一个只出现一次的字符
- 每天学习一算法系列(22)(在字符串中找出连续最长的数字串,并把这个串的长度返回)
- 字符串是否包含问题--算法学习--不断优化才是算法的王道
- 算法面试-学习笔记-左旋转字符串
- [转] 算法学习之字符串左移和右移
- [算法学习]在字符串中删除特定的字符
- 每天学习一算法系列(17)(在一个字符串中找到第一个只出现一次的字符)
- 2011.10.21算法学习日志-生成元素的全排列
- 算法 - 输出一个字符串的全排列(C++)
- 算法习题53:字符串全排列问题
- 我的算法学习(一)----数组的全排列