算法导论从零单排

2016.11.30开始,每周两章。

</br>
2.1-1 以图2-2为模型,说明INSERTION-SORT在数组A = {31,41,59,26,41,58}上的执行过程

31,41,59

26,31,41,59 循环* 3

26,31,41,41,59 循环 * 1

26,31,41,41,58,59 循环* 1

</br>
2.1-2 重写过程INSERTION-SORT,使之按非升序(而不是非降序)排序

for j = 2 to A.length

{

  key = A[j];

  i = j - 1; //被比较数

  for( i > 0 and A[i] < key )

  {

    A[i + 1] = A[i];

    i = i - 1;

  }

  A[i + 1] = key;

}

区别仅仅在于内置for循环的大于号和小于号,排序均是从左到右插入排序,判断符号决定插入的位置

</br>
2.1-3 考虑以下查找问题:

输入: n个数的一个序列A = 和一个值v

输出:下标i使得v = A[i]或者当v不在A中出现时,v为特殊值NIL

写出线性查找的伪代码,它扫描整个序列来查找v。使用一个循环不变式来证明你的算法是正确的。确保你的循环不变式满足三条必要的性质。

for i = 1 to A.length

{

  if( i > A.length )

    return NIL;

  if( v = A[i] )

    return i;

}

</br>
2.1-4 考虑把两个n位二进制整数加起来的问题,这两个整数分别存储在两个n元数组A和B中。这两个整数的和应按二进制形式存储在一个(n+1)元数组C中。请给出该问题的形式化描述,并写出伪代码。

从右到左不断取ABC数组中的数字,进行判断,加入新数组D中,判断过程有8种情况,其中新数组C是用来存放进位的全零数组

for( i = A.length downto 0)

{

  if( i = 0 )

    D[i] = C[i];

  else

  {

    if( A[i] = 0, B[i] = 0, C[i] = 0)

      D[i] = 0;

    else if( 001 )

      D[i] = 1;

    else if( 010 )

      D[i] = 1;

    else if( 011 )
    {

      D[i] = 0;

      C[i - 1] = 1;

    }

    else if( 100 )

      D[i] = 1;

    else if( 101 )
    {

      D[i] = 0;

      C[i - 1] = 1;
    }

    else if( 110 )
    {

      D[i] = 0;

      C[i - 1] = 1;
    
    }

    else if( 111 )
    {

      D[i] = 1;

      C[i - 1] = 1;
    
    }

  }

}

</br>

2.2-1 用theta记号表示函数 n3/1000 - 100n² - 100n + 3.
</br>

theta n3

</br>

2.2-2 考虑排序存储在数组A中的n个数:首先找出A中的最小元素并将其与A【1】中的元素进行交换。接着,找出A中的次最小元素并将其与A【2】中的元素进行交换。对A的前n - 1个元素按该方式继续。该算法称为选择算法,写出其伪代码。该算法维持的循环不变式是什么?为什么他只要对前n - 1个元素,而不是对所有n个元素运行?用theta记号给出选择排序的最好情况与最坏情况的运行时间。
</br>

第一遍 n -1次比较

for j = 1 to A.length                      //c1     n + 1

{

//min = A[j];

  flag = j;                              //c2     n

  for( i = j + 1 to A.length )   //c3     n/2 * (n + 1)

  {

    if( A[j] > A[i] )           //c4     (α - 1) * (n + 1)

     //min = A[i];

    flag = i;         //c5     α/2 * (n + 1)

  } //now  I find the min

  temp = A[j];                       //c6     n

  A[j] = A[flag];                     //c7     n

  A[flag] = temp;//swap       //c8     n

}

循环不变式:

初始化:初始数组为空 成立

保持:每次放入的都是剩下的最小的元素 成立

终止:归纳

因为最后一个比所有其他元素都大 所以已经被置换到最后

运行时间T(n) = c1 * (n + 1) + c2 * n + c3 * n /2 * (n + 1) + c4 * (α - 1) * (n + 1) + c5 * α/2 * (n + 1) + c6 * n + c7 * n + c8 * n

theta n²

</br>

2.2-3 再次考虑线性查找问题。假定要查找的元素等可能的为数组中的任何元素,平均需要检查输入序列的多少元素?最坏情况又如何呢?用theta记号给出线性查找的平均情况和最坏情况运行时间。证明你的答案。
</br>

