算法学习---一个博弈问题

引用自matrix67的博客。

让我们来玩一个游戏。下面有五行石子,白色的石子都是我的,黑色的石子都是你的。我们轮流拿走一个自己的石子,并且规定如果一个石子被拿走了,它后面的所有石子都要被扔掉。谁先没有拿的了,谁就输了。

○●●○●●○●●○
●○○●○●●○●
○○○○
●●●○●●●

..比如说,如果你先走的话,你可以把第四行的第三个石子拿走,按规定第四行将会只剩下前面两个石子:

○●●○●●○●●○

●○○●○●●○●

○○○○

●●

现在轮到我走了。我可以拿走第二行倒数第二个石子,于是整个棋局变成了这样:

○●●○●●○●●○

●○○●○●●

○○○○

●●

现在,假如说你拿走了第二行中的第一个石子(于是第二行就没了),那么我就赢定了。我可以拿走第一行中的第一个石子,从而让整个棋局只剩下后面三行:

○○○○

●●

要求给出一种最合理的走法。

如果是残局
○○○○
●● 

把每个白色石子记作 +1 ,把每个黑色石子记作 -1 。于是 ○○○○ + ●● + ● = 4 – 2 – 1 = 1 ,结果是一个正数(记这个结果为特征值),这就表明该局面下我将必胜,即使此时轮到我先走。

你会发现上面的说法很有道理 如果棋局是这样

○○
●●●●
 
○○ + ●●●● = 2 – 4 = -2 ,是一个负数,这就意味着不管谁先走,你都能必胜,

如果是

○○
●●

如果我先走你后走,你就赢定了;如果你先走我后走,我就赢定了。因为 和为0。

所以正确的走法:只需要走特征值最大的就可以了。

博客原文http://www.matrix67.com/blog/archives/6333

算法:

#include <stdio.h>
#include <stdlib.h>

template <class type>
void inline Swap(type &a,type &b)
{
	type tmp=a;
	a=b;
	b=tmp;
}

class Chess
{
public:
	Chess()
	{
		int i,j;
		int c[5][10]=
		{{0,1,1,0,1,1,0,1,1,0},
		{1,0,0,1,0,1,1,0,1,-1},
		{0,0,0,0,-1,-1,-1,-1,-1,-1},
		{1,1,1,0,1,1,1,-1,-1,-1},
		{1,-1,-1,-1,-1,-1,-1,-1,-1,-1} 	};  

		//1是黑棋 0是白棋

		for(i=0;i<5;i++)
			for(j=0;j<10;j++)
				chess[i][j]=c[i][j];
		rows=5;

	}

	Chess(Chess &other)
	{
		int i,j;
		for(i=0;i<other.rows;i++)
		{for(j=0;j<10;j++)
			chess[i][j]=other.chess[i][j];
		}
		rows=other.rows;

	}

	int isWin(int turn) const  //1该黑棋走,0该白棋走
	{
		if (rows==0) return 1;

		int i,j;
		for(i=0;i<rows;i++)
		{
			for(j=0;j<10;j++)
			{
				if(chess[i][j]==!turn) return 0;
			}
		}
		return 1;

	}

	void takeout(int k,int l)
	{

		int i,j,flag=0;

		for(j=l;j<10;j++) chess[k][j]=-1;

		for(j=0;j<10;j++)
		{
			flag=chess[k][j]!=-1;
			if (flag==1) break;

		}
		if(flag==0)
		{

			for(i=k;i<rows-1;i++)
			{
				for(j=0;j<10;j++)
				{
					Swap(chess[i][j],chess[i+1][j]);
				}
			}

			rows--;
		}

	}

	int CheckInput(int i,int j)
	{

		if(i<0||i>=rows||j<0||j>=10)
		{
			printf("该棋子不存在!\n");
			return 0;
		}
		else
		{
			if(chess[i][j]==-1) {
				printf("该棋子不存在!2\n");
				return 0;
			}
			if(chess[i][j]==0) {
				printf("您不能选择白棋!\n");
				return 0;
			}

		}
	return 1;

	}

