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

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

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
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值