平均需要检查输入序列的一半。最坏情况即为检查所有输入的元素。

theta2/n theta n

因为输入等可能的为数组中的任何元素,所以期望为一半。最坏情况即为,给出的输入在序列的最后一个,或者无法找到。

</br>

2.2-4 应如何修改任何一个算法,才能使之具有良好的最好情况运行时间?
</br>
尽量避免出现遍历的循环,这样运行时间会出现n²及以上的量级。应该使用分组递归的方法。

</br>

2.3-1 使用图2-4作为模型,说明归并排序在数组A = {3,41,52,26,38,57,9,49}上的操作。
</br>

1.逐层分组

3,41,52,26 38,57,9,49

3,41 52,26 38,57 9,49

2.排序

3,41 26,52 38,57 9,49

3.归并

3,26,41,52 9,38,49,57

3,9,26,38,41,52,57

</br>

2.3-2 重写过程MERGE,使之不使用哨兵,而是一旦数组L或R的所有元素均被复制回A就立刻停止,然后把另一个数组的剩余部分复制回A。
</br>

因为我们知道从p到q一共多少个值,所以使用哨兵后我们就无需在取数之前判断数组是否已取完。如果不使用哨兵,则需要判断EOF。我们知道循环次数的信息无法用上,有点浪费。

//先将数组放置到两个新建的空数组内

//原数组为A[]数组中从p到r的一个数组,A【q】为A【p】 A【r】中的一个数

n1 = q - p + 1

n2 = r - q

let L[n1] and R[n2] be new array

for i = 1 to n1

{

L[i] = A[p + i - 1]

}

for i = 1 to n2

{

R[i] = A[q + i]

}

//分配好两个小数组

i = 1 //初始化i和j

j = 1

for k = p to r   //新数组长度

{

if( L[i] < R[j])

{

A[k] = L[i]

i = i + 1

while(L[i] = EOF)

{

k = k + 1

A[k] = R[j]

j = j + 1

}

}

else

{

A[k] = R[j]

j = j + 1

while(R[j] = EOF)

{

k = k + 1

A[k] = L[i]

i = i + 1

}

}

}

//EOF代指判断数组末尾方法

</br>
2.3-3 使用数学归纳法证明:当n刚好是2的幂时,以下递归式的解是T(n) = nlgn。
</br>

T(n) = 2 若 n = 2

2T(n/2) + n 若 n = 2的k次方, k > 1

当k = 1的时候,可以分成两个长度为1的递归式,运行时间是nlgn

当k + 1的时候,相当于原来的两个式子递归为一个两倍长的式子,运行时间为(n+1)lg(n+1)

成立

</br>

2.3-4 我们可以把插入排序表示为如下的一个递归过程。为了排序A[1..n],我们递归地排序A[1..n - 1],然后把A[n]插入已排序的数组A[1..n - 1]。为插入排序的这个递归版本的最坏情况运行时间写一个递归式。
</br>

recursion(A,1,n)

{

if(n = 2)

{

if(A[2] > A[1])

return true;

else

{

tem = A[2]

A[2] = A[1]

A[1] = tem

return true;

}

}

if(n > 2)

{

recursion(A,1,n - 1)

j = n - 1

for(j > 0 and A[n] < A[j])

{

A[j + 1] = A[j] //向右移动一位

j = j - 1

}

A[j] = A[n]

return true;

}

}

</br>

2.3-5 回顾查找问题,注意到,如果序列A已排好序,就可以将该序列的中点与v进行比较。根据比较的结果,原序列中有一半就可以不用再作进一步的考虑了。二分查找算法重复这个过程,每次都将序列剩余部分的规模减半。为二分查找写出迭代或递归的伪代码。证明:二分查找的最坏情况运行时间为theta lgn

</br>

for(i = 1 to n)

{

min = 1

max = n

middle = (min + max)/2  //截断小数,不存在小数问题

if(key > A[middle])

{

min = A[middle]

if(max == min + 1)

return NIL

middle = (min + max)/2

}

else if(key < A[middle])

{

max = A[middle]

if(max == min + 1)

return NIL

middle = (min + max)/2

}

else

return A[middle]

}

1 to n

1 to n /2

1 to n /4

.

.

.

深度为lgn

</br>

