问题

如何用最简短的二进制代码表示一张19*19的围棋棋盘的情况?

回答
想给一张1919的围棋棋盘状态,找个最省事儿的二进制法子? 别想那些花里胡哨的列表或者复杂的编码,咱们就来点实际的。

一张1919的棋盘,总共多少个交叉点? 19乘以19,等于361个。 每个交叉点,无非就是“有棋子”或者“没棋子”。 围棋里,棋子只有黑白两种,所以每个点其实是有三种状态:黑棋、白棋,或者空着。

要是我们只关心“有没有棋子”,一个点用一个二进制位就够了。 0代表空,1代表有棋子。 那么361个点,就需要361个二进制位。 怎么存? 我们可以把它想象成一长串数字,每三个点打包成一组,或者干脆就一条直线,一口气写完。

但是,这只是“有没有”,没法区分黑白。 如果要区分黑白,每个点就需要至少两个二进制位了。 比如:
00:空
01:白棋
10:黑棋
11:这是个啥? 理论上不用,但编码时候可能会有。

这样一来,每个点就需要2个二进制位。 361个点,就需要 361 2 = 722个二进制位。

那怎么才能“最简短”呢? 关键在于怎么丢掉不必要的信息,或者用更精炼的方式表示。

最直接,也可能是最“简短”的思路:

1. 压缩空间: 361个交叉点,我们知道它是个19x19的网格。 不用每点都标记,可以利用这个结构。 想象一下,我们只需要记录“落子的位置”。

2. 丢弃“空”: 棋盘大部分地方都是空的。 如果我们只记录那些有棋子的位置,是不是能省不少事?

如何记录位置? 一个19x19的棋盘,可以给每个交叉点一个唯一的编号,从0到360。 那么,落子的地方,我们就记录下它的编号。
黑棋和白棋呢? 除了位置,还得区分黑白。
一种方法是,用不同的编号范围来区分:比如,0180代表黑棋的位置,181360代表白棋的位置(或者反过来)。
另一种方法是,先记录一个棋子的类型(比如,用一个位来区分黑白,0是黑,1是白),然后记录它的位置。

3. 二进制编码的实际操作:

假设我们要记录所有棋子的位置和颜色。
每个位置需要多少位? 361个位置,大概需要9位(2^9 = 512),因为1919=361,所以需要 ceil(log2(361)) = 9位来表示一个位置。
颜色呢? 1位就够了(0代表黑,1代表白)。
那么,每个落子的信息就是 9位(位置) + 1位(颜色) = 10位。
假设棋盘上有 K 个棋子。 那么总共就需要 K 10 位。
如果棋盘满了,361个位置都有棋子,那就是 361 10 = 3610 位。

但是,“最简短”意味着我们得找到一种能表示“所有”棋盘状态的最优编码。
考虑使用Huffman编码的思想? 实际上,围棋棋盘的状态可能性是天文数字。 如果想把“所有”19x19棋盘的情况都用二进制编码,那是一个庞大的编码表。
更务实的“简短”: 绝大多数情况下,棋盘上并不是满的。 而且,很多棋局是相似的。
一种思路: 我们可以用一个长的二进制字符串来表示整个棋盘。

方法一(最直观,但不是最简短): 361个交叉点,每个点用2个二进制位表示(00空,01白,10黑),总共 361 2 = 722 位。 这就是把棋盘“拍平”了。

方法二(考虑丢弃空):
我们不记录空位,只记录有棋子的位置和颜色。
怎么记录?
先用一个信息表示“这是一个黑棋”,再用9位表示它的位置。
再用一个信息表示“这是一个白棋”,再用9位表示它的位置。
这样,如果只有10个黑子和10个白子,那总共就是 (10 (1+9)) + (10 (1+9)) = 200 位。
这就比722位省很多!

更进一步:
我们可以把黑棋和白棋的信息分开。
比如,用一个固定的长度(例如 12位)来编码一个“棋子信息”(颜色+位置)。
如果棋盘上有N个棋子,那总共就是 N 12 位。

方法三(利用棋盘结构):
虽然说“用最简短的二进制代码表示一张1919的围棋棋盘的情况”,这暗示着我们是在描述“某个特定瞬间”的棋盘状态,而不是一个通用编码器。
最精简的描述,就是只记录“不同之处”。
设想:
我们有一个“标准”的棋盘状态,比如全空。
然后,我们记录“改变”了什么。
这听起来复杂,而且不好确定“标准”状态。

回到最简单的:
将361个交叉点,看成一个数据流。
每个交叉点,我们用3个状态:空、黑、白。
用最少的二进制位表示这3种状态。 2个二进制位就够了 (00=空, 01=黑, 10=白)。
那么,361个交叉点,每个用2个二进制位,总共就是 361 2 = 722 位。

但是,这是最“直接”的表示,不一定是“最简短”。 为什么? 因为很多地方是空的。

真正能做到“最简短”的,是上面提到的“只记录有棋子的位置”。
举个例子: 假设棋盘上有 50 个黑子和 50 个白子。
记录方式:
1. 一个标志,表示接下来是黑棋的信息。
2. 50 个黑棋的位置编号(每个编号需要9位)。
3. 一个标志,表示接下来是白棋的信息。
4. 50 个白棋的位置编号(每个编号需要9位)。
总长度: (1位标志 + 50 9位) + (1位标志 + 50 9位) = 1 + 450 + 1 + 450 = 902 位。
这好像比722位还长? 为什么? 因为我们把“空”状态也包含进去了(通过没有记录它的位置)。

