Mit6.006-problemSet01

1-1 函数的渐近性(asymptotic behavior)

每个包含5个函数的集合,对它们进行排序,若序列中 f a f_a fa出现在 f b f_b fb之前,那么 f a = O ( f b ) f_a=\mathcal{O}(f_b) fa=O(fb)。如果 f a = O ( f b ) f_a=\mathcal{O}(f_b) fa=O(fb),且 f b = O ( f a ) f_b=\mathcal{O}(f_a) fb=O(fa),意味着: f a 和 f b f_a和f_b fafb可以出现在任意顺序,表明可以用花括号将 f a 和 f b f_a和f_b fafb围到一个集合中。举例,如果函数是: f 1 = n , f 2 = n , f 3 = n + n f_1=n,f_2=\sqrt{n},f_3=n+\sqrt{n} f1=nf2=n f3=n+n ,正确的答案是: ( f 2 , { f 1 , f 3 } ) 或 ( f 2 , { f 3 , f 1 } ) (f_2,\{f_1,f_3\})或(f_2,\{f_3,f_1\}) (f2,{f1,f3})(f2,{f3,f1})

注意: a b c 代 表 a ( b c ) , 并 非 ( a b ) c a^{b^c}代表a^{(b^c)},并非(a^b)^c abca(bc)(ab)c log ⁡ 代 表 log ⁡ 2 \log代表\log_2 loglog2,除非一个不同的底被明确地说明。Stirling近似估算可以帮助比较阶乘。

a)

f 1 = log ⁡ ( n n ) , f 2 = ( log ⁡ n ) n , f 3 = log ⁡ ( n 6006 ) , f 4 = ( log ⁡ n ) 6006 , f 5 = log ⁡ ( log ⁡ ( 6006 n ) ) f_1=\log(n^n),f_2=(\log n)^n,f_3=\log(n^{6006}),f_4=(\log n)^{6006},f_5=\log(\log(6006n)) f1=log(nn),f2=(logn)n,f3=log(n6006),f4=(logn)6006,f5=log(log(6006n))

f 5 , f 3 , f 4 , f 1 , f 2 f_5,f_3,f_4,f_1,f_2 f5,f3,f4,f1,f2

b)

f 1 = 2 n , f 2 = 600 6 n , f 3 = 2 600 6 n , f 4 = 600 6 2 n , f 5 = 600 6 n 2 f_1=2^n,f_2=6006^n,f_3=2^{6006^n},f_4=6006^{2^n},f_5=6006^{n^2} f1=2n,f2=6006n,f3=26006n,f4=60062n,f5=6006n2

f 1 , f 2 , f 5 , f 4 , f 3 f_1,f_2,f_5,f_4,f_3 f1,f2,f5,f4,f3

c)

f 1 = n n , f 2 = ( n n − 6 ) , f 3 = ( 6 n ) ! , f 4 = ( n n / 6 ) , f 5 = n 6 f_1=n^n,f_2=\binom{n}{n-6},f_3=(6n)!,f_4=\binom{n}{n/6},f_5=n^6 f1=nn,f2=(n6n),f3=(6n)!,f4=(n/6n),f5=n6

Sterling’s近似法:

n ! = 2 π n ( n e ) n ( 1 + Θ ( 1 n ) ) n!=\sqrt{2\pi n}(\dfrac{n}{e})^n(1+\Theta(\dfrac{1}{n})) n!=2πn (en)n(1+Θ(n1))

{ f 2 , f 5 } , f 4 , f 1 , f 3 \{f_2,f_5\},f_4,f_1,f_3 {f2,f5},f4,f1,f3

d)

f 1 = n n + 4 + n ! , f 2 = n 7 n , f 3 = 4 3 n log ⁡ n , f 4 = 7 n 2 , f 5 = n 12 + 1 / n f_1=n^{n+4}+n!,f_2=n^{7\sqrt n},f_3=4^{3n\log n},f_4=7^{n^2},f_5=n^{12+1/n} f1=nn+4+n!,f2=n7n ,f3=43nlogn,f4=7n2,f5=n12+1/n

