HNU_算法_实验1(2021级)-算法实验2-金币阵列问题

  一、实验名称

金币阵列问题;

有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;
}

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值