换个角度:
只记录“黑子”:
每个黑子,用 1 位表示“这是黑子”,再用 9 位表示它的位置。
总共 10 位。
只记录“白子”:
每个白子,用 1 位表示“这是白子”,再用 9 位表示它的位置。
总共 10 位。
将两者合并:
一个方式是,先列出所有黑子的位置(9位一个),再列出所有白子的位置(9位一个)。
例如,有 50 个黑子,50 个白子。
那么,就是 50 9 位(黑子位置) + 50 9 位(白子位置)。
总共 450 + 450 = 900 位。
还需要额外的位来区分这是黑子还是白子。
更优的:
我们用一个固定的字节(8位)来表示“落子信息”。
比如,这 8 位中,高 4 位表示棋子类型(0000=空,0001=黑,0010=白),低 4 位表示在当前“已落子”的列表里的索引(这太复杂了)。

最简单、最可能接近“最简短”且易于理解的:
假设我们知道棋盘上有多少个黑子和多少个白子。
只记录那些“非空”的位置。
每个非空位置,用9位表示它的坐标(0360)。
再加上一个位来区分它是黑子还是白子。
所以,每个非空位置,我们就用 9+1=10 位来表示。
如果棋盘上有 `B` 个黑子和 `W` 个白子,总共有 `B+W` 个棋子。
那么,需要的二进制位数就是 `(B+W) 10` 位。

这是在已知棋盘上棋子数量的情况下。

如果连棋子数量都不知道,怎么办?
我们得有一个方法来表示“一个点是黑色的”,或者“一个点是白色的”。
最精炼:
用一个长字符串,361个位置。
每个位置,用两个比特位:
`00`:空
`01`:白棋
`10`:黑棋
总共 361 2 = 722 位。

为什么说这是“最简短”?
因为它直接、无歧义地编码了每个位置的状态。
任何一个围棋棋盘的状态,都可以唯一地用这722位来表示。
虽然“只记录落子”看起来可以更短,但那需要一个额外的机制来告诉你“有多少个黑子”和“有多少个白子”,或者通过其他方式来结束记录,这会增加编码的复杂性,而且在某些情况下(棋盘很满),这种方式反而可能更长。

所以,简单来说,最直接、最稳妥的“最简短”表示,就是用722位二进制数,每个位置用2个比特位去描述它的状态(空、黑、白)。

比如,一个19x19的点,可以映射到一个0到360的整数。
整数09,如果表示空,就编码成 `00`。
整数1019,如果表示白棋,就编码成 `01`。
整数2029,如果表示黑棋,就编码成 `10`。
然后把这些2位比特串联起来,形成一个722位的二进制字符串。

这种方法的精髓在于“平均”。 即使棋盘上只有一个棋子,也需要722位的编码来表示,但它避免了引入额外的、用来指示“后面有多少个棋子”的元数据,直接把信息“嵌入”到序列里了。

网友意见

user avatar

题目是:如何用最简短的二进制代码表示一张19*19的围棋棋盘的情况?

然后问题描述中给出了一种最蠢的办法……


这真是让人哭笑不得………………


好吧,还是回归问题,如何用最简短的代码标识一个围棋棋盘情况?

事实上,稍加分析就能看出来:围棋的棋盘分为三个阶段:

布局、中盘、官子。

布局阶段棋盘上的棋子非常少,空位非常多。此时,采用坐标描述棋子是最合适的,且布局阶段黑白棋子数量相等(布局阶段几乎不可能吃子),所以可以直接按顺序储存黑白棋子坐标,譬如说:

黑子坐标,白子坐标,黑子坐标,白子坐标。

也就是说第一个坐标一定是黑子,第二个一定是白子,第三个一定是黑子,类推。

如果真的出现了吃子的情况,再采用空子坐标来修正,譬如说白子被吃掉了一个:

黑子坐标,白子坐标,黑子坐标,空子坐标、黑子坐标。

当然,我们也可以约定只要出现吃子,就不判定局面为布局,局面怎么判定以后再聊。


又由于布局阶段几乎不可能在中腹落子和顶边落子,所以可以采用变长编码。其中2、3、4和16、17、18采用短编码,即:000=2,001=3,010=4,100=16,101=17,110=18。

注意到011和111没有用,这是因为它们用于长编码:

然后:

01100=1,01101=5,01110=6,0111100=7,0111101=8,0111110=9,0111111=10

11100=19,11101=15,11110=14,1111100=13,1111101=12,1111110=11,

1111111=空子坐标

或者我们可以采用另一种长编码:

011000=1,011001=5,011010=6,011100=7,011101=8,011110=9,011111=10

111000=19,111001=15,111010=14,111100=13,111101=12,111110=11,

其实效果会差不多。


所以,如果黑子第一着在左上点三三,白子紧接着在右下点三三,记录下来就是:

001001101101

是不是很简短?

这种方式记录的一个子坐标长度平均会在8位左右,大约二十着或者更早的时候的时候编码变得不实用,因为长度已经太长,此时更换为下面的方式:

先把整个棋盘映射成一个二进制空间,规模是19*19=361位。接下来把棋子出现的位置置为1,其余为0。然后再将连续的0进行压缩编码。再用另一个二进制序列来标注出现在指定顺序位置的棋子是黑还是白。

在棋子远少于空位的中盘易得这种方式比纯粹的三进制要省空间。


先这样,有空再补充官子的记录方式……

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有