算法和数据结构(16)堆排序

优先队列1

之前的堆中有提及过优先队列,链接:https://blog.csdn.net/qq_32193775/article/details/104115268

前言

许多应用程序都需要处理有序的元素,但不一定要求它们全部有序,例如,绝大多数手机分配给来电的优先级都会比游戏程序的高;

在这种情况下一个合适的数据结构应该支持两种操作,删除最大元素和插入元素,这种数据类型叫做优先队列,优先队列的使用和队列(删除最老的元素)以及栈(删除最新的元素)类似,但是它需要有个权值,高效的实现它更有挑战;

重要操作:

  1. delMax() 删除最大元素
  2. insert() 插入元素
  3. less() 辅助函数,比较大小

api:

MaxPQ() 创建优先队列
MaxPQ(int max) 创建初始容量为max的优先队列
MaxPQ(Key[] a) 用a[] 的元素创建一个优先队列
void insert(Key k) 优先队列中插入元素
Key max() 返回最大元素
Key delMax() 删除最大元素
Boolean isEmpty() 返回队列是否为空
int size() 返回优先队列中元素个数

同时也存在delMin(),MinPQ()等方法;

初级实现

  1. 数组实现(无序):最简单的方式,基于栈,要实现删除最大元素,可以添加一段类似于选择排序的内循环代码,将最大元素与边界元素交换,然后删除它,和栈中pop()类似;
  2. 数组实现(有序):insert()中添加代码,将所有较大的元素向右移动一格,以使数组保持有序(插入排序类似),这样,最大的元素在数组的一端,删除最大的元素就和pop()类似了;

堆实现

数据结构二叉堆能够很好的实现优先队列的基本操作;

复习:堆有序:一颗二叉树,每个结点都大于等于它的两个子结点

完全二叉树可以用数组表示,具体方法如:根结点在index=1,它的子结点在2,3处,子结点的子结点在位置4,5,6,7处;

故,位置k的结点的父结点为k/2处,其子结点分别在2k,2k+1处;

堆的算法

我们用长度为N+1的数组pq[]来表示一个大小为N的堆,我们不会使用pq[0],重要的操作就是如何实现这个堆的有序化;下列代码为辅助函数

private boolean less(int i, int j){
  return pq[i].compareTo(pq[j])<0;
}
private void exch(int i,int j){
  Key t = qp[i];
  qp[i] = qp[j];
  qp[j] = t;
}

由下至上的堆有序化(上浮)

如果堆的有序状态,因为某个结点变得比它的父结点更大而被打破,那么就需要交换它和它的父结点来修复堆;

即判断它和k/2的大小,若大,则交换;但这个结点依然可能比新的父结点大,需要循环;

代码如下:

private void swim(int k){
  while(k>1&&less(k/2,k)){
    exch(k/2,k);
    k=k/2;
  }
}

由上至下的堆有序(下沉)

同上浮,因为某个结点变得比它的两个子结点或其中之一更小而被打破了;那么需要同上浮方法来修复堆;代码如下:

private void sink(int k){
  while(2*k<=N){//N为当前优先队列的大小
    int j = 2*k;
    if(j<N&&less(j,j+1)) j++;//找到两个子结点中的最大值;
    if(!less(k,j)) break;
    exch(k,j);
    k=j;
  }
}

基于堆(数组表示,完全二叉树)的优先队列

public class MaxPQ<Key extends Comparable<Key>>{
  private Key[] pq;
  private int N=0;//size
  
  public MaxPQ(int max){
    pq=(Key[]) new Comparable[max+1];
  }
  public int size(){
    return N;
  }
  public void insert(Key v){
    pq[++N]=v;
    swim(N);
  }
  public Key delMax(){
    Key max = pq[1];
    excha(1,N--);
    pq[N+1] = null;
    sink(1);
    return max;
  }
  //注意其他方法见前述;
}

堆排序

我们

可以把任意优先队列变成一种排序方法,将所有元素插入一个查找最小元素的优先队列;随后重复调用删除最小元素的操作来将它们按顺序删除(同样也可以删除最大元素),用基于堆的优先队列这样做是一种全新的排序方法吗----堆排序;

为保证和上述代码一致,所有堆排序采用删除最大元素来进行;

代码如下:

public static void sort(Comparable[] a){
  int N = a.length;
  for(int k = N/2 ; k>=1;k--){
    sink(a,k,N);
  }
  while(N>1){
    exch(a,1,N--);
    sink(a,1,N);
  }
}

代码解释:

exch和sink方法稍有改动,

for循环中将该数组做到堆有序;

随后可以已知,根结点为所有元素最大值,故需要先将根结点和末尾交换位置,根结点固定了,n-1长度的数组不有序,所有要下沉(因根结点为小值导致);故而调用sink方法;

从而达到了排序的效果;

时间复杂度:2logN;

发布了17 篇原创文章 · 获赞 0 · 访问量 352

猜你喜欢

转载自blog.csdn.net/qq_32193775/article/details/104226724