f 5 , f 2 , f 1 , f 3 , f 4 f_5,f_2,f_1,f_3,f_4 f5,f2,f1,f3,f4

1-2 给定一个支持序列操作的数据结构D

1)D.build(x)以 O ( n ) \mathcal{O}(n) O(n)时间

2)D.insert_at(i, x) 和D.delete_at(i),每个都是 O ( log ⁡ n ) \mathcal{O}(\log n) O(logn)时间

当操作执行时,n个项目存储在D。描述算法,用提供的低级操作,实现下面高级操作。下面的每个操作,应该以 O ( k log ⁡ n ) \mathcal{O}(k\log n) O(klogn)时间执行。delete_at 返回删掉的项目。

(a) reverse(D, i, k):反转D中,始于索引i,k个项目的顺序(上边界索引为 i+k-1 )。

def reverse(D, i, k):
    if k < 2:
        return
    x2 = D.delete_at(i + k - 1)
    x1 = D.delete_at(i)
    D.insert_at(i, x2)
    D.insert_at(i + k - 1, x1)
    reverse(D, i + 1, k - 2)

(b) move(D, i, k, j): 移动始于索引i的D中k个项目,以原有顺序,放到索引为 j 的项目前面。假设 i ≤ j ≤ i + k i \leq j \le i+k iji+k,不成立

def move(D, i, k, j):
    if k < 1
        return
    x = D.delete_at(i)
    if j > i
        j = j - 1
    D.insert_at(j, x)
    j = j + 1
    if i > j
        i = i + 1
    move(D, i, k - 1, j)

1-3 Binder Bookmarks

Sisa Limpson是一个非常条理的二年级学生,她的所有课程笔记,整理为一页一页的,存在一个3环装订器。如果她有n页笔记在她的装订器,首页为索引0,最后一页为索引n-1。当学习时,Sisa经常重排笔记。为了帮助重新组织,她有两个书签,A和B,帮她记录装订器中的位置。

描述一个数据库来记录Sisa装订器的页,支持下面的操作,当操作发生时,有n个页在装订器中。假设在任意shift、move操作发生前,两个书签放置在装订器中。A总是比B有更小的索引。每个操作,声明运行时间为最坏情形的,还是摊还的。

build(X)

用迭代器X中的页,以 O ( ∣ X ∣ ) \mathcal{O}(|X|) O(X)时间初始化数据库


place_mark(i, m)

把书签 m ∈ { A , B } m\in\{A,B\} m{A,B},放到索引 i 和索引 i+1之间,以 O ( n ) \mathcal{O}(n) O(n)时间


read_page(i)

返回索引 i 处的页,以 O ( 1 ) \mathcal{O}(1) O(1)时间


shift_mark(m, d)

书签 m ∈ { A , B } m\in\{A,B\} m{A,B},当前在索引为 i 的页前面,移动它到索引为 i+d 的页前面, d ∈ { − 1 , 1 } d\in\{-1,1\} d{1,1},以 O ( 1 ) \mathcal{O}(1) O(1)时间


move_page(m)

获取当前处于书签 m ∈ { A , B } m\in\{A,B\} m{A,B}前面的页,移动它到另外一个书签前面,以 O ( 1 ) \mathcal{O}(1) O(1)时间


有多种可能的解法。首先注意问题声明,要求常量时间来 read_page(i) 操作,那只可能用基于数组的实现来支持,链表方式是不正确的。另外注意到:直到两个书签被放置,我们简单地存储所有页到一个尺寸为n的静态数组,因为没有操作会改变页序列,直到书签被放置。我们提出一个解法,概括了我们课上讨论的动态数组。另外一种常见的解法,可以减少为使用两个动态数组(一个以书签A为结尾、一个以书签B为结尾),加上一个基于数组的双端队列(如Problem Session 1中描述),来存储书签A和B中的页。

