0-1规划在编程问题中的应用(UnityC#脚本/折返约瑟夫/OpenGL机器人摆臂循环)

文章介绍了0-1规划的概念,通过Unity游戏脚本中的地面检测示例和数据结构作业中解决折返约瑟夫问题的应用,展示了0-1逻辑在编程中的实用价值。作者通过C#和OpenGL的实例进一步阐述了这种逻辑在游戏开发中,如机器人走路摆臂循环中的应用,强调了0-1变量在处理互斥条件时的有效性。
摘要由CSDN通过智能技术生成

一、0-1规划的定义

百度百科的解释:0-1规划是决策变量仅取值0或1的一类特殊的整数规划。在处理经济管理中某些规划问题时,若决策变量采用 0-1变量即逻辑变量,可把本来需要分别各种情况加以讨论的问题统一在一个问题中讨论。

如上面所说,0-1规划是采样0和1作为逻辑变量处理问题的方法,并且经常是用在处理经济管理中。那么为题就来了,我是怎么发现这个东西的呢?学校里压根没教过这个算法。

这还得归功于B站两位UP主M_StudioCellStudio,当然并没有直接关系,而是间接关系。

二、发现的缘由

在去年,也就是我大二上学期的时候,在B站看了M_Studio老师的2D横板闯关游戏案例教学和CellStudio老师的FPS游戏学习案例,因为当时想练一练C#编写游戏脚本,于是就学了CellStudio老师的FPS案例和M_Studio老师的小狐狸的2D横板闯关游戏案例(当然这个小狐狸系列的视频已经被下架删除了)。

当然重点都不是这些,而是在脚本中有两个防止角色二段跳的代码吸引了我。如下图

我们就算不知道Unity中的一些函数,也可以脑补出实现跳跃的方法。只要我们学过条件判断if,可以很自然的想到,if (按下空格) {角色起跳}; 这么一个逻辑伪码。但是它太简单了,以至于如果玩家一直按空格角色会一直跳,这显然不符合市面上大部分的游戏规则。

于是我们看上图这段代码,在这个横板2D闯关游戏案例里,他用了一个coll.isTouchingLayers(ground)来检测角色是否在地面上。在FPS案例里,同样有characterController.isGrounded来检测角色是否在地面上。

这样我们就可以想到,角色起跳时要同时满足两个条件,一个是玩家按下跳跃键,二是角色在地面上,至于这两个函数是怎么检测角色在地面上的咱们不说。我想说的是,角色是否在地面上这个是否,它让你想到了什么?Bool型对吗?0/1True or False。这就是我想说的,完全可以当成如果角色在地面上,那么这个isGrounded变量就是1,不在地面上就是0,这是一个很简单的逻辑,却能解决很多问题。

三、实际应用

1.折返约瑟夫问题

我并不是发现了这个方法之后马上应用的,而是在大二上学期的一次数据结构作业中,一次偶然的机会发现了这个方法的好用之处。那次作业我也发了文章,详见:[C语言、C++]数据结构作业:用双向链表和0-1变量实现折返约瑟夫问题_反约瑟夫问题


 

对,折返约瑟夫问题,我们回忆一下普通的约瑟夫问题,是约瑟夫环,用循环链表就可以解决。然后那次作业留的是修改后的折返约瑟夫问题,也就是摒弃了环的结构,而变成了线性,从一端到另一端,然后再往回遍历,循环往复,那我们自然想到双向链表,然后让指针在两头来回遍历即可,这里有一个问题,如何让程序知道,你的指针此时是应该向右遍历还是向左遍历呢?这是一个嵌套的过程,有人可能会说用递归,我没试过但感觉可以,但是很难想很费脑子。于是我们之上介绍的isGrounded变量就起作用了,只不过这里我换成了isBack,没有区别,反正都是0/1。

#include <iostream>
using namespace std;
typedef struct LinkNode {
	int data;
	LinkNode* next;
	LinkNode* pre;
}*LinkList;
void InitLinkList(LinkList& L) {
	L = NULL;
}
void CreatRear(LinkList& L,int n) {
	L = NULL;
	if (n <= 0)return;
	else {
		L = new LinkNode;
		cin >> L->data;
		L->pre = NULL; L->next = NULL;
		if (n > 1) {
			LinkNode* r = L;
			for (int i = 1; i < n; i++) {
				LinkNode* s = new LinkNode;
				cin >> s->data;
				r->next = s;
				s->pre = r;
				r = r->next;
			}
			r->next = NULL;
		}
	}
}
void Returning_Joseph(int n, int m) {
	LinkList L; InitLinkList(L);
	CreatRear(L, n);
	LinkNode* p = L; int isBack = 0;
	for (int i = 0; i < n - 1; i++) {
		int j = 0;
		while (j < m - 1) {
			if (isBack == 0) {
				while (p->next != NULL && j < m - 1) {
					p = p->next; j++;
				}
				if (p->next == NULL)isBack = 1;
			}
			if (isBack == 1) {
				while (p->pre != NULL && j < m - 1) {
					p = p->pre; j++;
				}
				if (p->pre == NULL)isBack = 0;
			}
		}
		cout << p->data << " ";
		LinkNode* r = p;
		if (p->next == NULL || p->pre == NULL) {
			if (p->next == NULL) {
				p = p->pre;
				delete(p->next);
			}
			else {
				p = p->next;
				delete(p->pre);
			}
		}
		else {
			p->pre->next = p->next;
			p->next->pre = p->pre;
			if (isBack == 0)p = p->next;
			else p = p->pre;
			r->next = NULL; r->pre = NULL;
			delete(r);
		}
	}
	cout << endl;
	cout << p->data << "是胜者"; 
}
int main(){
	Returning_Joseph(5, 3);
}

0/1的逻辑也很简单,只要发现下一个后继/前驱指针为NULL,改变isBack的值,前面写两个分支条件判断,isBack是0,向有遍历指针,isBack是1,向左遍历指针,问题就解决了。

2.OpenGL机器人走路摆臂循环

这是最近计算机图形学OpenGL的一次作业,让写一个机器人的程序,其中有一个要求,就是当按下‘w’键时,机器人会摆臂迈腿,像人一样,于是我就又想到了这个方法。

void keyboard(unsigned char key, int x, int y)
{
	switch (key) {
	case'f':
		feet1 = (feet1 + 5) % 90;//180
		feet2 = (feet2 + 5) % 90;
		glutPostRedisplay();
		break;
	case 's':
		arm2 = (arm2 + 5) % 360;
		glutPostRedisplay();
		break;
	case 'e':
		hand3 = (hand3 - 5) % 180;//180
		glutPostRedisplay();
		break;
	case 'h':
		hand2 = (hand2 - 5) % 180;
		hand4 = (hand4 - 5) % 180;
		glutPostRedisplay();
		break;
	case 'w':
		if (isnext == 0) {
			arm2 = (arm2 + 5) % 360;
			arm1 = (arm1 - 5) % 360;
			leg1 = (leg1 - 5) % 180;
			leg2 = (leg2 + 5) % 180;
			if (leg2 == 40) {
				isnext = 1;
			}
		}
		else {
			arm2 = (arm2 - 5) % 360;
			arm1 = (arm1 + 5) % 360;
			leg1 = (leg1 + 5) % 180;
			leg2 = (leg2 - 5) % 180;
			if (leg2 == -40) {
				isnext = 0;
			}
		}
		glutPostRedisplay();
		break;

	case 27:
		//exit(0);
		break;

	default:
		break;
	}
}

如上是键盘交互的代码,咱们不看那么多直接看有关w的那段,如下面:

case 'w':
		if (isnext == 0) {
			arm2 = (arm2 + 5) % 360;
			arm1 = (arm1 - 5) % 360;
			leg1 = (leg1 - 5) % 180;
			leg2 = (leg2 + 5) % 180;
			if (leg2 == 40) {
				isnext = 1;
			}
		}
		else {
			arm2 = (arm2 - 5) % 360;
			arm1 = (arm1 + 5) % 360;
			leg1 = (leg1 + 5) % 180;
			leg2 = (leg2 - 5) % 180;
			if (leg2 == -40) {
				isnext = 0;
			}
		}

通过上面的代码,大家应该知道了大致的思路,isnext就是我设的一个0/1的逻辑变量,当它为0时,左胳膊和右腿往前旋转,右胳膊和左腿往后旋转,当isnext为1的时候,再反过来就好了。改变isnext的条件也很简单,就是腿旋转达到一定角度的时候,比如左腿向前旋转到40°的时候更改,这里其实我简化了,因为胳膊和腿的旋转步长是相同的,所以判断胳膊和腿的任何一个旋转到一定角度然后更改isnext变量都可以,否则会更加复杂,但是我说的这个0/1变量的思想已经包含在其中了。

四、视频讲解

0-1规划在编程问题中的应用(UnityC#脚本/折返约瑟夫/OpenGL机器人摆臂循环)_哔哩哔哩_bilibiliicon-default.png?t=N3I4https://www.bilibili.com/video/BV1UM4y1b7pK/?spm_id_from=333.999.0.0

五、总结

总之,虽然这不是一个什么算法,但是我觉得这是解决一套问题的一种非常好用的方法,简单的说,只要你解决问题的过程中需要分情况讨论,且两种情况是相互约束的这么一种逻辑,就比如地面检测,你不可能同时在地上和空中两种状态,折返约瑟夫你不可能同时向左和右遍历,机器人走路也是,不可能同时迈两只腿或者抬起两只胳膊,这些都是一种逻辑,也就都可以用这种方法解决,虽然很简单,但是能解决很多问题,在最开始的时候我还不知道这个叫做0-1规划,我自己乱起的叫0-1变量,但是对我来说还是很有意义的,学习中的发现的快乐可能就在于此吧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值