算法导论习题4-5:芯片检测

2019.11.14更新: 修正了方法1里的一个错误,增加了方法3。

习题4-5 3rd edition (4-6 2nd edition)

Diogenes 教授有n个被认为是完全相同的VLSI芯片,原则上它们是可以互相测试的。教授的测试装置一次可测试二片,当该装置中放有两片芯片时,每一片就对另一片作测试并报告其好坏。一个好的芯片总能够正确的报告另一片的好坏,但一个坏的芯片的结果就是不可靠的。这样,每次的测试的四种可能结果如下:

A芯片报告       B芯片报告                结论 
-------------------------------------------------------- 
B是好的          A是好的            都是好的,或都是坏的 
B是好的          A是坏的            至少一片是坏的 
B是坏的          A是好的            至少一片是坏的 
B是坏的          A是坏的            至少一片是坏的 

a)证明若不少于 n/2 的芯片是坏的,在这种成对测试方式下,使用任何策略都不能确定哪个芯片是好的。
b)假设有多于 n/2 的芯片是好的,考虑从 n 片中找出一片好芯片的问题。证明 n/2 对测试就足以使问题的规模降至近原来的一半。 
c)假设有多于 n/2 的芯片是好的,证明好的芯片可用 O(n) 对测试找出.给出并解答表达测试次数的递归式。

第一问证明如下:将芯片分为3组,(a)好的(b)数量和a组相同的坏的(c)剩下的坏的。若每组都坚持认为本组是好的而其它组的都是坏的,则a组和b组无法区分。

可以通过如下三种方法找出一个好的芯片,时间复杂度都是O(n)。

解法1:

即对第二问的证明。将芯片两两配对,对于后三种情况(至少其中一个是坏的),可以直接将该对芯片丢弃,这样丢弃的好的一定不会超过坏的。剩下的都是第一种情况,以及可能剩下的单个未配对的。如果数量为偶数,即没有未配对的,那么好芯片的对数一定超过坏芯片对数,所以每对里面丢弃一个即可。如果数量为奇数,则会剩下一个未配对的,分两种情况讨论,1)报告“好好”的总对数为奇数:那么其中的好芯片对数一定超过坏芯片的对数,每对保留一个,并且丢掉剩下的那个未配对的;2)报告的“好好”总对数为偶数:要么好对数等于坏对数,此时剩下的一个必然是好芯片,要么好对数>=坏对数+2,此时剩下的一个是好是坏无所谓,策略都是每对保留一个,并且保留剩下的那个未配对的。代码见findGoodByBisection函数。

解法2:

优点是只需单向检测即可,其实解法1稍作修改也可只用到单向检测(如果a认为b是好的则将a丢弃,否则两个都丢弃)。本方法是基于如下事实:用所有芯片去检测某一个固定芯片,如果报告好的次数 >= 坏的次数,则被测芯片是好的。

方法如下:分别用第2,3,4...个芯片去检测第一个,一旦坏的次数 > 好的次数,则将所有用过的芯片丢弃。

分两种情况讨论:(1)被测芯片是好的 (2)被测芯片是坏的。这两种情况都可以保证丢弃的好芯片一定不多于坏芯片。代码见findGoodByOneDirection函数。

解法3:

有一道常见面试题Leetcode 169. Majority Element: 找出数组中出现次数超过一半的数字。解法是每次丢掉两个不同的数字。本质上和此题是一样的。代码见findGoodByMajorityElement函数。

import random
class Chip:
    def __init__(self, isGood):
        #1 good, 0 bad
        self.isGood = isGood
        
    def test(self, other):
        if self.isGood:
            return other.isGood
        else:
            #return random.randint(0,1)
            #make it harder: pretend to be good
            return 1 - other.isGood

def findGoodByBisection (a):
    if len(a) == 1:
        return a[0]
    candidate = []
    i = 0
    while i+1 < len(a):
        if a[i].test(a[i+1]) and a[i+1].test(a[i]):
            candidate.append(a[i])
        #else discard both
        i += 2
    #the last one is unpaired
    if i == len(a) - 1:
        if len(candidate) % 2:
            #tested good pairs > bad pairs, discard the unpaired
            #e.g. 4 good pairs, 3 bad pairs
            pass
        else:
            #case 1:
            #tested good pairs == bad pairs, the unpairs must be good
            #e.g. 5 good pairs, 5 bad pairs, and a unpaired
            #the unpaired must be kept
            #case 2:
            #tested good pairs >= bad pairs + 2, doesn't matter what the unpaired is
            #e.g. 6 good pairs, 4 bad pairs
            candidate.append(a[i])
    return findGoodByBisection(candidate)

def findGoodByOneDirection(a):
    good, bad = 0, 0
    for i in range(1,len(a)):
        if a[i].test(a[0]):
            good += 1
        else:
            bad += 1
        if bad > good:
            return findGoodByOneDirection(a[i+1:])
    return a[0]

#Leetcode 169. Majority Element
#https://leetcode.com/problems/majority-element/
def findGoodByMajorityElement(a):
    sameType = []
    for chip in a:
        if not sameType:
            sameType.append(chip)
        elif sameType[0].test(chip) and chip.test(sameType[0]):
            sameType.append(chip)
        else:
            #different type, discard both
            sameType.pop()
    return sameType[0]

def run():
    chips = []
    badSize = random.randint(0,10)
    goodSize = badSize + random.randint(1,2)
    for i in range(badSize):
        chips.append(Chip(0))
    for i in range(goodSize):
        chips.append(Chip(1))
    random.shuffle(chips)
    print("good size {} bad size {}".format(goodSize, badSize))
    assert findGoodByBisection(chips).isGood
    assert findGoodByOneDirection(chips).isGood
    assert findGoodByMajorityElement(chips).isGood
    
for i in range(100):
    run()

 

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页