对于我们的方式,在两个书签放置后,我们将存储n页到一个尺寸为3n的静态数组S,无论何时build(X)或place_mark(i, m)被调用,我们可以 O ( n ) \mathcal{O}(n) O(n)时间完整重建(n=|X|)。为了构建S:

  • 放置子序列 P 1 P_1 P1在S开始:从索引0到书签A

  • 跟着n个空数组位置

  • 跟着子序列 P 2 P_2 P2,位于书签A和B之间

  • 跟着n个空数组位置

  • 跟着子序列 P 3 P_3 P3,从书签B到索引n-1

我们将保持“分隔”, P 1 , P 2 , P 3 P_1,P_2,P_3 P1,P2,P3连续存储在S,它们之间有非0数量的空数组槽。我们也用4个符号保存4个索引: a 1 a_1 a1指向 P 1 P_1 P1末尾, a 2 a_2 a2指向 P 2 P_2 P2开始, b 1 b_1 b1指向 P 2 P_2 P2结束, b 2 b_2 b2指向 P 3 P_3 P3开始。

  • 如果i<n, n 1 = ∣ P 1 ∣ = a 1 + 1 n_1=|P_1|=a_1+1 n1=P1=a1+1,那么页在P1,我们返回页 S[i]

  • 否则,如果 n 1 ≤ i ≤ n 1 + n 2 n_1\leq i\leq n_1+n_2 n1in1+n2 n 2 = ∣ P 2 ∣ = ( b 1 − a 2 + 1 ) n_2=|P_2|=(b_1-a_2+1) n2=P2=(b1a2+1),那么页在 P 2 P_2 P2,我们返回页 S [ a 2 + ( i − n 1 ) ] S[a_2+(i-n_1)] S[a2+(in1)]

  • 否则, i > a 1 + n 2 i\gt a_1+n_2 i>a1+n2,那么页在 P 3 P_3 P3,我们返回页 S [ b 2 + ( i − n 1 − n 2 ) ] S[b2+(i-n_1-n_2)] S[b2+(in1n2)]

只要存储索引的 a 1 , a 2 , b 1 , b 2 a_1,a_2,b_1,b_2 a1a2b1b2保存着,这个算法就会返回正确的页,并且某些数学操作、单数组索引查找,以最坏情形 O ( 1 ) \mathcal{O}(1) O(1)时间返回。

为了支持shift_mark(m, d),移动处于索引 ( a 1 , a 2 , b 1 , b 2 ) (a_1,a_2,b_1,b_2) (a1,a2,b1,b2)的页到索引位置 ( a 2 − 1 , a 1 + 1 , b 2 − 1 , b 1 + 1 ) (a_2-1,a_1+1,b_2-1,b_1+1) (a21,a1+1,b21,b1+1)。这个算法保证了数据结构,因此是正确的,并以 O ( 1 ) \mathcal{O}(1) O(1)时间运行,基于一个数组索引查找、一个索引写。注意,这个操作不会改变 P 1 、 P 2 、 P 3 P_1、P_2、P_3 P1P2P3之间的空间,因此操作的执行时间是最坏情形。

为了支持 move_page(m),移动索引 ( a 1 , b 1 ) (a_1,b_1) (a1,b1)处的相关页到索引位置 ( b 1 + 1 , a 1 + 1 ) (b_1+1,a_1+1) (b1+1,a1+1)。如果执行这个移动打破了隔断(比如 ( a 1 , a 2 ) (a_1,a_2) (a1,a2) ( b 1 , b 2 ) (b_1,b_2) (b1,b2)变得相邻),如同上面描述的,重建整个数据结构。这个算法保存了数据结构的不变性,因此是正确的。注意这个算法:重建时,两个相邻段之间的空间是关闭的;重建完后,两个相邻的段之间有n个空位置。最多每次move_page操作,两个相邻段之间的额外空间会改变。因此,这个操作每n次操作,花费 O ( n ) \mathcal{O}(n) O(n)时间,这个操作以分摊 O ( 1 ) \mathcal{O}(1) O(1)时间执行。

1-4 双向链表

