《算法导论》笔记(4)堆排序与快速排序 含部分习题

堆排序。堆是一个不需要指针的二叉树,不需要指针是因为对下标运算得到父、左、右三元素。不用指针的好处是省空间,交换上下元素时运算更简单,另外便于迅速定位父、子结点,构建时快速遍历同层结点以及上一层结点。但因为不用指针,需要队列(deque或vector)来储存数据,以满足动态进出的要求。而对于实际的应用场景,随着堆的增长,申请越来越大的连续内存空间,一定是要跨区域的,其实还是牵涉了指针的运算,只是封装在底层。

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)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值