《算法导论》学习笔记之Chapter 2-2.1,2.2,2.3插入排序,选择排序,归并排序

来源:互联网 发布:cmd 关闭80端口 编辑:程序博客网 时间:2024/05/21 14:40

本来想写所谓的学习笔记,想来想去还是觉得直接记录自己在学习过程中编写的代码更靠谱。下面就闲话少说,参考文中给的伪代码,用自己熟悉的语言实现。

1:插入排序

public void InsertSort(int[] a) {for (int i = 1; i < a.length; i++) {int k = a[i];int j = i - 1;while (j >= 0 && a[j] >= k) {a[j + 1] = a[j];j--;}//此处为什么是j+1,不是j,需要注意:是因为j在while循环中已经先减去1了。可画图助于理解。a[j + 1] = k;}}

上边的代码,是按照升序排序。如果按照非升序排序,则修改a[j] > k 为 a[j] <= k

插入排序,最好的时间是O(n),此时数组已排好序;最坏的情况时间是O(n.^2),此时数组反向排序;综合来说,插入排序算法的时间复杂度为O(n.^2)

下面是一个习题:计算两个二进制数相加的函数:

public static void BinaryAdd(int[] a, int[] b) {int c[] = new int[5];int len = a.length;int flag = 0;for (int i = len - 1; i >= 0; i--) {int temp = a[i] + b[i] + flag;if (temp < 2) {c[i+1] = temp;flag = 0;} else {c[i+1] = 0;flag = 1;}}c[0] = flag;}

下面是习题2.2-2,考察选择排序:

public static void SelectSort(int[] a) {for (int i = 0; i < a.length - 1; i++) {int smallest = a[i];int k = i;for (int j = i + 1; j < a.length; j++) {if (a[j] <= smallest) {smallest = a[j];k = j;}}a[k] = a[i];a[i] = smallest;}}

我是按照递增顺序排列的,需要注意的是:

a[k] = a[i];a[i] = smallest;

我最初编写的时候,没注意,写成了:

a[i] = smallest;

a[k] = a[i];

虽然只是顺序反了,猛一看没啥区别,其实差别大了,你会丢失掉原来的a[i]值。关于2.2-3习题的线性查找问题,将其与二分查找相比较分析一下:线性查找 针对所有数组:有序,无序;二分查找主要针对有序数组;线性查找:查找次数平均为N/2.二分查找:查找次数在10位的数组中最大数为4(运气好的好,也许第一次就找到了)。基数小时,二者差别不明显;当基数增大时:线性查找次数将会成正比增长,K=N/2;而二分查找,我们通过一个公式来表达,K=㏒2(N),对数计算给出了二分查找法最大耗费的次数。那么N/2与㏒2(N)对比之下,差异性就显现了。
2.3节考察分而治之的算法:归并排序。我实现的代码如下:

public void MergeSort(int[] a, int p, int r) {if (p < r) {int q = (p + r) / 2;MergeSort(a, p, q);MergeSort(a, q + 1, r);Merge(a, p, q, r);}}public static void Merge(int[] a, int p, int q, int r) {int n1 = q - p + 1;int n2 = r - q;int[] L = new int[n1];int[] R = new int[n2];for (int i = 0; i < n1; i++) {L[i] = a[p + i];}for (int j = 0; j < n2; j++) {R[j] = a[q + j + 1];}int i = 0;int j = 0;int k = p;while (i < n1 && j < n2) {if (L[i] < R[j]) {a[k] = L[i];k++;i++;} else {a[k] = R[j];k++;j++;}}while (i < n1) {a[k] = L[i];k++;i++;}while (j < n2) {a[k] = R[j];k++;j++;}}

上面的代码,我觉得写得已经非常的清晰了,相信以后就算淡忘了,应该也能轻易看懂。其实,就是逐渐拆分大问题,然后分而治之,之后再合并小问题。其中,有些地方还可以简化编写形式,如:

a[k] = R[j];
k++;
j++;
可以直接简化为:a[k++] = R[j++];


分治算法的时间复杂度符合这个公式:
            T(n) = aT(n/b) + D(n) + C(n)
其中T(n/b)是被分解成a份,每份为n/b的子问题的求解时间; D(n)为分解问题的时间;C(n)为合并子问题的时间;

针对归并排序算法,最坏的运行情况:
分解仅仅计算子数组的中间值,所以D(n)=θ(1);
而合并过程则需要C(n)=θ(n)的时间;
中间解决所需时间为两个规模为n/2的子问题时间2T(n/2);
最后T(n) = 2T(n/2) + θ(n);  (θ(1)被忽略)

归并排序对n个数进行排序,分解树有logn+1层(从最顶层n个分解到最底层每个节点为1个),每一层代价为cn,所以,归并排序总的时间代价函数为cn*(logn+1), θ(nlogn)

参照这个过程,二分查找最坏的运行时间为θ(logn),分解过程是一样的,区别在于二分查找每次仅比较1个值;




 
阅读全文
0 0