lecture2中,我们描述了一个单向链表,在本问题中,你将实现一个双向链表,支持另外一些常量时间操作。双向链表的每个节点x持有一个x.prev,指向序列中它前面的节点,另外,x.next指向序列中它后面的节点。一个双向链表L持有一个指针L.tail,指向序列尾部。另外,L.head,指向序列首部。对于这个问题,双向链表没有保存它的长度。


(a)给定一个上面描述的双向链表,描述算法来实现下面的序列操作,每个 O ( 1 ) \mathcal{O}(1) O(1)时间

insert_first(x),insert_last(x),delete_first(),delete_last()

以下是支持请求操作的算法描述,这些算法每个都执行固定数量的任务,因此无需正确性论证。每个以 O ( 1 ) \mathcal{O}(1) O(1)时间重新链接常量的指针、构建一个单独的节点。

insert_first(x):构建一个新的双向链表节点a来存储x。如果双向链表是空的(例如,head和tail没有链接节点),那么把链表中的head和tail链接到a。否则,链表有一个头节点b,因此让a的next指针指向b,b的prev指针指向a,设置链表的head指向a。

insert_last(x):构建一个新的双向链表节点a来存储x。如果双向链表是空的(例如,head和tail没有链接节点),那么把链表中的head和tail链接到a。否则,链表有一个尾节点b,因此让a的prev指针指向b,b的next指针指向a,设置链表的tail指向a。

delete_first(x):这个方法,仅在链表至少包含一个节点时有意义,因此假设链表有一个头节点。从链表头,展开并存储项目x。然后设置head指向节点a(由head节点的next指针指向)。如果a不是一个节点,那么我们从链表中移除了最后一个项目,因此设置tail为None(head已经设为了None)。否则,设置新head的prev指针为None。然后返回x。

delete_last(x):这个方法,仅在链表至少包含一个节点时有意义,因此假设链表有一个尾节点。从链表尾,展开并存储项目x。然后设置tail指向节点a(由tail节点的prev指针指向)。如果a不是一个节点,那么我们从链表中移除了最后一个项目,因此设置head为None(tail已经设为了None)。否则,设置新tail的next指针为None。然后返回x。


(b) 给定双向链表L两个节点 x 1 和 x 2 x_1和x_2 x1x2 x 1 x_1 x1 x 2 x_2 x2之前,描述一个常量时间的算法来移除 x 1 x_1 x1 x 2 x_2 x2(包含)之间的所有节点,并将它们作为一个新的双向链表返回。

O ( 1 ) \mathcal{O}(1) O(1)时间构建一个新的空链表 L 2 L_2 L2,设置它的head和tail为 x 1 、 x 2 x_1、x_2 x1x2。展开这个子链表, x 1 x_1 x1为head、 x 2 x_2 x2为tail时必须注意。如果 x 1 x_1 x1是L的head,设置L的新head为节点a( x 2 x_2 x2的next指针指向的节点),否则,设置 x 1 x_1 x1的prev指向的节点,其next指针指向a。相似得,如果 x 2 x_2 x2是L的尾部,设置L的新尾部为节点b(由x1的prev指针指向的节点),否则,设置 x 2 x_2 x2的next指针指向的节点,其prev指针指向b。

这个算法直接从 x 1 x_1 x1 x 2 x_2 x2(包含)移除节点,因此是正确的,以 O ( 1 ) \mathcal{O}(1) O(1)时间重新链接常量个指针。

© 给定节点x来自双向链表 L 1 L_1 L1、双向链表 L 2 L_2 L2,描述一个常量时间算法,将 L 2 L_2 L2splice到 L 1 L_1 L1中。splice操作之后, L 1 L_1 L1应该包含之前在另外链表中的所有节点,并且 L 2 L_2 L2应该空的。

首先,让 x 1 x_1 x1 x 2 x_2 x2 L 2 L_2 L2的头和尾节点, x n x_n xn为x的next指针指向的节点(可能为None)。我们可以从 L 2 L_2 L2中移除所有节点,通过设置它的head和tail为None。然后splice节点,首先设置 x 1 x_1 x1的prev指针为x,并设x的next指针为 x 1 x_1 x1。相似得,设置 x 2 x_2 x2的next指针指向 x n x_n xn。如果 x n x_n xn是None,那么设置 x 2 x_2 x2为L的新tail;否则,设置 x n x_n xn的prev指针指向 x 2 x_2 x2

这个算法从 L 2 L_2 L2中直接插入节点到 L L L,因此是正确的,以 O ( 1 ) \mathcal{O}(1) O(1)时间执行,重新链接常量指针。

(d) 用提供的模板,实现Doubly_Linked_List_Seq类中的操作。不要更改Doubly_Linked_List_Node类。你可以从网站下载包含一些测试用例的代码模板。

class Doubly_Linked_List_Seq:
    def insert_first(self, x):
        new_node = Doubly_Linked_List_Node(x)
          if self.head is None:
            self.head = new_node
            self.tail = new_node
        else:
            new_node.next = self.head
            self.head.prev = new_node
            self.head = new_node

    def insert_last(self, x):
        new_node = Doubly_Linked_Node(x)
        if self.tail is None:
            self.head = new_node
            self.tail = new_node
        else:
            new_node.prev = self.tail
            self.tail.next = new_node
            self.tail = new_node
    
    def delete_first(self):
        assert self.head
        x = self.head.item
        self.head = self.head.next
        if self.head is None:    self.tail = None
        else:    self.head.prev = None
        return x
    
    def delete_last(self):
        assert self.tail
        x = self.tail.item
        self.tail = self.tail.prev
        if self.tail is None:    self.head = None
        else:    self.tail.next = None
        return x
    
    def remove(self, x1, x2):
        L2 = Doubly_Linked_List_Seq()
        L2.head = x1
        L2.tail = x2
        if x1 == self.head:    self.head = x2.next
        else:    x1.prev.next = x2.next
        if x2 == self.tail:    self.tail = x1.prev
        else:    x2.next.prev = x1.prev
        x1.prev = None
        x2.next = None
        return L2

    def splice(self, x, L2):
        xn = x.next
        x1 = L2.head
        x2 = L2.tail
        L2.head = None
        L2.tail = None
        x1.prev = x
        x.next = x1
        x2.next = xn
        if xn:    xn.prev = x2
        else:    self.tail = x2
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MIT 6.824 课程的 Lab1 是关于 Map 的实现,这里单介绍一下实现过程。 MapReduce 是一种布式计算模型,它可以用来处理大规模数据集。MapReduce 的核心想是将数据划分为多个块,每个块都可以在不同的节点上并行处理,然后将结果合并在一起。 在 Lab1 中,我们需要实现 MapReduce 的基本功能,包括 Map 函数、Reduce 函数、分区函数、排序函数以及对作业的整体控制等。 首先,我们需要实现 Map 函数。Map 函数会读取输入文件,并将其解析成一系列键值对。对于每个键值对,Map 函数会将其传递给用户定义的 Map 函数,生成一些新的键值对。这些新的键值对会被分派到不同的 Reduce 任务中,进行进一步的处理。 接着,我们需要实现 Reduce 函数。Reduce 函数接收到所有具有相同键的键值对,并将它们合并成一个结果。Reduce 函数将结果写入输出文件。 然后,我们需要实现分区函数和排序函数。分区函数将 Map 函数生成的键值对映射到不同的 Reduce 任务中。排序函数将键值对按键进行排序,确保同一键的所有值都被传递给同一个 Reduce 任务。 最后,我们需要实现整个作业的控制逻辑。这包括读取输入文件、调用 Map 函数、分区、排序、调用 Reduce 函数以及写入输出文件。 Lab1 的实现可以使用 Go 语言、Python 或者其他编程语言。我们可以使用本地文件系统或者分布式文件系统(比如 HDFS)来存储输入和输出文件。 总体来说,Lab1 是一个比较简单的 MapReduce 实现,但它奠定了 MapReduce 的基础,为后续的 Lab 提供了良好的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值