存档

‘C/C++’ 分类的存档

海盗分金问题分析与解决

2010年8月2日 1 条评论

海盗分金问题分析与解决

——Nim取子问题变形后的解决方案

全文下载链接:海盗分金问题分析与解决.pdf

Author: Zealot Ke(kejie[at]kuxun.cn)
Date: 2010/7/30

背景

强哥邮件发出一个题目

  1. 有5个海盗,特别的残忍,特别的聪明
  2. 他们抢了100个金币,要进行分配,每个人都想分配足够多的金币,他们寸步不让.分配顺序是:5号海盗->4号海盗…1号海盗,即是:先由5号海盗决定如何分配,若分配不均,5号被扔下海,4号海盗再决定如何分配,依次类推.
  3. 如果没有二分之一或以上的海盗同意,分配人将被其他人扔下大海.
  4. 假如你是5号海盗,你会如何分配金币.确保自己不会被扔下大海,而且让其他4个海盗心服口服.

定义问题

5号海盗必须按照上述规则,找出最优分配方案,否则将被其他人扔下大海。

当前状态

正确分配方案还没出来,必须尽快找出最优解。

分析

典型的nim取子问题的变形,采用倒推方式即可找到最优解。

制定解决方案

倒推方法如下所示

  1. 如果只有一个海盗,如何分配?
  2. 如果有两个海盗,如何分配?
  3. 如果有n个海盗,每个海盗至少需要几个人同意?找出n-1个海盗情况下的分配方案中,需要收买多少海盗?最低需要多少金币?

实现解决方案

倒推方法实现如下表所示,每一行表示一种人数情况下的分配方案,也即,最后一行给出5号海盗的分配方案。

分配方案倒推分析表

分配方案倒推分析表

注意:从表中可以看出聪明是大前提,当然残忍也是。nim取子游戏向来是君子游戏,没有这个前提的话,后面的海盗不按套路出牌就不可能有最优解。

标准化解决方案

从上表不难看出规律,也即,自己拿尽可能多的金币,同时保证后面的人依次分配0、1交错的金币数。规范化命题如下
*命题: 假设对于n个聪明、残忍的海盗(n≥1),抢了Gold个金币,要进行分配,每个人都想分配足够多的金币,他们寸步不让。分配顺序是:n号海盗->n-1号海盗…1号海盗,即是:先由n号海盗决定如何分配,若分配不均,n号被扔下海,n-1号海盗再决定如何分配,依次类推。在该规则下,n号海盗最佳分配方案应当如此(为简单,避免使用地板函数,按n为奇偶数分类讨论):
不妨假设ones为得到1个金币海盗数,zeros为得到0个金币的海盗数。

  1. 当n为奇数时,ones=zeros=(n-1)/2,分配方案为Gold-ones,0,1…0,1
  2. 当n为偶数时,ones=( n-2)/2,zeros=n/2,分配方案为Gold-ones,0,1…1,0

该命题从上述分析中的规律中得出,不是定理,更不是公理,作为标准化解决方案需要严格证明。

证明:
注释:使用跳跃数学归纳法证明,跳跃的step为2。
先证明n为奇数时命题是否成立。
当n=1时,显然该命题成立
假设当n=m(m>1)时命题成立,则分配方案为
Ones(m) = Zeros(m) = (m-1)/2
Proposal(m) = Gold-Ones(m),0,1,…,0,1
= Gold-(m-1)/2, 0, 1, …,0,1
当n=m+1时,显然命题成立,理由如下
Ones(m+1) = (m+1-2)/2=(m-1)/2,Zeros(m+1) = (m+1)/2
注意:a) m+1为偶数,计算ones和zeros需要换个公式b) 对比n=m情形,仅仅是多了个0。
Proposal(m+1) = Gold-Ones(m+1),0,1,…,0,1,0
= Gold-(m-1)/2, 0, 1, …,0,1,0
对比Ones(m),Ones(m+1),Zeros(m),Zeros(m+1)可以看出仅仅是在Proposal(m)序列后面添个0。这会导致Proposal(m+1)与Proposal(m)两个序列中,0、1交错序列刚好错位,对应金币分配方案中则为:m+1号海盗分配方案就是把Proposal(m)中没有得到金币的人每人给1个金币实现自己金币最大化,同时保证同意的人数大于等于2。
综上所述,当n为奇数时,该命题成立。
再证明n为偶数时命题是否成立。
当n=2时,命题成立(大于的最小偶数只能是2)
归纳方法同1。
综合1、2分析,该命题成立。
证毕。

