《算法导论》笔记(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
    评论
堆排序是一种基于堆数据结构的排序算法。堆是一种特殊的二叉树,具有以下性质: 1. 堆是一个完全二叉树,即除了最后一层外,其他层都是满的,并且最后一层的节点都尽量靠左排列。 2. 堆中的每个节点的值都大于等于(或小于等于)其子节点的值,这被称为堆的性质。 堆排序的基本思想是: 1. 构建一个最大堆(或最小堆),使得堆的根节点是堆中的最大(或最小)元素。 2. 将堆的根节点与堆的最后一个节点交换位置,然后将堆的大小减1。 3. 通过调用heapify函数,将新的根节点下沉到合适的位置,以维持堆的性质。 4. 重复步骤2和步骤3,直到堆的大小为1,排序完成。 以下是堆排序的示例代码: ```python def heapify(arr, n, i): largest = i left = 2 * i + 1 right = 2 * i + 2 if left < n and arr[i] < arr[left]: largest = left if right < n and arr[largest] < arr[right]: largest = right if largest != i: arr[i], arr[largest] = arr[largest], arr[i] heapify(arr, n, largest) def heapSort(arr): n = len(arr) for i in range(n // 2 - 1, -1, -1): heapify(arr, n, i) for i in range(n - 1, 0, -1): arr[i], arr[0] = arr[0], arr[i] heapify(arr, i, 0) arr = [12, 11, 13, 5, 6, 7] heapSort(arr) print("Sorted array is:", arr) ``` 这段代码首先构建一个最大堆,然后将堆的根节点与最后一个节点交换位置,并调用heapify函数来维持堆的性质。重复这个过程直到排序完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值