一、实验名称
金币阵列问题;
有m*n枚金币在桌面上排列成一个m行n列的金币阵列。每一枚金币或正面朝上,或背面朝上。用数字表示金币状态,0表示正面朝上,1表示背面朝上。
金币阵列游戏的规则是:
(1)每次将任一行金币翻过来放在原来的位置上。
(2)每次可以任选2列,交换这2列金币的位置。
任务:给定金币的初始状态和目标状态,编程计算按金币游戏规则,将金币排列从初始状态变换到目标状态所需的最少变换次数。
二、实验目的
通过上机实验,要求掌握金币阵列问题的问题描述、算法设计思想、程序设计。
三、实验原理
解决金币阵列问题,并计算出程序运行所需要的时间。
四、实验步骤
1. 首先考虑行的反转。将初始状态存在二维数组start[][]中,目标数组设为goal[][],若start的第i行中0的个数与goal的第i行中1的个数相等,该行翻转后的结果才可能有意义,则该行进行翻转操作。
2. 所有行都判断完后考虑列的交换。从goal的第1列开始,在start中找一列和该列 i 相同的列 j ,且 j 必须大于 i ,将 i 和 j 交换。
所有操作进行完后判断start和goal是否相同,若相同,返回操作次数,不相同则返回-1
五、关键代码
//行反转
int reverseRow(int a[maxn][maxn],int r,int n)
{
for(int i=0;i<n;i++) a[r][i]^=1;
return 1;
}
//列交换
int exchangeColumn(int a[maxn][maxn],int m,int j,int k)
{
if(j==k) return 0;
for(int i=0;i<m;i++) swap(a[i][j],a[i][k]);
return 1;
}
//判断两数组的j列和k列是否相等
bool judgeColumn(int a[maxn][maxn],int b[maxn][maxn],int m,int j,int k)
{
for(int i=0;i<m;i++) if(a[i][j]!=b[i][k]) return false;
return true;
}
//拷贝数组
void resetArray(int a[maxn][maxn],int b[maxn][maxn],int m,int n)
{
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
a[i][j]=b[i][j];
}
}
}
//获取最小处理步数
int getMinSteps(int a[maxn][maxn],int b[maxn][maxn],int c[maxn][maxn],int m,int n)
{
int num=0,minSteps=0x7fffffff;
for(int k=0;k<n;k++)
{
//将k列放在第一列 并且适当变换使其与原数列第一列相同
num+=exchangeColumn(a,m,0,k);
for(int i=0;i<m;i++)
{
if(a[i][0]!=b[i][0]) num+=reverseRow(a,i,n);
}
int j=1;
for(;j<n;j++)
{
int f=0;
int temp_r=0;
int r=j;
for(;r<n;r++)
{
//依次判断数组后面的列是否和目标数列的列相同
if(judgeColumn(a,b,m,r,j))
{
if(judgeColumn(a,b,m,r,r)) //如果要交换的列已经匹配,暂时不要交换,继续往后遍历
{
f=1; //记录下这次情况
temp_r=r; //记录下可以交换的列
}
else{
//否则交换两列
f=0;
num+=exchangeColumn(a,m,j,r);
break;
}
}
}
if(f==1){ //如果还没发生交换,则交换之前记录的列
num+=exchangeColumn(a,m,j,temp_r);
}
if(r==n&&f==0) break; //没有找到相同的列,提前结束循环
}
//找到了这样的交换方式
minSteps= (j==n)?min(minSteps,num):minSteps;
num=0;
resetArray(a,c,m,n); //重置temp数组
}
return minSteps==0x7fffffff?-1:minSteps;
}
六、测试结果
时间复杂度: O(mn³)
翻转所有的行需要时间O(m)。
需要遍历后序每一列,需要时间O(n)
需要找到匹配的每一列,每次判断需要O(m),共n次判断,并且有n列,总复杂度O(mn²)
总时间复杂度为O(n(m+(mn²)))=O(mn+mn³) = O(mn³)
七、实验心得
通过这次实验,我了解熟悉了金币阵列问题的求解过程及原理。存在问题是本题的案列自己实现较为困难,直接用的评测网站上给出的样例。
八、完整代码
#include<iostream>
#include<fstream>
#include<time.h>
#include <windows.h>
using namespace std;
const int maxn=110;
int originalArray[maxn][maxn],goalArray[maxn][maxn],tempArray[maxn][maxn];
//思路,枚举可能变换的所有的方式,最后进行比较
//行反转
int reverseRow(int a[maxn][maxn],int r,int n)
{
for(int i=0;i<n;i++) a[r][i]^=1;
return 1;
}
//列交换
int exchangeColumn(int a[maxn][maxn],int m,int j,int k)
{
if(j==k) return 0;
for(int i=0;i<m;i++) swap(a[i][j],a[i][k]);
return 1;
}
//判断两数组的j列和k列是否相等
bool judgeColumn(int a[maxn][maxn],int b[maxn][maxn],int m,int j,int k)
{
for(int i=0;i<m;i++) if(a[i][j]!=b[i][k]) return false;
return true;
}
//拷贝数组
void resetArray(int a[maxn][maxn],int b[maxn][maxn],int m,int n)
{
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
a[i][j]=b[i][j];
}
}
}
//获取最小处理步数
int getMinSteps(int a[maxn][maxn],int b[maxn][maxn],int c[maxn][maxn],int m,int n)
{
int num=0,minSteps=0x7fffffff;
for(int k=0;k<n;k++)
{
//将k列放在第一列 并且适当变换使其与原数列第一列相同
num+=exchangeColumn(a,m,0,k);
for(int i=0;i<m;i++)
{
if(a[i][0]!=b[i][0]) num+=reverseRow(a,i,n);
}
int j=1;
for(;j<n;j++)
{
int f=0;
int temp_r=0;
int r=j;
for(;r<n;r++)
{
//依次判断数组后面的列是否和目标数列的列相同
if(judgeColumn(a,b,m,r,j))
{
if(judgeColumn(a,b,m,r,r)) //如果要交换的列已经匹配,暂时不要交换,继续往后遍历
{
f=1; //记录下这次情况
temp_r=r; //记录下可以交换的列
}
else{
//否则交换两列
f=0;
num+=exchangeColumn(a,m,j,r);
break;
}
}
}
if(f==1){ //如果还没发生交换,则交换之前记录的列
num+=exchangeColumn(a,m,j,temp_r);
}
if(r==n&&f==0) break; //没有找到相同的列,提前结束循环
}
//找到了这样的交换方式
minSteps= (j==n)?min(minSteps,num):minSteps;
num=0;
resetArray(a,c,m,n); //重置temp数组
}
return minSteps==0x7fffffff?-1:minSteps;
}
bool** rand_arr(int m,int n){
//随机创建一个m行,n列的数组
bool **arr=new bool*[m];
for(int i=0;i<m;i++)
arr[i]=new bool[n];
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
arr[i][j]=rand()%2;
return arr;
}
void change_arr(bool** arr,int m,int n){
//对数组arr做题目要求的那两种变换
int num=(rand()%100)+100;//变换次数为100-199次的随机数
while(num--){
bool choice=rand()%2;
if(choice){//如果choice1,做翻转一行的变换
int line=rand()%m;//翻转这一行
for(int i=0;i<n;i++)
arr[ line][i]=arr[ line][i];
}
else{//否则调换两列
int first_lie=rand( )%n;
int second_lie=rand()%n;
for(int i=0;i<m;i++){
bool tmp=arr[i][first_lie];
arr[i][first_lie]=arr[i][second_lie];
arr[i][second_lie]=tmp;
}
}
}
}
int main(){
ofstream out1("output.txt");
while(1){
//生成规模为n的随机数
cout<<"请输入数据规模m,n:"<<endl;
int m,n;
cin>>m>>n;
bool **arr=new bool*[m];
for(int i=0;i<m;i++)
arr[i]=new bool[n];
arr=rand_arr(m,n);
bool **goal=new bool*[m];
for(int i=0;i<m;i++)
goal[i]=new bool[n];
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
goal[i][j]=arr[i][j];
change_arr(goal,m,n);
ofstream out("input.txt");
out<<1<<'\n';
out<<m<<' '<<n<<'\n';
for(int i=0;i<m;i++){
for(int j=0;j<n;j++)
out<<arr[i][j]<<' ';
out<<endl;
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++)
out<<goal[i][j]<<' ';
out<<endl;
}
out.close();
ifstream in("input.txt");
int num;
in>>num;
LARGE_INTEGER nFreq,nBegin,nEnd;
double time;
QueryPerformanceFrequency(&nFreq);
while(num--){
QueryPerformanceCounter(&nBegin);
int n,m;
in>>m>>n;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
in>>originalArray[i][j];
}
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
in>>goalArray[i][j];
}
}
resetArray(tempArray,originalArray,m,n);//拷贝数组
int result=getMinSteps(tempArray,goalArray,originalArray,m,n);
QueryPerformanceCounter(&nEnd);
time=(double)(nEnd.QuadPart-nBegin.QuadPart)/(double)nFreq.QuadPart;
//cout<<"变换次数:"<<result<<"\n 用时:"<<time<<endl<<endl;
out1<<time<<endl;
}
}
out1.close();
return 0;
}