2.3-6 注意到2.1节中的过程INSERTION-SORT的第5~7行的while循环采用一种线性查找来(反向)扫描已排好序的子数组A[1..j - 1]。我们可以使用二分查找来把插入排序的最坏情况改进到theta nlgn吗?
</br>

for i = 2 to n

{

key = A[i]

j = i - 1

//接下来进入二分查找

min = 1

max = i

middle = (min + max) / 2

if(A[middle] > key)

{

max = middle

if(max == min + 1)

{

for(i = n downto max)

A[n+1] = A[n]

A[max] = key

}

middle = (min + max) / 2

}

else if(A[middle] < key)

{

min = middle

if(max == min + 1)

{

for(i = n dowmto max)

A[n+1] = A[n]

A[max] = key

}

middle = (min + max) / 2

}

else

{

for(i = n dowmto midlle)

A[n+1] = A[n]

A[middle] = key

}

//放置数字的时候 将每个数字后移一位 复杂度仍然是n

//查找的复杂度从n变为lgn

//所以时间复杂度为 (lgn + n)*n = n²

}

</br>

2.3-7 描述一个运行时间为theta(nlgn)的算法,给定n个整数的集合S和另一个整数x,该算法能确定S中是否存在两个其和刚好为X的元素。
</br>

二分排序,时间复杂度为nlgn

排序好之后每个元素遍历一边 复杂度n,遍历时二分查找X - A[n]的值,复杂度为lgn

nlgn + nlgn = nlgn

</br>

Q2.1虽然归并排序的最坏情况运行时间为theta nlgn,而插入排序的最坏情况运行时间为theta n²,但是插入排序中的常量因子可能使得它在n较小时,在许多机器上实际运行的更快。因此,在归并排序中当子问题变得足够小时,采用插入排序来使递归的叶变粗是有意义的。考虑对归并排序的一种修改,其中使用插入排序来排序长度为k的n/k个子表,然后使用标准的合并机制来合并这些子表,这里k是一个待定的值。
</br>

a.证明:插入排序最坏情况可以在theta nk时间内排序每个长度为k的 n/k个子表

</br>
排序一个长度为k的子表所用时间为 k²,排序n/k个所用时间为theta nk
</br>
b.表明在最坏情况下如何在theta nlg(n/k)时间内合并这些子表
</br>
待合并的子表一共有n/k个,一共排序lg(n/k)次,每次排n个(每次排序n/k个,排n/2k次,每个2k个),所以theta = nlg(n/k)
</br>
k越大合并越快,但是排序慢
</br>
c.假定修改后的算法的最坏情况运行时间为theta (nk + nlg(n/k)),要使修改后的算法与标准的归并排序具有相同的运行时间,作为n的一个函数,借助theta记号,k的最大值是什么?
</br>
theta(nk + nlg(n/k)) = theta(nlgn)

k + lg(n/k) = lgn

k < lgn

//当k大于lgn后,修改后的算法复杂度上升。
</br>
d.在实践中,我们应该如何选择k?
</br>
综合排序和归并的算法效率,选取k + lg(n/k)取最小值时的k值,因为实际上这两项前还有别的系数,所以需要根据实际情况确定。

</br>
Q2-2 冒泡排序是一种流行但低效的排序算法,它的作用是反复交换相邻的未按次序排列的元素。
</br>

BUBBLESORT(A)

for i = 1 to A.length - 1

for j = A.length downto i + 1

if A[j] < A[j - 1]

exchange A[j] with A[j - 1]

</br>
a.假设A’表示BUBBLESORT(A)的输出。为了证明BUBBLESORT正确,我们必须证明它将终止并且有:

A’[1] <= A'[2] <= A’[3] … A’[n]

其中n = A.length。为了证明BUBBLRSORT确实完成了排序,我们还需要证明什么?
</br>
原先数组未排序 and 新数组中数据全部来自A
</br>
下面两部分将证明不等式

b.为第2~4行的for循环精确地说明一个循环不变式,并证明该循环不变式成立。你的证明应该使用本章中给出的循环不变 式证明的结构。

初始化:对于第一个排序的数字A’[1],它是数组中所有元素遍历一遍之后选出的最小项

保持:两两比较后,较大的向左移动,所以在剩下的元素中,每次抵达左端的都是“剩下数组”中最小的数

终止:当i = A.length - 1时,只剩两个元素需要比较,比较后排序即完成。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容