《算法导论》笔记(4)堆排序与快速排序 含部分习题
2015-02-04 14:53
417 查看
堆排序。堆是一个不需要指针的二叉树,不需要指针是因为对下标运算得到父、左、右三元素。不用指针的好处是省空间,交换上下元素时运算更简单,另外便于迅速定位父、子结点,构建时快速遍历同层结点以及上一层结点。但因为不用指针,需要队列(deque或vector)来储存数据,以满足动态进出的要求。而对于实际的应用场景,随着堆的增长,申请越来越大的连续内存空间,一定是要跨区域的,其实还是牵涉了指针的运算,只是封装在底层。
Parent(); Left(); Right(); exchange();四个函数简单明了略过不提。
优先队列是堆的一种应用。一种更复杂的应用方式是,分组的优先队列,某些操作系统的任务管理有应用。个人感觉另外一种有趣的应用方式是,每个结点是一组FIFO队列而不是单个元素,保证大致公平而优化效率。
注意,堆的构建时间是O(n),堆排序时间是O(n*lg(n)),因为构建时是每层元素个数乘层高求和,而排序时,每个元素都是放到最高点,是元素乘总层高求和。
习题6.5-9k个有序链表的合并
思考题6-1插入方式建堆和直接排序建堆的区别。插入方式最坏是nlg(n)的复杂度。为什么有这个区别?直接建堆是从底层建起,最多的元素比较最少的次数(层数),而插入方式是最多的元素比较最多的次数,越往后层数越高。
思考题6-2 D叉堆。改变left(),right(),parent()的具体实现。Heapify时比较全部子结点。伪码略。
思考题6-3Young矩阵,优点是同时提供最小最大元素。搜索元素方法,先找到行数,然后在对角线上搜索。小则向上,大则向右。插入元素方法,第一列插入,后面元素下移一位,最后多一位插入最后一行第二列,后续按行顺移一位。
快速排序。需要两个index游标,一个顺序向后移动比较每个元素,一个标明分界点位置。算法的效率依赖于元素的初始排列。为使算法效率更均衡,引入了随机抽样。用以比较各元素的主元是随机抽取的。
习题7.2-6,随机输入数组,选择的主元平均分布,比(1-a):a更公平的划分的概率为2*(1/2-a)=1-2a
习题7.4-6,三个数,需要两个数分别落入(0,a)与(a,1)区间,故归一化后概率密度为6a(1-a)。
思考题7-1,两个游标一个从头向后移,一个从尾向前移,如果有分划错误的元素则停止,两个游标停止时同时交换,并继续前进。保证了游标划过的地方都满足与主元的比较关系。
思考题7-4,尾递归优化。前半部用递归,后半部改为循环。展开时的栈深度只与每次展开的前半部有关。所以如果每次partition拆分时前半部都有N-1个元素,后半部只有一个元素,栈深入为N。那么展开小的部分,循环大的部分就可以了
保证了每次展开都是最短的子列。那么最差情况下,栈深度为lg(n)。
Parent(); Left(); Right(); exchange();四个函数简单明了略过不提。
Max_heapify(A,i){ if i>A.length/2 return; max=find_max_in3(A[i],left(),right()); if(max!=i) { exchange(A[i],A[max]); Max_heapify(A,max); } } Build_MaxHeap(A){ for each i=A.length/2 downto 1 Max_heapify(A,i);} Heap_sort(){ for i= A.length down to 1 { exchange(A[1],A[length]); pop(A[length]);heapify(A,1); } }
优先队列是堆的一种应用。一种更复杂的应用方式是,分组的优先队列,某些操作系统的任务管理有应用。个人感觉另外一种有趣的应用方式是,每个结点是一组FIFO队列而不是单个元素,保证大致公平而优化效率。
Insert(A,x){ length+=1;A[length]=x;build_MaxHeap(); } Extract(A){ exchange(A[1],A[length]);pop(A[length]);heapify(A,1); } change_key(A,i,x){ if(x<A[i]){heapify(A,i)}; while(x>A[i].parent()and A[i].parent()!=null ) { exchange(A[i], A[i].parent()); i=A[i].parent().index(); } }
注意,堆的构建时间是O(n),堆排序时间是O(n*lg(n)),因为构建时是每层元素个数乘层高求和,而排序时,每个元素都是放到最高点,是元素乘总层高求和。
习题6.5-9k个有序链表的合并
combine(A1…Ak){ B=build_Min_Heap(A1[1]…Ak[1]); while(A[1]…A[k] not empty){C[i]=extract(B); i++} } extract(B){ out=B[1]; Ai= B[1].belong(); while (Ai is empty){ Ai=Ai.next_list(); } exchange(B[1],Ai[1]); Ai.pop(1); heapify(B,1); return out; }
思考题6-1插入方式建堆和直接排序建堆的区别。插入方式最坏是nlg(n)的复杂度。为什么有这个区别?直接建堆是从底层建起,最多的元素比较最少的次数(层数),而插入方式是最多的元素比较最多的次数,越往后层数越高。
思考题6-2 D叉堆。改变left(),right(),parent()的具体实现。Heapify时比较全部子结点。伪码略。
思考题6-3Young矩阵,优点是同时提供最小最大元素。搜索元素方法,先找到行数,然后在对角线上搜索。小则向上,大则向右。插入元素方法,第一列插入,后面元素下移一位,最后多一位插入最后一行第二列,后续按行顺移一位。
快速排序。需要两个index游标,一个顺序向后移动比较每个元素,一个标明分界点位置。算法的效率依赖于元素的初始排列。为使算法效率更均衡,引入了随机抽样。用以比较各元素的主元是随机抽取的。
quick_sort(A,p,r){ if(p==r ) return ; if(p+1==r) return compare_exchange(p,r); q=partition(A, p, r); quick_sort(A,p,q); quick_sort(A,q+1,r); } partition(A,p,r){ v=random_choose(A,p,r); for (i=p to r) { if(A[i]<v){ q++; exchange(A[q],A[i]);} return q; }
习题7.2-6,随机输入数组,选择的主元平均分布,比(1-a):a更公平的划分的概率为2*(1/2-a)=1-2a
习题7.4-6,三个数,需要两个数分别落入(0,a)与(a,1)区间,故归一化后概率密度为6a(1-a)。
思考题7-1,两个游标一个从头向后移,一个从尾向前移,如果有分划错误的元素则停止,两个游标停止时同时交换,并继续前进。保证了游标划过的地方都满足与主元的比较关系。
思考题7-4,尾递归优化。前半部用递归,后半部改为循环。展开时的栈深度只与每次展开的前半部有关。所以如果每次partition拆分时前半部都有N-1个元素,后半部只有一个元素,栈深入为N。那么展开小的部分,循环大的部分就可以了
Tail_recursive_quicksort(A,p,r){ while p<r { q=partition(A,p,r); if(q<(r-p)/2){ tail_recursive_quicksort(A,p,q); p=q+1; } else{ tail_recursive_quicksort(A,q,r); r=q+1; } }
保证了每次展开都是最短的子列。那么最差情况下,栈深度为lg(n)。
相关文章推荐
- 《算法导论》笔记(1)排序 含部分习题
- 算法导论学习笔记(五):快速排序
- 《算法导论》笔记 第7章 7.1快速排序的描述
- 《算法导论》笔记(14) 基本的图算法 部分习题
- 算法导论学习笔记(一)快速排序及优化
- 《算法导论》笔记(18) 最大流 含部分习题
- 算法导论笔记--3--快速排序
- 《算法导论》笔记(15) 最小生成树 部分习题
- 《算法导论》笔记(9) 动态规划 部分习题
- 《算法导论》第7章 快速排序 个人笔记
- 算法学习笔记----第二部分:排序和顺序统计量----第6章、堆排序
- 《算法导论》笔记 第7章 7.3快速排序的随机化版本
- 《算法导论》笔记 第7章 7.1快速排序的描述
- 《算法导论》笔记(16) 单源最短路径 部分习题
- 算法导论笔记之快速排序
- 练习《算法导论》之排序:插入排序,归并排序,堆排序,快速排序
- 《算法导论》笔记 第7章 7.2快速排序的性能
- 《算法导论》笔记 第7章 7.3快速排序的随机化版本
- 算法导论笔记:07快速排序
- 《算法导论》笔记(17) 所有结点对最短路径 部分习题