算法学习-字符串的全排列

题目

给定字符串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[n]);
		Permutation(a, size, n+1);
		swap(a[i], a[n]);
	}
}

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[n] == 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[n]);
		Permutation(a, size, n+1);
		swap(a[i], a[n]);
	}
}

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[n]);
		Permutation(a, size, n+1);
		swap(a[i], a[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[n] == 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数组,一个八皇后问题,以后再处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值