倍道而行 :堆(heap)
概念
先来两个概念(别头疼):
普通队列:先进先出,后进后出
优先队列:出队顺序和入队顺序无关;和优先级相关。就好比:我们的电脑操作系统会按照各个进程的优先级来安排CPU执行哪一个进程。
堆(heap)被称为一种优先队列,但是堆的本质不是队列。取出顺序就是每次取最大的呗。
下图就是一个堆:
本质就是一个完全二叉树。完全二叉树的定义看图就可得出:父节点都大于等于子节点。且是从上大下,从左至右的排序。
堆的实现
看图:
仔细看每个数的下标。数组是从1开始的。父节点和子节点的关系也可以很容易得出。
eg:41是28和16的父节点,下标分别为2、4、5,满足图中的公式。
如题:有10个数,请用代码将之组成一个堆?
算法思想:每次往里面插入一个数,我们只需要比较插入的数和其父节点数的大小就行。
代码:
k指的是插入的序号。建议大家这样理解:
假设现在已经构建好了有10个数的堆,我再往里面插入一个数为102,即第11个数。那么k=11.然后与其父节点(k/2=5),即arr[5]进行比较。若大于父节点就交换位置,并让k/2=2,继续进行下去,至到和根结点进行比较。
void shiftUp(int k,int arr[]){ while (k>1 && arr[k/2]<arr[k]) { swap(arr[k/2], arr[k]); k /=2; } }
取堆里面的数:每次取都是取第一个位置的数,也就是最大的数。
如题:现在构建好了一个堆,那么我取走第一个数后,整个堆就需要进行调整,那该如何?
目前思想:(以本遍博客上面的例子为例,读者可以对照着看)
本题的:k代表开始的位置为1,j为子节点的位置(eg:指的是62这个根的左右两个子节点)。
- 1.取走62,我就让15代替62的位置。即,用最末尾的数字放到根节点上。
- 2.比较用15和62的两个左右节点进行比较,如果大就不做处理,否则交换位置并且,让k=j.
- 继续走第二步
void shiftDown(int k,int arr[],int count ){ while (2*k< count) { //确认有左子节点 int j = 2*k; if (j+1<count && arr[j+1] > arr[j]) { //如果有右节点 j+=1; } if (arr[k]>arr[j]) { 如果,比较发现字节点没有父节点大,就退出 break; } swap(arr[k], arr[j]);//否则就交换位置 k = j;//然后让k=j,继续进行比较。 } }
结尾:
堆排序算法还是很简单的。
建立堆,然后一个一个的取出,那么这就是一个从大到小的一个有序的序列。
但行好事,莫问前程。