MIT 6.00 导论课程笔记(三)

Lecture 09

二分法

实际上是重复了 Lecture 8 的内容。

Lisp语言存储列表是使用了box pointer diagram,使用的是链表,每一个盒子有两个指针,一个指向下一个位置,一个指向值对象。这种存储链表的方式如果要找到第i个列表元素的话,需要按顺序一个一个的来,即线性查找时间。

Python和Fortran语言改进了这一问题,采用了另一种方式来存储列表。使用一整块来存储数据,每一块中每一个格子都有一个指针,指针指向值对象。

这两种的区别在于Python的方法省去了指向下一个指针,直接分配一整块内存给链表。这两种方法各有优劣,都是对访问时间和空间权衡之后得出的决策。

二分法隐藏的通用思想

  1. Pick the mid point
  2. Check to see if this is answer
  3. If not, reduce to small problem repeat

我们应该在search之前sort吗?

搜索无序表时,复杂度为n。
搜索有序表时,复杂度最快为nlogn。

然而,如果我们要搜索 k 次呢?

搜索无序表时,复杂度为 kn
搜索有序表时,复杂度为 nlogn+klogn

代码

#冒泡排序
def bubbleSort1(L):
    for j in range(len(L)):
        for i in range(len(L)-1):
            if L[i] > L[i+1]:
                temp = L[i]
                L[i] = L[i+1]
                L[i+1] = temp
            print L
        print '*******'

def bubbleSort(L):
    swapped = True
    while swapped:
        swapped = False
        for i in range(len(L)-1):
            if L[i]>L[i+1]:
                temp = L[i]
                L[i] = L[i+1]
                L[i+1] = temp
                swapped = True
                print L
        print '********'
#选择排序
def selectionSort(L):
    for i in range(len(L)):
        minIndex = i
        minVal = L[i]
        for j in range(i,len(L)):       
            if L[j] < minVal:
                minIndex = j
                minVal = L[j]
        L[minIndex] = L[i]
        L[i] = minVal
        print L

Lecture 10

分治法

分治算法概要步骤

  • 将问题分割为同类型的子问题
  • 单独解决这些子问题
  • 联合这些解决办法

归并排序

归并排序是分治法的一个重要例子

  • 将列表分割成一半
  • 继续分割直到每个列表都有一个元素
  • 归并这些子列表
def merge(left, right):
    result = []
    i,j = 0,0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i = i+1
        else:
            result.append(right[j])
            j = j + 1
    while i < len(left):
        result.append(left[i])
        i = i + 1
    while j < len(right):
        result.append(right[j])
        j = j+1
    return result
def mergeSort(L):
    if len(L)<2:
        return L[:]
    else:
        middle = len(L)/2
        left = mergeSort(L[:middle])
        right = mergeSort(L[middle:])
        together = merge(left,right)
        print 'merged',together
        return together

哈希

  1. 哈希算法的意思是给列表中的每个元素一个对应的索引值。
  2. 给时间以空间
  3. 要创建一个完全平均的哈希算法是困难的(完全平均的意思是列表中每个元素被检索到的时间都是一样的)

    代码示例

#这是一个整数对应整数索引的哈希算法,哈希表为small-large
def create(smallest,largest):
    intSet = []
    for i in range(smallest,largest+1):
        intSet.append(None)
    return intSet

def insert(intSet,e):
    intSet[e] = 1

def member(intSet,e):
    return intSet[e] == 1
#这是一个字母对应整数的哈希算法,利用计算机内部的ascii创建
def hashChar(e):
    return ord(e)
def cSetCreate():
    cSet = []
    for i in range(0,255):
        cSet.append(None)
    return cSet
def cSetInsert(cSet,e):
    cSet[hashChar(e)] = 1
def cSetMember(cSet,e):
    return cSet[hashChar(e)] == 1

Exception异常

这是python另一个语法机制。
可以分为

  • Unhandled exception
  • Handled exception

代码示例

#try...except被叫做tri-accept block
def readFloat(requestMsg,errMsg):
    '''test exception'''
    while True:
        val = raw_input(requestMsg)
        try:
            val = float(val)
            return val
        except:
            print errMsg

区分异常(Exception)和断言(Assert)
我的理解是:
断言是为了防止用户输出一些不符要求的值而强行检查退出;
异常则是主要防止程序真正运行时一些乱七八糟的错误。

Lecture 11

