C语言全排列的实现
1. 问题描述
最近在做一个相似航班的项目,那么问题来了,究竟什么是相似航班呢?所谓的相似航班就是将航班号大致相同的两个或两个以上的航班以不同的颜色显示到显示屏上,以避免管制误看了相似的航班号而导致误报航班信息造成事故。先来介绍一下航班号由什么构成的,航班号是由三个代表航空公司的字母以及代表飞机编号的三位或四位的数字组成。相似航班有很多种情况,现在主要来阐述一下具体的一个需求,也即空管所提需求的③:公司相同,字符串中飞机编号即数字相同,但是数字位置不同。其实利用面向对象的语言Java、C#实现这一功能并不难只需调用Contains方法即可。这里叙述一下我一开始的的想法,单单拿飞机编号为四位的情况来说,4位不完全相同的数字的排列组合有24种,也就是每一个航班号与之相似的就有23个,人工要找出这23种数字的排列方式实在是太麻烦,所以我想利用全排列的算法将这23种配对的方式找出来,问题的数学模型如下图。我把这个问题叫做BoxAndBall问题。
问题描述:有盒子A与盒子B分别能放四个球,盒子位置分别为①②③④,又有两组编号分别编号均为1234的球,每一个球恰好能放进盒子的一个位置。现在分别将右侧的球随机的放入左边的盒子里,求放好之后相同编号的球多对应盒子位置的配对组合。如图示例盒子位置的配对情况如下:
①-④ ②-③ ③-② ④-①
下面具体来实现以下使用C语言递归的算法来实现球的全排列并放入盒子的算法。
2. 算法描述
我们可以发现,输入123,输出的全排列共有123,132,213,231,312,321六种。
但是怎么用C语言来实现这种全排列的输出呢?我们直观的想法就是使用递归的方法来解决,经过分析可以发现:3个数(123)的排列,第一位1不动,剩下两个数(23)的排列,只要相互颠倒一下就可以出现关于1开头的所有排列,即123,132。把2换到第一位,保持不动,剩下的两个数(13)的排列只要相互颠倒一下们就可以出现关于2开头的所有排列213,231。同理,把3换到第一位,可得到312,321。扩展:把3个数的所有排列,前面加一个4,就可以得到关于4开头的所有排列,4123,4132,4213,4231,4312,4321。若把4与后续数据中的任意一个数据交换,通过完成对后续三个数的全排列,就可以得到相应的数开头的四数的排列。
所以,对4个数的排列,可以转换成首位不动,完成对3个数的排列,对3个数的排列,可以转换成首位不动,完成对2个数的排列,对2个数的排列,可以转换成首位不动,完成对1个数的排列,对于1个数,无排列,直接输出结果。下面是具体的算法实现。
所以,对4个数的排列,可以转换成首位不动,完成对3个数的排列,对3个数的排列,可以转换成首位不动,完成对2个数的排列,对2个数的排列,可以转换成首位不动,完成对1个数的排列,对于1个数,无排列,直接输出结果。下面是具体的算法实现。
3. 具体实现
下面是全排列的C语言是程序:
#include<stdio.h>
#include<string.h>
void Shift(char *s)
{
if (!s || !s[0]) return; //空串
char ch = s[0];
int i = 0;
while (s[++i])
{
s[i - 1] = s[i];
}//end while
s[i - 1] = ch;
}//end function Sh
//本函数对于一个已经排序好的数据进行全排列
void Permutation(char list[], int head)
{
int i; //局部变量,递归使用
int len; //存放字符串长度
len = strlen(list);
if (len - head == 1) //后续没有再排列的,则输出排列数据,head指的是除了最后一位之前的
{
printf("%s\n", list);
}//end if
else
{
for (i = head; i < len; i++) //从当前位置开始,每个数当一次队首,并进行后续排列
{
Permutation(list, head + 1); //后续串排列
Shift(&list[head]); //轮流当为第一个数
}//end for
}//end else
}//end function Per
/*获取字符串*/
void Arrangement(char *str)
{
Permutation(str, 0); //排列算法,从串的第几位开始排列
}
int main()
{
char str[] = "1234";
Arrangement(str);
return 0;
}
下面利用排列后的数据得出的位置匹配序列的C语言实现:
#include<stdio.h>
#include<string.h>
/*任务:对比两个相似串,输出相等字符对应位置*/
//将串左移一位,首位存到末尾
int nCountArrange = 0; //打印序号,输出排序后字符串序列使用
int nCountCrspdLoc = 1; //打印序号,输出对应位置使用
char cString[24][4]; //指针数组,用来存放排列后的数据
int jj = 0; //局部变量,存储全排列后的字符使用
void Shift(char *s)
{
if (!s || !s[0]) return; //空串
char ch = s[0];
int i = 0;
while (s[++i])
{
s[i - 1] = s[i];
}//end while
s[i - 1] = ch;
}//end function Sh
//本函数对于一个已经排序好的数据进行全排列
void Permutation(char list[], int head)
{
int i; //局部变量,递归使用
int len; //存放字符串长度
len = strlen(list);
if (len - head == 1) //后续没有再排列的,则输出排列数据,head指的是除了最后一位之前的
{
for (int k = 0; k < len; k++)
{
cString[jj][k] = list[k]; //使用char型指针数组来存放全排列后的字符串
}//end for
jj++;
}//end if
else
{
for (i = head; i < len; i++) //从当前位置开始,每个数当一次队首,并进行后续排列
{
Permutation(list, head + 1); //后续串排列
Shift(&list[head]); //轮流当为第一个数
}//end for
}//end else
}//end function Per
/*获取字符串*/
void Arrangement(char *str)
{
Permutation(str, 0); //排列算法,从串的第几位开始排列
}
/*打印排列后字符串序列*/
void PrintAfterArrangement()
{
for (int q = 0; q < 24; q++)
{
printf("%2d:", nCountArrange);
for (int w = 0; w < 4; w++)
{
printf("%c", cString[q][w]);
}
printf("\n");
nCountArrange++;
}
}
/*对比两个字符串并且输出对应位置*/
void CmpAndOutCrspdLoc(char *str1, char *str2)
{
int len; //字符串长度
int nLocation[4][2];
len = strlen(str1); //获取字符串长度
printf("%2d:", nCountCrspdLoc);
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (str1[i] == str2[j])
{
printf("%d-%d ", i + 1, j + 1); //输出对应位置,方便查看
}
}
}
nCountCrspdLoc++;
}
/*遍历所有字符串序列情况*/
void ArrangeAll(char* str)
{
for (int i = 0; i < 24; i++)
{
CmpAndOutCrspdLoc(str, cString[i]);
printf("\n");
}
}
int main()
{
char str[] = "1234";
Arrangement(str);
PrintAfterArrangement();
ArrangeAll(str);
return 0;
}
运行结果: