我是靠谱客的博主 笑点低宝贝,最近开发中收集的这篇文章主要介绍Mit6.006-problemSet011-1 函数的渐近性(asymptotic behavior)1-2 给定一个支持序列操作的数据结构D1-3 Binder Bookmarks1-4 双向链表,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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{2pi 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^{7sqrt n},f_3=4^{3nlog 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}(klog 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 } min{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 } min{A,B} m{A,B},当前在索引为 i 的页前面,移动它到索引为 i+d 的页前面, d ∈ { − 1 , 1 } din{-1,1} d{1,1},以 O ( 1 ) mathcal{O}(1) O(1)时间


move_page(m)

获取当前处于书签 m ∈ { A , B } min{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_1leq ileq 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 igt 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

最后

以上就是笑点低宝贝为你收集整理的Mit6.006-problemSet011-1 函数的渐近性(asymptotic behavior)1-2 给定一个支持序列操作的数据结构D1-3 Binder Bookmarks1-4 双向链表的全部内容,希望文章能够帮你解决Mit6.006-problemSet011-1 函数的渐近性(asymptotic behavior)1-2 给定一个支持序列操作的数据结构D1-3 Binder Bookmarks1-4 双向链表所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(62)

评论列表共有 0 条评论

立即
投稿
返回
顶部