该标准化解决方案用程序实现如下
说明:该程序考虑了金币数量远小于海盗数量时,最先出来分配的部分海盗都得喂鲨鱼去了。

$ cat nim.c
#include 

const int g_first = 2;
const int g_zero = 0;
const int g_one = 1;

int print_proposal(int *gold, int *pirate, int *ones, int *zeros, int *status) {
        int             ret = 1;
        int             number = 0;
        int             remain = *gold - *ones;
        if (*pirate <= 0) {
                printf(".\n");
                return 0;
        }
        if (remain <= 0) {
                number = 0;     /* not enough gold, killed by others, regenerate proposal */
                ret = 1;        /* conitnue */

                /* regenerate proposal */
                if (*pirate % 2 == 0) {
                        *ones = (*pirate - 2) / 2;
                        *zeros = *pirate / 2;
                } else {
                        *ones = *zeros = (*pirate - 1) / 2;
                }
        } else if (*status == g_one) {
                number = 1;
                *ones--;
                *status = g_zero;
        } else if (*status == g_zero) {
                number = 0;
                *zeros--;
                *status = g_one;
        } else {
                number = remain;
                *status = g_zero;
        }
        if (*status != g_first && *ones <= 0 && *zeros <= 0) {
                ret = 0;
        }

        printf("%d ", number);
        *pirate = *pirate - 1;

        return ret;
}

int main()
{
        int             gold;
        int             pirate;
        int             ones;
        int             zeros;
        int             temp;
        int             status;         /* process first, zero or one */
        printf("Please Enter gold and pirate number:\n");
        while (scanf("%d %d", &gold, &pirate) == 2) {
                if (pirate < 1 || gold < 1)
                        break;

                if (pirate % 2 == 0) {
                        ones = (pirate - 2) / 2;
                        zeros = pirate / 2;
                } else {
                        ones = zeros = (pirate - 1) / 2;
                }

                status = g_first;
                printf("Proposal(%d, %d) is: ", gold, pirate);
                temp = pirate;
                while (print_proposal(&gold, &temp, &ones, &zeros, &status))
                        ;
                if (pirate == 1)
                        printf(".\n");

                printf("\n");
                printf("Please Enter gold and pirate number:\n");
        }
        return 0;
}

程序运行结果如下所示

$ gcc nim.c -o nim
$ ./nim
Please Enter gold and pirate number:
100 1
Proposal(100, 1) is: 100 .

Please Enter gold and pirate number:
100 2
Proposal(100, 2) is: 100 0 .

Please Enter gold and pirate number:
100 3
Proposal(100, 3) is: 99 0 1 .

Please Enter gold and pirate number:
100 4
Proposal(100, 4) is: 99 0 1 0 .

Please Enter gold and pirate number:
100 5
Proposal(100, 5) is: 98 0 1 0 1 .

Please Enter gold and pirate number:
0

决定下一步

本问题到此为止,暂时没有下一步动作。

总结

这个结论告诉我们

  1. 最先掌握分配权的人,看似最危险,胆小的人可能会担心所有人都不支持你,你就只能下海去喂鲨鱼了。其实,他优先掌握了主动权,天下武功,唯快不破,掌握市场先机很重要。
  2. 越快越好,但不能快过了头。100个金币,1000个海盗的话,最先出来分配的人必然死掉。同样的,20年前搞酒店搜索,率先占据主动权,快得一塌糊涂那也没戏。

完。

单件模式:Singleton那些事

2010年3月1日 3 条评论

单件模式确保一个类只有一个实例,并提供一个全局的访问点。 –《Head First 设计模式》

有些时候,我们恰好需要这样一种设计,保证我们实例化的类在进程地址空间内只有一个实例,有了Singleton,一切似乎都简单明了,其实暗藏杀机。

1. C++实现Singleton模式简单示例

简单的C++实现示例代码

class Singleton {
    public:
        static Singleton* GetInstance(void) {
            if (Singleton::m_instance == NULL) {
                Singleton::m_instance = new Singleton();
            }
            return Singleton::m_instance;
        }
    private:
        static Singleton* m_instance;
        Singleton() {}
};

使用gtest测试,简单地测试一下每次调用GetInstance是否返回同一地址

#include 
#include 

#include 
using namespace std;
#define NEW_COUNT 4096

class Singleton {
    public:
        static Singleton* GetInstance(void) {
            if (Singleton::m_instance == NULL) {
                Singleton::m_instance = new Singleton();
            }
            return Singleton::m_instance;
        }
    private:
        static Singleton* m_instance;
        Singleton() {}
};