	double EigenValue() const
	{
		double value = 0;
		int i,j;
		for(i=0;i<rows;i++)
		{
			if (chess[i][0] == 0 && chess[i][1] == 0 && chess[i][2] == -1)
			{
				value+= 0.5;
			}
			else
			{

				for(j=0;j<10;j++)
				{

					switch(chess[i][j])
					{
						case 1:value--;break;
						case 0:value++;break;

					}
				}
			}

		}
		return value;

	}

	void print() const
	{
		int i,j;
		for(i=0;i<rows;i++)
		{
			for(j=0;j<10;j++)
			{
				switch(chess[i][j])
				{
				case 1:printf("●");break;
				case 0:printf("○");break;

				}
			}
			printf("\n");
		}

	}
int chess[5][10];
int rows;

};

//AI下棋的程序
void AIPredict(Chess c,int &AIi,int &AIj)
{

	struct v{
		int i,j;
		double value;

	} value[50];
	int count=0,i,j;

	for(i=0;i<c.rows;i++)
		{
			for(j=0;j<10;j++)
			{
				if(c.chess[i][j]==0)
				{
					Chess predict=c;
					predict.takeout(i,j);
					value[count].i=i;
					value[count].j=j;
					value[count].value=predict.EigenValue();
					count++;
				}
			}
		}

	v maxvalue=value[0];
	for(i=1;i<count;i++)
	{
		if(value[i].value>maxvalue.value)
		{
			maxvalue=value[i];
		}

	}

	AIi=maxvalue.i;
	AIj=maxvalue.j;

}

int main()
{
	system("color F0");
	printf("游戏规则:\n下面有五行石子,白色空心○的石子都是我的,黑色实心●的石子都是你的。\n我们轮流拿走一个自己的石子,并且规定如果一个石子被拿走了,\n它后面的所有石子都要被扔掉。谁先没有拿的了,谁就输了。\n\n");
	Chess c;
	c.print();
	printf("\n\n");

	int i,j,turn;

	printf("你想先手还是后手?1为先手,0为后手:");
	scanf("%d", &turn);

	printf("\n");
	while(!c.isWin(turn))
	{

		if (turn==1) {

			do{

				printf("请输入i j表示你要下的位置:(从0开始)");
				scanf("%d %d",&i,&j);

			}while(!c.CheckInput(i,j));

				c.takeout(i,j);
				printf("\n\n");
				c.print();

		}
		else
		{
			AIPredict(c,i,j);
			printf("电脑下的位置是:<%d,%d>\n",i,j);
			c.takeout(i,j);
			printf("\n\n");
			c.print();

		}

		turn = !turn;
	}

	if(turn)
	{
		printf("你赢了!\n");
	}
	else
	{
		printf("你输了!\n");
	}

	system("pause");
	return 0;

}

  

时间: 2024-10-15 08:24:29

算法学习---一个博弈问题的相关文章

[算法学习]给定一个整型数组,找出两个整数为指定整数的和(3)

问题描述: 设计一个类,包含如下两个成员函数: Save(int input) 插入一个整数到一个整数集合里. Test(int target) 检查是否存在两个数和为输入值.如果存在着两个数,则返回true,否则返回false 允许整数集合中存在相同值的元素 分析: 与[算法学习]给定一个整型数组,找出两个整数为指定整数的和(2)不同,这里需要算出的是存不存在这两个数,可以在上一篇的基础上修改一下数据结构,HashMap其中key是数值,value是数值个数,然后需要作两步判断,map中存在数

一个谷歌程序员的算法学习之路

原文出处: Lucida (@peng_gong) 关于 严格来说,本文题目应该是我的数据结构和算法学习之路,但这个写法实在太绕口——况且CS中的算法往往暗指数据结构和算法(例如算法导论指的实际上是数据结构和算法导论),所以我认为本文题目是合理的. 这篇文章讲了什么? 我这些年学习数据结构和算法的总结. 一些不错的算法书籍和教程. 算法的重要性. 初学 第一次接触数据结构是在大二下学期的数据结构课程.然而这门课程并没有让我入门——当时自己正忙于倒卖各种MP3和耳机,对于这些课程根本就不屑一顾——

算法学习三阶段

