算法基础(1)-枚举

枚举在大家看来可能是一个非常简单的问题,不就是一个遍历算法嘛,有什么好说的,然而,在参加了北京大学MOOC的算法基础后,我直接被震惊了。原来枚举算法还能这么玩!

好吧,不说有的没得没得了,先来看第一个例子——熄灯问题

熄灯问题

这个问题的描述如下:

一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。

在上图中,左边矩阵中用X标记的按钮表示被按下,右边的矩阵表示灯状态的改变。对矩阵中的每盏灯设置一个初始状态。请你按按钮,直至每一盏等都熄灭。与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果。在下图中,第2行第3、5列的按钮都被按下,因此第2行、第4列的灯的状态就不改变。

请你写一个程序,确定需要按下哪些按钮,恰好使得所有的灯都熄灭。

这个问题的难度简直比鸡百鸭百高上数倍!当然,毕竟这是计算机相关内容,所以免不了会有下列图片的问题:

。。。。。。。。

但是,其实他的基本思路是和枚举算法思路是差不多的。枚举的思路在于,先找到一个不变量,在课程中,老师将它定义为局部,不是那个菊。。。

这个局部有什么特征呢?特征是当你确定了这个局部的具体值时,其他所有的数据都只能依照这个局部的数值来变化。

在这个问题中,局部就是第一行,为什么这么说呢?当我们只要确定了第一行按下的状态,那么其余行的按下状态就也已经确定了!如果你仔细思考一下,会发现这是理所当然的,第一行经历了一次按下后,那么势必会出现灯亮或灯不亮的一些场景,那么只有第二行的按下状态才能影响第一行灯的状态了。也就是说,只有第二行按钮才能熄灭第一行亮着的灯!

同理,也只有第三行的按钮才能熄灭第二行的灯,于是乎,一轮不可抗的力量就会向后面的灯按钮涌去,致使你不按也得按!再挣扎也没用了!人类!哈啊哈哈(好中二。。。

就像这样

我们回过头来再分析一下算法的时间,我们首先一一枚举第一行等的所有按钮按下的状态,第一行有6盏灯,每盏灯可以有按下和不按两种状态,于是他们所有的结果就是2^6=64,然后要将所有的灯遍历一次,那么总遍历数就是64*5*6。如果我们从列的一行开始枚举,那么枚举状态数量将是2^5=32,但是在现实中,还要考虑高速缓存的关系。所以随便哪种都可以。

宽度=w,高度=h,w小于h

我们可以很容易的推算出时间复杂度为theta(2^w)。指数级也是没办法的事=_=#

下面就看一下它的伪代码实现:

得到输入puzzle[5][6]二维数组
初始化按下数组Press[5][6]

for 第一行Press[1]的每一种按下去的状态
    计算之后的的Press数组的按下状态
    看看puzzle数组的地五行是否全部灯灭 puzzle[5]
    如果全部灯灭,则打印Press数组
    否则继续

如果没有正确的Press数组,那么就报告说,没有方案可以使得灯全部熄灭!

下面我们来把伪代码转换成C++代码

经过作者一小时的编程。。。才觉得课程中的代码简直不要太好!它的优点是没有进行赋值操作,如果我按照自然顺序的话,也就是检测按下灯,而改变灯的状态,那是相当费时费空间的。。。。。。可是老师好像没有给源码的样子。。。

#include <iostream>

void PrintTowDimensionArray(size_t row, size_t col);
void PrintResult(int n);
bool guess();
int EachConvertOneZero(int val);
bool CheckIsInRange(int value, int lower = 0, int upper = 5);

static int Press[5][6], Puzzle[5][6];

int main() {
    /*得到输入puzzle[5][6]二维数组
        初始化按下数组Press[5][6]

        for 第一行Press[1]的每一种按下去的状态
            计算之后的的Press数组的按下状态
            看看puzzle数组的地五行是否全部灯灭 puzzle[5]
            如果全部灯灭,则打印Press数组
            否则继续

            如果没有正确的Press数组,那么就报告说,没有方案可以使得灯全部熄灭!*/
    size_t count;
    std::cin >> count;
    for (size_t n = 0; n < count; n++) {
        //Get Input Puzzle,Init Press
        for (size_t row = 0; row < 5; row++) {
            for (size_t col = 0; col < 6; col++) {
                std::cin >> Puzzle[row][col];
                Press[row][col] = 0;
            }//for col
        }//for row
        if (guess()) {
            PrintResult(n);
            continue;
        }
        bool IsOK = false;
        //every case in first row of Press 
        for (size_t i = 1; i < 64; i++) {
            Press[0][0]++;
            int c = 0;
            while (Press[0][c]>1) {
                Press[0][c] = 0;
                c++;
                Press[0][c]++;
            }
            if (guess()) {
                PrintResult(n);
                IsOK = true;
                break;
            }
        }
        if (IsOK) {
            std::cout << "Puzzle#" << n + 1 << " No Result\0";
        }
    }//for case Number

    getchar();
    getchar();
    return 0;

}

void PrintTowDimensionArray(size_t row, size_t col) {
    for (size_t i = 0; i < row; i++) {
        for (size_t j = 0; j < col; j++) {
            std::cout << Press[i][j] << " ";
        }
        std::cout << '\n';
    }
}
void PrintResult(int i) {
    std::cout << "Puzzle#" << i << '\0';
    PrintTowDimensionArray(5, 6);
    std::cout << std::endl;
}

bool guess() {

    int PuzzleCopy[5][6];
    for (size_t i = 0; i < 5; i++) {
        for (size_t j = 0; j < 6; j++) {
            PuzzleCopy[i][j] = Puzzle[i][j];
        }
    }
    auto PressCopy = Press;
    for (size_t row = 0; row < 5; row++) {
        for (size_t col = 0; col < 6; col++) {
            if (row > 0) {
                PressCopy[row][col] = PuzzleCopy[row - 1][col];
            }
            if (PressCopy[row][col]) {
                if (CheckIsInRange(row - 1, 0, 4)) {
                    PuzzleCopy[row - 1][col] = EachConvertOneZero(PuzzleCopy[row - 1][col]);
                }
                if (CheckIsInRange(row + 1, 0, 4)) {
                    PuzzleCopy[row + 1][col] = EachConvertOneZero(PuzzleCopy[row + 1][col]);
                }
                if (CheckIsInRange(col - 1, 0, 5)) {
                    PuzzleCopy[row][col - 1] = EachConvertOneZero(PuzzleCopy[row][col - 1]);
                }
                if (CheckIsInRange(col + 1, 0, 5)) {
                    PuzzleCopy[row][col + 1] = EachConvertOneZero(PuzzleCopy[row][col + 1]);
                }
                PuzzleCopy[row][col] = EachConvertOneZero(PuzzleCopy[row][col]);
            }
        }
    }
    for (size_t i = 0; i < 6; i++) {
        if (PuzzleCopy[4][i]) {
            return false;
        }
    }
    return true;
}
int EachConvertOneZero(int val) {
    return val == 0 ? 1 : 0;
}
bool CheckIsInRange(int value, int lower, int upper) {
    return value >= lower&&value <= upper;
}

虽然我这个要比老师的代码直观一点点。。。不过在运行效率和空间效率上以及代码量上都不太理想。。。

好了,还有一个青蛙跳的问题,以及两道作业,额。。。人生好艰难=_=#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值