Archive for 2007
ICS Lab3 简易攻略
这个 Lab 应该比上一个 Lab 简单不少, Lab 的说明文档也差不多把基本的过关要点都提到了,所以这里主要介绍一下几个相关工具的使用,帮助大家进一步熟悉 Linux 命令行 ![]()
首先从主页上下载一个压缩包,最简单的方法是直接在命令行使用 wget 工具
wget http://10.132.143.100/lab2007/buflab-handout.tar
顺便提一下 wget 的功能很强大,除了可以在后台运行,它还支持 ftp https http 等协议,提供整个网站下载的功能(类似于 Windows 下的 WebZIP )
接着使用 tar 命令解压这个文件
tar xvf buflab-handout.tar
其中, x 参数表示解压工作, v 表示显示解压所得的文件列表, f 及其后面跟着的文件名指定了待解压文件
这样在当前目录下就多出了三个文件: bufbomb, makecookie, sendstring
接下来使用 makecookie 得到自己学号对应的 cookie 号,用法是
./makecookie 学号
这个 cookie 号在后面的几个关卡中都有可能会用到,同时 http://10.132.143.234/bufbombstatus.html 中可以通过自己的 cookie 号随时获得自己现在的过关情况。
和上一个 Lab 一样,这里介绍一下怎么过 Level 0 。
这个热身关和两个函数有关, test() 和 smoke() ,正常情况下 smoke 不会被任何函数调用,我们要做的就是输入一串特定的字符,使得 smoke 被调用。另外这个关卡简单之处在于 smoke 被调用后就直接退出程序,我们在破坏了栈的数据后不需要恢复原样。
注意到 test 中调用了 getbuf 函数,联系到 CS:APP Section 3.13 ,看来只要把 getbuf 的返回地址改成 smoke 的地址就行了。
召唤 gdb 监视 bufbomb 的运行,设置断点在 getbuf ,使用 run -t 学号 运行,程序停在 getbuf 的第三行,使用 disas 查看当前函数 (getbuf) 的汇编代码
0x08048ab3 : lea 0xffffffe8(%ebp),%eax
0x08048ab6 : sub $0x24,%esp
0x08048ab9 : push %eax
0x08048aba : call 0x8048bbc
0x08048abf : mov $0x1,%eax
看来这里的 %eax 保存了字符串的首字节地址,从第一句可以算出,它与 %ebp 指向的地址相差了 24 。
也就是说我们输入 24 个字符后,接下来输入的 4 个字符会覆盖掉原来的帧指针,再之后的 4 个字符就会覆盖函数的返回地址。把这个地址覆盖成 smoke 的就行,用 disas smoke 得到 smoke 的地址为 0×08048964 。
知道了这些后,我们就来构造这条特殊的字符串,新建一个文本文件(当然也可以在 sendstring 中键盘输入,不过调试起来比较麻烦),输入
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 89 04 08
最后四个字节就是 smoke 的地址,注意是 little endian 约定。假设保存文件为 ans.txt
但这只是文本格式的指令,需要把它转换为二进制值, sendstring 工具可以帮助我们生成二进制串:
cat ans.txt | ./sendstring > raw.txt
这行命令用到了 shell 的管道和重定向功能,它的作用是把 ans.txt 的内容放到 sendstring 中,把 sendstring 生成的内容放到 raw.txt 中。这样在 raw.txt 中包含了最后的二进制代码。
回到 gdb ,使用 run -t 学号 < raw.txt 运行程序,程序停在 getbuf 中的断点后,使用几个 ni 命令往下运行,等调用完 Gets 函数后(一般是在 0x08048abf ),查看当前的帧指针附近的信息:
(gdb) p/x *(int*) ebp@2
$ 1 = {0x0, 0x8048964}
看来我们已经成功得把返回地址改掉了,使用 c(ontinue) 继续运行,就可以看到成功信息了。
后面几关(除了最后一个)和这个热身关差不多,只是返回地址需要指向 buf 数组,运行嵌在其中的代码再返回原来的地址,要获得嵌入的二进制代码,可以先写一个包含汇编指令的 .s 文件,使用 gcc -c 把这个文件编译成 relocatable object ,再用 objdump -d 得到这段二进制代码。大致思路 pdf 上已经写得很详细了,这里不再赘述。
关于最后一关, CS:APP 书上有一个类似的题目 (3.38, P236) ,我记得我当时做的时候栈帧地址也是会跳动的,只是没有 Level 4 那个那么明显,有兴趣的同学可以试一下。
程序的下载地址: http://10.132.141.124/study/ics/bufbomb.c
其他几个提示:
输入的文本格式的二进制代码计算字节数比较麻烦,可以每 10 个字节一行,写完后使用 vim 的 shift+j 快捷键把这几行都拼起来。
本地过关后记得查看网站确定一下。
gdb 的 run 命令会自动根据上一次的参数运行程序,所以第一次输入 run -t 学号 < raw.txt 后,以后只要简单的使用 r 就可以了。
建议开两个 PieTTY 窗口,一个 gdb 调试,一个修改、生成二进制文本,很方便。
ICS Lab2
这个lab主要考察gdb的使用和对汇编代码的理解。后者在平时的作业中涉及得较多,这里不再赘述,主要介绍一下gdb
其实偶对这个也不是很熟,有错误请指正 @@
简单的说,gdb是一款强大的调试工具,尽管它只有文本界面(需要图形界面可以使用ddd,不过区别不大),但是功能却比eclipse等调试环境强很多。
接下来看看怎样让它为lab2拆炸弹服务,在命令行下运行gdb bomb就能开始调试这个炸弹程序,提高警惕,恩
首先最重要的,就是如何阻止炸弹的引爆,gdb自然提供了一般调试工具都包括的断点功能——break命令
在gdb中输入help break能够看到相关的信息
(gdb) help break Set breakpoint at specified line or function. Argument may be line number, function name, or "*" and an address. If line number is specified, break at start of code for that line. If function is specified, break at start of code for that function. If an address is specified, break at that exact address. With no arg, uses current execution address of selected stack frame. This is useful for breaking on return to a stack frame. Multiple breakpoints at one place are permitted, and useful if conditional. Do "help breakpoints" for info on other commands dealing with breakpoints.
可以看到break允许我们使用行号、函数名或地址设置断点
按ctrl+z暂时挂起当前的gdb进程,运行objdump –d bomb | more 查看反编译后的炸弹文件,可以看到里面有这么一行(开始的那个地址每个人都不同):
08049719 <explode_bomb>:
这个就是万恶的引爆炸弹的函数了,运行fg返回gdb环境,在这个函数设置断点:
break explode_bomb (可以使用tab键自动补齐)
显示
Breakpoint 1 at 0×8049707
接下来你可以喘口气,一般情况下炸弹是不会引爆的了
下面我们来拆第一个炸弹,首先同样是设置断点,bomb.c中给出了各个关卡的函数名,第一关就是phase_1,使用break phase_1在第一关设置断点
接下来就开始运行吧,输入run
Welcome to my fiendish little bomb. You hava 6 phases with
which to blow yourself up. Hava a nice day!
我们已经设置了炸弹断点,这些恐吓可以直接无视。
输入ABC继续(输入这个是为了方便在后面的测试中找到自己的输入串地址)
提示Breakpoint 2, 0x08048c2e in phase_1 (),说明现在程序已经停在第一个关了
接下来就是分析汇编代码,使用disassemble phase_1显示这个函数的汇编代码
注意其中关键的几行:
8048c2e:68 b4 99 04 08 push $0x80499b4 8048c33:ff 75 08 pushl 0x8(%ebp) 8048c36:e8 b1 03 00 00 call 8048fec <strings_not_equal>
这个lab很厚道的一点就是函数名很明确地说明了函数的功能 ^_^
估计这三行代码的意思就是比较两个字符串相等,不相等的话应该就会让炸弹爆炸了
因为字符串很大,所以传递给这个比较函数的肯定是他们的地址,分别为0x80499b4和0×8(%ebp)
我们先来看后者,使用p/x *(int*)($ebp +
查看字符串所在的地址
$1 = 0x804a720,继续使用p/x *0x804a720查看内存中这个地址的内容
$2 = 0×434241,连续的三个数,是不是想起什么了?把这三个数分别转换为十进制,就是67 66 65,分别为CBA的ASCII码,看来这里保存了我们输入的串。
接下来0x80499b4里肯定保存着过关的密码
p/x *0x80499b4,显示$3 = 0×62726556,c中的字符串是以0结尾的,看来这个字符串还不止这个长度,继续使用
p/x *0x80499b4@10查看这个地址及其后面36个字节的内容,终于在第二行中出现了终结符”0×0”(不一定是四个字节)
$4 = {0×62726556, 0x7469736f, 0x656c2079, 0×20736461, 0x75206f74, 0x656c636e, 0x202c7261, 0x72616e69,
0×75636974, 0x6574616c, 0×69687420, 0x2e73676e, 0×0, 0x21776f57, 0x756f5920, 0×20657627, 0×75666564,
0×20646573, 0×20656874, 0×72636573}
把开头到0×0的所有信息字节下来,通过手算或者自己写程序得出最后的密码串(注意little endian中字符的排列方式!)
输入run重新运行,输入刚才得出的密码串,如果前面的计算正确的话,就会提示
Phase 1 defused. How about the next one?
关于这个lab的一些其他心得:
1. VMware中开发很不舒服,屏幕小、字体丑@@、需要Ctrl+Alt切换回windows,不怎么方便,推荐在windows下使用pietty登录虚拟机中的linux系统(RedHat 9默认安装了sshd),个人觉得这样比较方便。
2. ASCII查询可以在linux终端中运行man ascii。
3. 退出gdb后,再次进入时一定要注意使用break给explode_bomb上断点,不可大意 ~.~
4. 后面的几关涉及递归等内容,也有和前面几次作业很相似的东东。
5. gdb中还有一个很好用的jump指令,可以在运行时任意跳转。
6. 看汇编代码时,使用objdump -d bomb > bomb.asm把汇编代码保存到bomb.asm中,然后使用sftp工具把这个文件下载到windows或者直接在vim中查看,这样比在gdb中看方便一些。
7. 个人认为lab2和期中考试不冲突,这个lab2可以帮你理清很多汇编语言的概念
其他补充:
sfox:
可以通过GDB中的x /s addr输出以\0结尾的字符串
ICSLab:
为了防止每次拆的时候都不停的输入之前的stage的key,可以把key存入文本文件,一行一个key,不要有多余字符
然后GDB run 的时候用gdb bomb 回车
(gdb) b …. ….
(gdb) r password.txt
这样bomb就会自动从password.txt中读入之前的密码
直到到达最后一个空行处,如Lab2的说明文档中所述。
[zz]什么是P问题、NP问题和NPC问题
http://www.matrix67.com/blog/archives/105
这或许是众多OIer最大的误区之一。
你会经常看到网上出现“这怎么做,这不是NP问题吗”、“这个只有搜了,这已经被证明是 NP问题了”之类的话。你要知道,大多数人此时所说的NP问题其实都是指的NPC问题。他们没有搞清楚NP问题和NPC问题的概念。NP问题并不是那种“ 只有搜才行”的问题,NPC问题才是。好,行了,基本上这个误解已经被澄清了。下面的内容都是在讲什么是P问题,什么是NP问题,什么是NPC问题,你如 果不是很感兴趣就可以不看了。接下来你可以看到,把NP问题当成是 NPC问题是一个多大的错误。
还是先用几句话简单说明一下时 间复杂度。时间复杂度并不是表示一个程序解决问题需要花多少时间,而是当问题规模扩大后,程序需要的时间长度增长得有多快。也就是说,对于高速处理数据的 计算机来说,处理某一个特定数据的效率不能衡量一个程序的好坏,而应该看当这个数据的规模变大到数百倍后,程序运行时间是否还是一样,或者也跟着慢了数百 倍,或者变慢了数万倍。不管数据有多大,程序处理花的时间始终是那么多的,我们就说这个程序很好,具有O(1)的时间复杂度,也称常数级复杂度;数据规模 变得有多大,花的时间也跟着变得有多长,这个程序的时间复杂度就是O(n),比如找n个数中的最大值;而像冒泡排序、插入排序等,数据扩大2倍,时间变慢 4倍的,属于O(n^2)的复杂度。还有一些穷举类的算法,所需时间长度成几何阶数上涨,这就是O(a^n)的指数级复杂度,甚至O(n!)的阶乘级复杂 度。不会存在O(2*n^2)的复杂度,因为前面的那个“2”是系数,根本不会影响到整个程序的时间增长。同样地,O (n^3+n^2)的复杂度也就是O(n^3)的复杂度。因此,我们会说,一个O(0.01*n^3)的程序的效率比O(100*n^2)的效率低,尽管 在n很小的时候,前者优于后者,但后者时间随数据规模增长得慢,最终O(n^3)的复杂度将远远超过O(n^2)。我们也说,O(n^100)的复杂度小 于O(1.01^n)的复杂度。
容易看出,前面的几类复杂度被分为两种级别,其中后者的复杂度无论如何都远远大于前者:一种是 O(1),O(log(n)),O(n^a)等,我们把它叫做多项式级的复杂度,因为它的规模n出现在底数的位置;另一种是O(a^n)和O(n!)型复 杂度,它是非多项式级的,其复杂度计算机往往不能承受。当我们在解决一个问题时,我们选择的算法通常都需要是多项式级的复杂度,非多项式级的复杂度需要的 时间太多,往往会超时,除非是数据规模非常小。
自然地,人们会想到一个问题:会不会所有的问题都可以找到复杂度为多项式级的算法呢?很遗憾,答案是否定的。有些问题甚至根本不可能找到一个正确的算法来,这称之为“不可解问题”(Undecidable Decision Problem)。The Halting Problem就 是一个著名的不可解问题,在我的Blog上有过专门的介绍和证明。再比如,输出从1到n这n个数的全排列。不管你用什么方法,你的复杂度都是阶乘级,因为 你总得用阶乘级的时间打印出结果来。有人说,这样的“问题”不是一个“正规”的问题,正规的问题是让程序解决一个问题,输出一个“YES”或“NO”(这 被称为判定性问题),或者一个什么什么的最优值(这被称为最优化问题)。那么,根据这个定义,我也能举出一个不大可能会有多项式级算法的问题 来:Hamilton回路。问题是这样的:给你一个图,问你能否找到一条经过每个顶点一次且恰好一次(不遗漏也不重复)最后又走回来的路(满足这个条件的 路径叫做Hamilton回路)。这个问题现在还没有找到多项式级的算法。事实上,这个问题就是我们后面要说的NPC问题。
下面 引入P类问题的概念:如果一个问题可以找到一个能在多项式的时间里解决它的算法,那么这个问题就属于P问题。P是英文单词多项式的第一个字母。哪些问题是 P类问题呢?通常NOI和NOIP不会出不属于P类问题的题目。我们常见到的一些信息奥赛的题目都是P问题。道理很简单,一个用穷举换来的非多项式级时间 的超时程序不会涵盖任何有价值的算法。
接下来引入NP问题的概念。这个就有点难理解了,或者说容易理解错误。在这里强调(回到我竭力想澄 清的误区上),NP问题不是非P类问题。NP问题是指可以在多项式的时间里验证一个解的问题。NP问题的另一个定义是,可以在多项式的时间里猜出一个解的 问题。比方说,我RP很好,在程序中需要枚举时,我可以一猜一个准。现在某人拿到了一个求最短路径的问题,问从起点到终点是否有一条小于100个单位长度 的路线。它根据数据画好了图,但怎么也算不出来,于是来问我:你看怎么选条路走得最少?我说,我RP很好,肯定能随便给你指条很短的路出来。然后我就胡乱 画了几条线,说就这条吧。那人按我指的这条把权值加起来一看,嘿,神了,路径长度98,比100小。于是答案出来了,存在比100小的路径。别人会问他这 题怎么做出来的,他就可以说,因为我找到了一个比100 小的解。在这个题中,找一个解很困难,但验证一个解很容易。验证一个解只需要O(n)的时间复杂度,也就是说我可以花O(n)的时间把我猜的路径的长度加 出来。那么,只要我RP好,猜得准,我一定能在多项式的时间里解决这个问题。我猜到的方案总是最优的,不满足题意的方案也不会来骗我去选它。这就是NP问 题。当然有不是NP问题的问题,即你猜到了解但是没用,因为你不能在多项式的时间里去验证它。下面我要举的例子是一个经典的例子,它指出了一个目前还没有 办法在多项式的时间里验证一个解的问题。很显然,前面所说的Hamilton回路是NP问题,因为验证一条路是否恰好经过了每一个顶点非常容易。但我要把 问题换成这样:试问一个图中是否不存在Hamilton回路。这样问题就没法在多项式的时间里进行验证了,因为除非你试过所有的路,否则你不敢断定它“没 有Hamilton回路”。
之所以要定义NP问题,是因为通常只有NP问题才可能找到多项式的算法。我们不会指望一个连多项式地验证一个 解都不行的问题存在一个解决它的多项式级的算法。相信读者很快明白,信息学中的号称最困难的问题——“NP问题”,实际上是在探讨NP问题与P类问题的关 系。
很显然,所有的P类问题都是NP问题。也就是说,能多项式地解决一个问题,必然能多项式地验证一个问题的解——既然正解都出 来了,验证任意给定的解也只需要比较一下就可以了。关键是,人们想知道,是否所有的NP问题都是P类问题。我们可以再用集合的观点来说明。如果把所有P类 问题归为一个集合P中,把所有 NP问题划进另一个集合NP中,那么,显然有P属于NP。现在,所有对NP问题的研究都集中在一个问题上,即究竟是否有P=NP?通常所谓的“NP问题 ”,其实就一句话:证明或推翻P=NP。
NP问题一直都是信息学的巅峰。巅峰,意即很引人注目但难以解决。在信息学研究中,这是一个耗费了很多时间和精力也没有解决的终极问题,好比物理学中的大统一和数学中的歌德巴赫猜想等。
目 前为止这个问题还“啃不动”。但是,一个总的趋势、一个大方向是有的。人们普遍认为,P=NP不成立,也就是说,多数人相信,存在至少一个不可能有多项式 级复杂度的算法的NP问题。人们如此坚信P≠NP是有原因的,就是在研究NP问题的过程中找出了一类非常特殊的NP问题叫做NP-完全问题,也即所谓的 NPC问题。C是英文单词“完全”的第一个字母。正是NPC问题的存在,使人们相信P≠NP。下文将花大量篇幅介绍NPC问题,你从中可以体会到NPC问 题使P=NP变得多么不可思议。
为了说明NPC问题,我们先引入一个概念——约化(Reducibility,有的资料上叫“归约”)。
简 单地说,一个问题A可以约化为问题B的含义即是,可以用问题B的解法解决问题A,或者说,问题A可以“变成”问题B。《算法导论》上举了这么一个例子。比 如说,现在有两个问题:求解一个一元一次方程和求解一个一元二次方程。那么我们说,前者可以约化为后者,意即知道如何解一个一元二次方程那么一定能解出一 元一次方程。我们可以写出两个程序分别对应两个问题,那么我们能找到一个“规则”,按照这个规则把解一元一次方程程序的输入数据变一下,用在解一元二次方 程的程序上,两个程序总能得到一样的结果。这个规则即是:两个方程的对应项系数不变,一元二次方程的二次项系数为0。按照这个规则把前一个问题转换成后一 个问题,两个问题就等价了。同样地,我们可以说,Hamilton回路可以约化为TSP问题(Travelling Salesman Problem,旅行商问题):在Hamilton回路问题中,两点相连即这两点距离为0,两点不直接相连则令其距离为1,于是问题转化为在TSP问题 中,是否存在一条长为0的路径。Hamilton回路存在当且仅当TSP问题中存在长为0的回路。
“问题A可约化为问题B”有一个重要的 直观意义:B的时间复杂度高于或者等于A的时间复杂度。也就是说,问题A不比问题B难。这很容易理解。既然问题A能用问题B来解决,倘若B的时间复杂度比 A的时间复杂度还低了,那A的算法就可以改进为B的算法,两者的时间复杂度还是相同。正如解一元二次方程比解一元一次方程难,因为解决前者的方法可以用来 解决后者。
很显然,约化具有一项重要的性质:约化具有传递性。如果问题A可约化为问题B,问题B可约化为问题C,则问题A一定可约化为问题C。这个道理非常简单,就不必阐述了。
现在再来说一下约化的标准概念就不难理解了:如果能找到这样一个变化法则,对任意一个程序A的输入,都能按这个法则变换成程序B的输入,使两程序的输出相同,那么我们说,问题A可约化为问题B。
当然,我们所说的“可约化”是指的可“多项式地”约化(Polynomial-time Reducible),即变换输入的方法是能在多项式的时间里完成的。约化的过程只有用多项式的时间完成才有意义。
好 了,从约化的定义中我们看到,一个问题约化为另一个问题,时间复杂度增加了,问题的应用范围也增大了。通过对某些问题的不断约化,我们能够不断寻找复杂度 更高,但应用范围更广的算法来代替复杂度虽然低,但只能用于很小的一类问题的算法。再回想前面讲的P和NP问题,联想起约化的传递性,自然地,我们会想 问,如果不断地约化上去,不断找到能“通吃”若干小NP问题的一个稍复杂的大NP问题,那么最后是否有可能找到一个时间复杂度最高,并且能“通吃”所有的 NP问题的这样一个超级NP问题?答案居然是肯定的。也就是说,存在这样一个NP问题,所有的NP问题都可以约化成它。换句话说,只要解决了这个问题,那 么所有的NP问题都解决了。这种问题的存在难以置信,并且更加不可思议的是,这种问题不只一个,它有很多个,它是一类问题。这一类问题就是传说中的NPC 问题,也就是NP-完全问题。NPC问题的出现使整个NP问题的研究得到了飞跃式的发展。我们有理由相信,NPC问题是最复杂的问题。再次回到全文开头, 我们可以看到,人们想表达一个问题不存在多项式的高效算法时应该说它“属于NPC问题”。此时,我的目的终于达到了,我已经把NP问题和NPC问题区别开 了。到此为止,本文已经写了近5000字了,我佩服你还能看到这里来,同时也佩服一下自己能写到这里来。
NPC问题的定义非常简 单。同时满足下面两个条件的问题就是NPC问题。首先,它得是一个NP问题;然后,所有的NP问题都可以约化到它。证明一个问题是 NPC问题也很简单。先证明它至少是一个NP问题,再证明其中一个已知的NPC问题能约化到它(由约化的传递性,则NPC问题定义的第二条也得以满足;至 于第一个NPC问题是怎么来的,下文将介绍),这样就可以说它是NPC问题了。
既然所有的NP问题都能约化成NPC问题,那么只要任意一 个NPC问题找到了一个多项式的算法,那么所有的NP问题都能用这个算法解决了,NP也就等于P 了。因此,给NPC找一个多项式算法太不可思议了。因此,前文才说,“正是NPC问题的存在,使人们相信P≠NP”。我们可以就此直观地理解,NPC问题 目前没有多项式的有效算法,只能用指数级甚至阶乘级复杂度的搜索。
顺便讲一下NP-Hard问题。NP-Hard问题是这样一种 问题,它满足NPC问题定义的第二条但不一定要满足第一条(就是说,NP-Hard问题要比 NPC问题的范围广)。NP-Hard问题同样难以找到多项式的算法,但它不列入我们的研究范围,因为它不一定是NP问题。即使NPC问题发现了多项式级 的算法,NP-Hard问题有可能仍然无法得到多项式级的算法。事实上,由于NP-Hard放宽了限定条件,它将有可能比所有的NPC问题的时间复杂度更 高从而更难以解决。
不要以为NPC问题是一纸空谈。NPC问题是存在的。确实有这么一个非常具体的问题属于NPC问题。下文即将介绍它。
下文即将介绍逻辑电路问题。这是第一个NPC问题。其它的NPC问题都是由这个问题约化而来的。因此,逻辑电路问题是NPC类问题的“鼻祖”。
逻辑电路问题是指的这样一个问题:给定一个逻辑电路,问是否存在一种输入使输出为True。
什么叫做逻辑电路呢?一个逻辑电路由若干个输入,一个输出,若干“逻辑门”和密密麻麻的线组成。看下面一例,不需要解释你马上就明白了。
┌───┐
│ 输入1├─→┐ ┌──┐
└───┘ └─→┤ │
│ or ├→─┐
┌───┐ ┌─→┤ │ │ ┌──┐
│ 输入2├─→┤ └──┘ └─→┤ │
└───┘ │ ┌─→┤AND ├──→输出
└────────┘┌→┤ │
┌───┐ ┌──┐ │ └──┘
│ 输入3├─→┤ NOT├─→────┘
└───┘ └──┘
这是个较简单的逻辑电路,当输入1、输入2、输入3分别为True、True、False或False、True、False时,输出为True。
有输出无论如何都不可能为True的逻辑电路吗?有。下面就是一个简单的例子。
┌───┐
│输入1 ├→─┐ ┌──┐
└───┘ └─→┤ │
│AND ├─→┐
┌─→┤ │ │
│ └──┘ │ ┌──┐
│ └→┤ │
┌───┐ │ │AND ├─→输出
│输入2 ├→─┤ ┌──┐ ┌→┤ │
└───┘ └→┤NOT ├→──┘ └──┘
└──┘
上面这个逻辑电路中,无论输入是什么,输出都是False。我们就说,这个逻辑电路不存在使输出为True的一组输入。
回到上文,给定一个逻辑电路,问是否存在一种输入使输出为True,这即逻辑电路问题。
逻 辑电路问题属于NPC问题。这是有严格证明的。它显然属于NP问题,并且可以直接证明所有的NP问题都可以约化到它(不要以为NP问题有无穷多个将给证明 造成不可逾越的困难)。证明过程相当复杂,其大概意思是说任意一个NP问题的输入和输出都可以转换成逻辑电路的输入和输出(想想计算机内部也不过是一些 0和1的运算),因此对于一个NP问题来说,问题转化为了求出满足结果为True的一个输入(即一个可行解)。
有了第一个NPC 问题后,一大堆NPC问题就出现了,因为再证明一个新的NPC问题只需要将一个已知的NPC问题约化到它就行了。后来,Hamilton 回路成了NPC问题,TSP问题也成了NPC问题。现在被证明是NPC问题的有很多,任何一个找到了多项式算法的话所有的NP问题都可以完美解决了。因此 说,正是因为NPC问题的存在,P=NP变得难以置信。P=NP问题还有许多有趣的东西,有待大家自己进一步的挖掘。攀登这个信息学的巅峰是我们这一代的 终极目标。现在我们需要做的,至少是不要把概念弄混淆了。
Matrix67原创
转载请注明出处