Singleton* Singleton::m_instance = NULL;

TEST(Singleton, sample) {
    Singleton* init_instance = Singleton::GetInstance();
    ASSERT_NE(init_instance, (Singleton*)NULL);

    Singleton* p = NULL;
    ASSERT_EQ(p, (Singleton*)NULL);

    for (int i = 0; i < NEW_COUNT; ++i) {
        p = Singleton::GetInstance();
        ASSERT_EQ(init_instance, p);
    }
}

int main(int argc, char **argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

测试结果显示没有问题:
$ ./singleton
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from Singleton
[ RUN ] Singleton.sample
[ OK ] Singleton.sample (1 ms)
[----------] 1 test from Singleton (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (2 ms total)
[ PASSED ] 1 test.

当然,这种测试没有太大意义。给出测试结果,主要用于之后的对比。

2. python程序中调用Singleton模式的C++代码
初次接触Singleton后发现,使用Singleton模式的C++代码融合到python中有意想不到的惊喜:如果有某个类初始化需要很长时间,而之后调用的速度都很快的话,使用Singleton模式,然后在python中多次调用时,不会出现重复的初始化操作。
简单地尝试了一下

首先在singleton.cpp中添加了一行输出,标识一次初始化操作:

Singleton* Singleton::GetInstance(void) {
    if (Singleton::m_instance == NULL) {
        Singleton::m_instance = new Singleton();
        std::cout << "initialize here." << std::endl;
    }
    return Singleton::m_instance;
}

封装的代码如下
$ cat _pysingleton.cpp -n

#include 

#include "singleton.h"

using namespace std;

PyObject * GetInstance(PyObject *self, PyObject *args) {
    Singleton::GetInstance();
    return Py_BuildValue("s", NULL);
}

static PyMethodDef _pysingletonMethods[] = {
    {"GetInstance", GetInstance, METH_VARARGS, "get instance."},
    {NULL, NULL},
};

PyMODINIT_FUNC init_pysingleton(void) {
    PyObject * m;
    m = Py_InitModule("_pysingleton", _pysingletonMethods);
}

编译出来共享库_pysingleton.so
$ cat Makefile -n

CC                =       g++
FLAGS           =       -ggdb -O2 -Wall
INC               =       -I/usr/include/python2.5
LIB               =       -lgtest -lpython2.5
LLIB              =

%.o:%.cpp
        $(CC) $(FLAGS) $(INC) -c $<

_pysingleton.so: singleton.o _pysingleton.o
        $(CC) -shared $(CFLAGS) $(LLIB) $(INCLUDE) $^ -o $@

clean:
        rm -f *.so
        rm -f *.o
        rm -f *.pyc
        rm -f singleton

写个简单的python脚本测试一下在单一进程空间里,是否真的只调用一次
$ cat pysingleton.py -n

#!/usr/bin/env python
# -*- coding: utf-8 -*-

''' sample for pysingleton
'''

import os
import sys
import _pysingleton

class PySingleton:
    def __init__(self):
        _pysingleton.GetInstance()

def main():
    ''' main function
    '''
    for i in xrange(3):
        PySingleton()
    print 'Done'

if __name__ == '__main__':
    main()

结果是
$ ./pysingleton.py
initialize here.
Done

3. 多线程中使用Singleton模式
多线程里就会遇到一个double-check问题 (double-check-lock problem, DCLP)
首先,静态变量m_instance作用域是全局的,处于临界区,在多线程需要加锁。这一点很好理解。
但是,简单的在if外面加锁势必会影响性能,毕竟只有第一次调用才会生效,每次都有一次锁操作太费时,也没必要。
于是,就会有double-check方法了,伪代码为
if (Singleton::m_instance == NULL) // first check
lock
if (Singleton::m_instance == NULL) // second check

从这里可以看出,double-check有效地避免了重复锁操作问题。
然而,问题还没有结束。采用double-check的话,又会引入另一个问题。在这里
Singleton::m_instance = new Singleton();
编译器可能会做个优化,首先将一个有效的内存地址返回给m_instance,然后再调用构造函数初始化。这样很有下一次double-check中首次检查通过,但是得到的内存地址指向的是一个未经初始化的类。后果怎么样,只有天知道了。
更多细节,参考这里:http://www.ibm.com/developerworks/library/j-dcl.html

a. 最简单的修改方法是
Singleton* temp = new Singleton();
m_instance = temp;

但是一个自作聪明地编译器还是会把结果优化成难以想象的样子
b. 另一个方法就是使用volatile关键字,用以声明不受编译器优化的影响
比如 Singleton* volatile temp = new Singleton();
但是远远不够,temp是volatile,但是*temp呢,temp->foo,temp->bar...这些成员呢?所以,要改的话,得把全世界都改成volatile,除了那个lock-_-|||

4. 以上都只考虑单核,再考虑一下多核机器,问题将会更加复杂

总结:珍爱生命,远离Singleton。尤其是不要把Singleton放进通用库中,还有C++中那个叫template的高级玩意儿,都搞在一起老天都很难保证会发生些什么。


1. 这里也在讨论单例模式Why Singletons Are Controversial
2. 代码托管到Google Code,欢迎围观、讨论、批评、挑bug:点此进入(http://code.google.com/p/zldemo/source/browse/#svn/trunk/blog/singleton_everything)
(墙内用户请不要使用https访问)

C++拷贝构造函数没有执行的问题

2009年4月29日 没有评论

问题是在这http://lilacbbs.com/forum/d.php?bid=66&id=62395,想了半天想不明白

摘要就是 Fred z = Fred(3);时没有执行拷贝构造函数,查了标准手册才发现原来如此:会不会调用拷贝构造函数由具体编译器实现决定:

Standard C++98 12.8

15 Whenever a temporary class object is copied using a copy constructor, and this object and the copy have the

same cvunqualified

type, an implementation is permitted to treat the original and the copy as two different

ways of referring to the same object and not perform a copy at all, even if the class copy constructor or

destructor have side effects. For a function with a class return type, if the expression in the return statement

is the name of a local object, and the cvunqualified

type of the local object is the same as the function

return type, an implementation is permitted to omit creating the temporary object to hold the function return

value, even if the class copy constructor or destructor has side effects. In these cases, the object is

destroyed at the later of times when the original and the copy would have been destroyed without the optimization.

111) [Example:

class Thing {

public:

Thing();

~Thing();

Thing(const Thing&);

Thing operator=(const Thing&);

void fun();

};

Thing f() {

Thing t;

return t;

}

Thing t2 = f();

Here t does not need to be copied when returning from f. The return value of f may be constructed

directly into the object t2. ]

C++解析json的parser,tinyjson问题的解决办法

2009年4月26日 没有评论

前不久需要解析一个json的接口,上json官网(http://www.json.org/json-zh.html)上找找C++parser。结果发现长长的list,支持各种语言的各种版本。上第一个官网看看(http://blog.beef.de/projects/tinyjson/),对tiny**的东西还是比较感兴趣的,比如tinyxml。。。

果然够tiny,下载下来一看发现只有一个.hpp文件就行了。接下来就开始ft了,按照官网上的sample调用了一下,编译都不过:

../include/tinyjson/tinyjson.hpp: In function ‘typename json::grammar<typename Iterator::value_type>::variant json::parse(const Iterator&, const Iterator&)’:

../include/tinyjson/tinyjson.hpp:549: error: expected `;’ before ‘st’

../include/tinyjson/tinyjson.hpp:550: error: ‘st’ was not declared in this scope

../include/tinyjson/tinyjson.hpp: In function ‘typename json::grammar<typename Iterator::value_type>::variant json::parse(const Iterator&, const Iterator&) [with Iterator = __gnu_cxx::__normal_iterator<char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >]‘:

test.cpp:45: instantiated from here

../include/tinyjson/tinyjson.hpp:549: error: dependent-name ‘json::grammar<typename Iterator::value_type>::stack’ is parsed as a non-type, but instantiation yields a type

../include/tinyjson/tinyjson.hpp:549: note: say ‘typename json::grammar<typename Iterator::value_type>::stack’ if a type is meant

……

google了一下,相关资料真少,难道用c++解析json很发指么,最后硬着头皮在这个日文的bloghttp://d.hatena.ne.jp/S_Nakayama/20081207/1228592806)上找到了答案。修改记录如下:

549c549

< json::grammar< typename Iterator::value_type >::stack st;

> typename json::grammar< typename Iterator::value_type >::stack st;

565c565

< return json::grammar< typename Iterator::value_type >::variant(new boost::any());

> return typename json::grammar< typename Iterator::value_type >::variant(new boost::any());

相应的加上typename就行了。

最后,还想说一句,tinyjson官网的sample里遍历的方法也没编译过,最后改了一下遍历函数的参数,函数原型是void Traverse(json::grammar<char>::variant const v, const string& name, int level)。name表示上级节点名称,level表示当前内容的深度。虽然看着还是不爽,但好歹能工作了。