@IDE: Eclipse
@Language: Java
@Quote:https://www.cnblogs.com/DSNFZ/articles/7745785.html && Cornell CS2110
上完算法结构以后很久没复习sorting,现在重新整理总结一下:
(1)Merge Sort
看了一篇博主的文章后才明确我之前学的mergesort原来算是 top-down mergesort
根本概念是用recursion的思想去一层一层往下走,可以分解为以下步骤:
- 将无序数列一层层分解,直到size(list) <2
- 将分解后的数列两两merge, 直到得到原始的数列的长度
如图
用算法实现如下:
public void mergesort(int[] b,int h,int k) {//in place
if(b.length < 2) return ;
int t = (h+k)/2;
mergesort(b,h,t);
mergesort(b,t+1,k);
merge(b,h,t,k);
}
public void merge(int[] b, int h, int t,int k) {
int i=0; //pointer for left part
int u=0;
int v=t+1;//pointer for right part
int[] c = new int[t-h];
System.arraycopy(b, h, c, 0, t-h);//copy the left part
while(i<t-h) {
if(v<k && b[v] < c[i]) {//right < left
b[u]=b[v];
u++;v++;
}else {
b[u]=c[i];
u++;i++;
}
}
}
比较难的是分析时间复杂度和空间复杂度,下面是我自己的理解,如果有理解不对的地方希望得到指证:
(1) Space Complexity:如果以代码中的实现方式来看的话,每次merge的时候都会新建一个二分之一子数组size的新数组,所以space complexity is O(N);
【注意 mergesort 和merge 的过程都是in place】
(2)Time Complexity: 在分析这个问题的时候主要看比较的次数,考虑元素比较次数,两个长度分别为m和n的有序数组,每次比较处理一个元素,因而合并的最多比较次数为(m+n-1),最少比较次数为min(m,n);
【PS: 因为每次两个两个比较之后都会产生一个新的最小的元素,下一步就是将稍微大一点的元素跟其他元素去比较,最后一次比较的时候,只剩一个数未放入新数组,于是直接放入,不比较,于是是m+n-1】
在理解这个的时候借鉴的是引用的博主的文章的思想,摘抄如下:
归并的基本思想是合并多路有序数组,通常我们考虑两路归并算法。
归并排序是稳定的,这是因为,在进行多路归并时,如果各个序列当前元素均相等,可以令排在前面的子序列的元素先处理,不会打乱相等元素的顺序。
考虑元素比较次数,两个长度分别为m和n的有序数组,每次比较处理一个元素,因而合并的最多比较次数为(m+n-1),最少比较次数为min(m,n)。对于两路归并,序列都是两两合并的。不妨假设元素个数为n=2^h,
第一次归并:合并两个长度为1的数组,总共有n/2个合并,比较次数为n/2。
第二次归并:合并两个长度为2的数组,最少比较次数是2,最多是3,总共有n/4次,比较次数是(2~3)n/4。
第三次归并:合并两个长度为4的数组,最少比较次数是4,最多是7,总共有n/8次合并,比较次数是(4-7)n/8。
第k次归并:合并两个长度为2^(k-1)的数组,最少比较次数是2^(k-1),最多是2^k-1,总共合并n/(2^k)次,比较次数是[2^(k-1)~(2^k-1)](n/2^k)=n/2~n(1-1/2^k)。
按照这样的思路,可以估算比较次数下界为n/2*h=nlg(n)/2。上界为n[h-(1/2+1/4+1/8+...+1/2^h)]=n[h-(1-1/2^h)]=nlog2(n)-n+1。
综上所述,归并排序比较次数为nlgn-n+1~nlog2(n)/2。
归并排序引入了一个与初始序列长度相同的数组来存储合并后的结果,因而不涉及交换。
在具体实现练习这个用法的时候,遇到了leetcode 148,算是很好的练习了。
(2) Quick Sort
这个方法的根本思想是:
- 找到pivot, 然后根据pivot将小于它的数字放在左边,大于它的数字放在右边,从而会形成两个新的队列;
- 对这个队列做以上重复的操作直至最后队列里的数字为1个。
要注意的是形成的两个新的队列里面的元素不要求是sorted。
用下图表示更直观:
所以这个算法会由两个函数 + recursion 构成:
- Partition
- QuickSort
伪代码如下:
比较重要的就是怎么实现 partition:
当时学算法的时候用的就是以下这种思路,j代表开始的起点,k代表末尾,如果j+1 处的元素值比j 处的元素值要大就放在左边,反之放在右边。
Pivot的选择往往是 start or end or middle
但是比较好的是取中位数:median of first, middle, last, values (median of first, middle, last values)
比如选择start 作为 pivot :
- Time complexity:
Worst case -- 这个数组已经排好序了,那么选择start 或者 end的话就需要n轮recursion, 每次涉及到n次比较,结果是O(n*n);
Average case – Depth of recursion is logn, and time to partition on each level is O(n), the total time is O(n log n).
- Space complexity:
Worst case – O(n) because of the depth of recursion is O(n)
Average case – O(logn)