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