?? 第一阶段:练经典经常使用算法,以下的每一个算法给我打上十到二十遍,同一时候自己精简代码, 由于太经常使用,所以要练到写时不用想,10-15分钟内打完,甚至关掉显示器都能够把程序打 出来. 1.最短路(Floyd.Dijstra,BellmanFord) 2.最小生成树(先写个prim,kruscal 要用并查集,不好写) 3.大数(高精度)加减乘除 4.二分查找. (代码可在五行以内) 5.叉乘.判线段相交.然后写个凸包. 6.BFS.DFS,同一时候熟练hash 表(要熟,要灵活,代码要

算法学习 - 表达树的建立(后缀表达式法),树的先序遍历,中序遍历,后序遍历

表达树就是根据后缀表达式来建立一个二叉树. 这个二叉树的每个叶子节点就是数,真祖先都是操作符. 通过栈来建立的,所以这里也会有很多栈的操作. 树的先序遍历,中序遍历,后序遍历的概念我就不讲了,不会的自行百度,不然也看不懂我的代码. 下面是代码: // // main.cpp // expressionTree // // Created by Alps on 14-7-29. // Copyright (c) 2014年 chen. All rights reserved. // #includ

我的算法学习之路

关于 严格来说,本文题目应该是我的数据结构和算法学习之路,但这个写法实在太绕口--况且CS中的算法往往暗指数据结构和算法(例如算法导论指的实际上是数据结构和算法导论),所以我认为本文题目是合理的. 这篇文章讲了什么? 我这些年学习数据结构和算法的总结. 一些不错的算法书籍和教程. 算法的重要性. 初学 第一次接触数据结构是在大二下学期的数据结构课程.然而这门课程并没有让我入门--当时自己正忙于倒卖各种MP3和耳机,对于这些课程根本就不屑一顾--反正最后考试划个重点也能过,于是这门整个计算机专业本

周总结(2017.2.16):第一周算法学习。

周总结:算法学习总结之DFS和BFS 一:DFS算法 目的:达到被搜索结构的叶节点. 定义:假定给定图G的初态是所有的定点都没有访问过,在G中任选一定点V为初始出发点,首先访问出发点并标记,然后依次从V出发搜索V的每个相邻点W,若W未曾出现过,则对W进行深度优先遍历(DFS),知道所有和V有路径相通的定点被访问. 如果从V0开始寻找一条长度为4的路径的话: 思路步骤: 先寻找V0的所有相邻点:dis{v1,v2,v3},V1没有访问过,所以对V1进行深度遍历并将V1标记为访问过,此时路径长度为1

算法学习 - 01背包问题(动态规划C++)

动态规划 01背包 问题描述 求解思路 代码实现 放入哪些物品 代码 动态规划 我在上一篇博客里已经讲了一点动态规划了,传送门:算法学习 - 动态规划(DP问题)(C++) 这里说一下,遇到动态规划应该如何去想,才能找到解决办法. 最主要的其实是要找状态转移的方程,例如上一篇博客里面,找的就是当前两条生产线的第i个station的最短时间和上一时刻的时间关系. minTime(station[1][i]) = minTime(station[1][i-1] + time[i], station[

LCA 算法学习 (最近公共祖先)poj 1330

poj1330 在求解最近公共祖先为问题上,用到的是Tarjan的思想,从根结点开始形成一棵深搜树,处理技巧就是在回溯到结点u的时候,u的子树已经遍历,这时候才把u结点放入合并集合中,这样u结点和所有u的子树中的结点的最近公共祖先就是u了,u和还未遍历的所有u的兄弟结点及子树中的最近公共祖先就是u的父亲结点.这样我们在对树深度遍历的时候就很自然的将树中的结点分成若干的集合,两个集合中的所属不同集合的任意一对顶点的公共祖先都是相同的,也就是说这两个集合的最近公共祖先只有一个.时间复杂度为O(n+q

算法学习 - 链表的游标实现~ C++

链表的游标实现,就是用另外一种方法来访问链表,模拟游标. 在我学习的理解中,就是创建一个节点数组,模拟内存的排列,然后从其中来申请内存和释放内存.但是实际的内存没有被释放~ 下面直接贴代码了: // // main.cpp // CursorList // // Created by Alps on 14-7-27. // Copyright (c) 2014年 chen. All rights reserved. // #include <iostream> #define CursorSp