这节课几乎是纯理论的东西,讲述的是测试和调试。

  • 验证(Validation):是隐藏问题和增加自信心的过程,是测试(testing)和寻因(reasoning)的集合。
  • 调试:定位并解决程序失败的问题。
  • 防卫性程式设计

    Testing

  • 测试:主要是考察输入/输出的表现

  • 主要分为单元测试集成测试
  • 测试集的选取要合理,要大到可以给予我们信心,也要小到时间合理。

    bug

    关于bug有两条西式的谬论

  • bugs crawl in programs
  • bugs breed

    John认为有两条非常好的调试工具,一个是print语句,另一个是代码阅读。

关于调试,还有一个重要思想:系统化的思想。
如何系统化?

  1. 研究程序文本
  2. 研究如何挽回
  3. 寻找生成此项错误的最简输入
  4. 二分法查找可能出错的地方

代码示例:

def silly():
    res=[]
    done=False
    while not done:
        elem = raw_input('Enter element, return when done')
        if elem == '':
            done = True
        else:
            res.append(elem)
    #二分法第一步:选择中间部分输出查看,发现前面没问题
    #print 'res  ',res
    #定位问题,‘=’将前后引用指向了统一个对象
    tmp = res
    #如下代码进行解决
    #tmp = res[:]
    tmp.reverse()
    print 'res  ',res,'  tmp  ',tmp
    #第二步,在后面的中间进行print,发现问题
    #查到bug所在,bug的原因是tmp和res指向同一个对象
    isPal = (res == tmp)
    if isPal:
        print 'is palindrome'
    else:
        print 'not palindrome'

Lecture 12

关于调试

编程者经常犯的小错误有:

  • 自变量的顺序错误
  • 拼写错误
  • 初始化错误
  • Object vs Values
  • 假名错误
  • 副作用

    我们需要建立一个自己的犯错模型。

John对于编程者的建议有:

  • 记录下你所有的曾经的尝试,不要重复犯错
  • 重新考虑代码思路
  • 调试代码而非注释
  • 寻求他人帮助
  • 休息一会再来看
  • 慢一点,欲速不达
  • 控制代码的行数
  • 保存旧的版本

    优化问题

    简而言之就是两个部分:

  • 一个需要求极值的函数

  • 函数上的约束

经典的优化问题有:最短路径问题,旅行商问题,序列对齐问题等。接下来要介绍的是背包问题。

连续背包问题

假设一个小偷,他只有一个8磅的背包,有三种货物,4磅的金砂,3磅的银砂和10磅的葡萄干。如果这个小偷想拿到最有价值的分配,他该怎么做?
显而易见的做法是:
先装4磅金砂,再装3磅银砂,最后装1磅的葡萄干。
为什么说这个问题是连续背包呢?因为金砂还有银砂可以看做是无限小的。
这个问题的数学描述是

function=cg×pg+cs×ps+cr×pr

constraints=pg+ps+pr8

其中 cg,cs,cr 分别代表金砂的价值,银砂的价值和葡萄干的价值;
pg,ps,pr 分别代表金砂的重量,银砂的重量和葡萄干的重量。
这个问题使用贪婪算法就可以解决。
但是,
局部最优并不能总是保证全局最优。

不连续背包问题

0/1 Knapsack Problem
不连续背包问题中物品的重量是不能再细分的。
同样是一个小偷,他有8磅容量的背包,房间内物品有

  • 表,重0.5磅,价值A位,两块
  • 收音机,重2磅,价值D位,两块
  • Tiffany花瓶,重5磅,价值B位,两个
  • Velvet Evis,重6磅,价值C位,两块

现在有三个小偷

  • 一个是贪婪的贼,使用贪婪算法(greedy algorithm),他的选择是先拿最贵的,也就是 2+1
  • 一个精心思考的慢性子的贼,他会使用蛮力法(Bruce algorithm),他的选择时把每种选择都试一遍,他最后还没试出最优解就被抓到警察局了。
  • 还有最后一种使我们这样聪明的贼。

现在用数学公式描述一下上述问题

function=i=1nPiXi

constraint=i=1nwiXi

其中, n 是物品的总数, wi 是第 i 个物品的重量, Pi 是第 i 个物品的价值, X 是一个向量,例如 (0,0,0,0,0,0,0,0) 代表8个物品中没有取任意一个。

动态编程

Dynamin Programming, John说这个名字没有任何意义。

它的重点在于

  • overlapping subproblems(重叠子问题)
  • optimal substructrure(优化子结构)

猜你喜欢

转载自blog.csdn.net/sjz_hahalala479/article/details/55105809