文章目录
1. 优先队列概念
可以看出是一个有序的队列,每次出队的都是队列中最大(最小)的元素,新加的元素入队,队列中的元素依然保持有序
2. 优先队列的几种实现方式比较
3. 堆和二叉堆
3.1 堆的概念
- 一棵具有特定性质的树
- 结点的值大于等于其孩子节点的值或小于等于
- 大于等于–>大顶堆
- 小于等于—>小顶堆
- 堆是一棵完全树,叶子节点只会位于第h层和第h-1层
3.2 二叉堆
- 在堆的基础上,每个节点最多只能有2个子节点
- 二叉堆是一棵完全二叉树,因此可以用数组来表示
对于一棵完全二叉树,如果使用数组来表示的话,对于第i
个位置的节点而言,其父节点在第(i-1)/2
位置上,其左子节点在2*i+1
位置上(如果有左子节点),其右子节点在2*i+2
位置上(如果有右子节点)
3.3 堆的下沉操作
以大顶堆为序,下面的代码记作根节点的位置是1,即创建的数组大小是N+1,下标0的位置空出,这种情况下:对于第i
个位置的节点而言,其父节点在第i/2
位置上,其左子节点在2*i
位置上(如果有左子节点),其右子节点在2*i+1
位置上(如果有右子节点)
如果某个根节点<子节点的值,则交换该子节点与父节点的值,重复这个过程,直到遇到一个小于等于根节点值的子节点
public void sink(int arr[],int k)
{
int N=arr.length;
while(2*k<=N)
{
int j=2*k;//指向左子节点
//根节点>=max(左子节点,右子节点)
if(j<N&&arr[j]<arr[j+1])//如果右子节点存在且右子节点比左子节点大
j++;//j指向右子节点
if(arr[k]>=arr[j])//找到<=根节点的子节点 停止
break;
swap(arr, k, j);
k=j;//k指向交换后的位置
}
}
public void swap(int arr[],int i,int j)
{
int tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
上面的代码实现的是堆中的下沉操作,从上到下调整堆的结构
3.4 堆的上浮操作
如果某个子节点大于根节点的值,则交换该子节点与父节点的值,重复这个过程,直到遇到一个大于等于其值的父节点
public void swim(int arr[],int k)
{
while(k>1&&arr[k/2]<arr[k])//a[k/2]:父节点 arr[k]:当前节点
{
swap(arr,k,k/2);
k=k/2;//注意这里的k每次倍减 指向上一个父节点
}
}
public void swap(int arr[],int i,int j)
{
int tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
从下到上调整堆的结构
有了上浮和下沉操作之后,在堆中添加和删除元素就容易了
3.5 在堆中添加一个元素
将元素加到数组末尾,然后增加堆的大小,并将新添加的元素上浮到合适的位置即可,调用swim方法
3.6 删除堆中的最大元素
交换堆顶元素(数组第1个元素)和数组最后一个元素实现,然后将新的堆顶元素下沉(调用sink方法
)到合适的位置,将堆的大小-1(大小减一相当于删除了最后1个元素,因为已经交换,所以最后一个元素就是原来的堆顶最大元素)
3.7 堆的完整代码实现
public class PriorityQueueDemo {
int pq[];
int size=0;
public static void main(String[] args) {
PriorityQueueDemo pq=new PriorityQueueDemo(5);
pq.insert(1);
pq.insert(2);
pq.insert(3);
pq.insert(4);
pq.insert(5);
pq.deleteMax();
pq.print();
}
public PriorityQueueDemo(int N)
{
pq=new int[N+1];
}
public boolean isEmpty()
{
return size==0;
}
public int size()
{
return size;
}
public void insert(int val)
{
pq[++size]=val;
swim(size);
}
public void deleteMax()
{
swap(1, size);
pq[size]=-1;
size--;
sink(1);
}
public void swim(int k)
{
while(k>1&&pq[k/2]<pq[k])//a[k/2]:父节点 arr[k]:当前节点
{
swap(k,k/2);
k=k/2;
}
}
public void sink(int k)
{
int N=pq.length;
while(2*k<=N)
{
int j=2*k;//指向左子节点
//根节点>=max(左子节点,右子节点)
if(j<N&&pq[j]<pq[j+1])//如果右子节点存在且右子节点比左子节点大
j++;//j指向右子节点
if(pq[k]>=pq[j])//找到<=根节点的子节点 停止
break;
swap(k, j);
k=j;
}
}
public void swap(int i,int j)
{
int tmp=pq[i];
pq[i]=pq[j];
pq[j]=tmp;
}
public void print()
{
for(int i=1;i<=size;i++)
System.out.print(pq[i]);
System.out.println();
}
}
4. 堆排序
堆排序可以分为以下两个阶段:
- 堆的构造阶段:将原始数组元素加入到1个堆中
- 下沉排序阶段:按递减(递增)顺序取出所有元素并得到排序结果(每次都是取堆顶元素,因此需要下沉,添加–上浮 删除–下沉)
注意:数组元素下标是从0开始的,因此编号从0开始,所以对于编号为k的节点,其左子节点编号为2k+1,右子节点编号为2k+2, ,父节点为(k-1)/2, 堆中最右最下边的含有子节点的子树根节点的编号为N/2-1
4个步骤:
- 构建所有数组元素形成的堆
- 交换堆顶元素和当前堆的最后一个元素
- 堆的大小减一
- 调整新的大小的堆,即将堆顶元素下沉,然后重复步骤2,直到堆中只有1个元素
ps: 使用大顶堆可以得到一个升序序列, 使用小顶堆可以得到一个降序序列
import java.util.Arrays;
public class PriorityQueueDemo {
public static void main(String[] args) {
int arr[]={2,3,8,7,0,5,4,1,6,9};
PriorityQueueDemo heap=new PriorityQueueDemo();
heap.sort(arr);
System.out.println(Arrays.toString(arr));
}
public void sort(int arr[])
{
int N=arr.length;
//将数组元素构建出一个大顶堆
//最后一个节点编号是N-1 其父节点编号为:(N-1-1)/2=N/2-1
for(int k=N/2-1;k>=0;k--)//N/2-1指向最后一个父节点 从其开始调整
{
sink(arr,k,N);
}
//交换 下沉
while(N>0)
{
swap(arr, 0, N-1);
N--;//N--可以看出是删除堆顶最大元素 因此需要进行下沉操作
sink(arr, 0, N);
}
}
public void sink(int arr[],int k,int N)
{
while(2*k+1<N)
{
int j=2*k+1;//指向左子节点
//根节点>=max(左子节点,右子节点)
if(j+1<N&&arr[j]<arr[j+1])//如果右子节点存在且右子节点比左子节点大 1
j++;//j指向右子节点
if(arr[k]>=arr[j])//找到<=根节点的子节点 停止 2
break;
swap(arr, k, j);
k=j;
}
}
public void swap(int arr[],int i,int j)
{
int tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
}
5. 堆的相关问题
5.1 在小顶堆中找到最大的元素
小顶堆中寻找最小元素简单,即堆顶元素,最大元素一定存在于叶子节点,找打第一个叶子节点开始遍历到数组尾部即可
最后一个叶子节点是最后一个节点的双亲节点的下一个节点
最后一个节点位置:N-1
最后一个节点的双亲节点位置:(N-1)/2
最后一个节点的双亲节点的下一个节点位置:(N-1)/2+1
5.2 在堆中删除任意元素
找到该元素,记录该元素的位置为i, 进行以下操作:
swap(arr, i, N-1);
N--;
sink(arr, i, N);
5.3 在最小堆中寻找第k小的元素
删除堆顶元素k次,返回第k次删除得到的元素
5.4 动态中位数的查找
中位数定义:
- 奇数个数据,升序之后取最中间的元素
- 偶数个数据,升序之后取中间两个元素的平均值
动态中位数:数据是动态添加的,每次都要返回当前数据的中位数(当前是奇数个数据,下次就是偶数)
解决:使用两个堆
大顶堆:包含较小的一半的元素
小顶堆:包含较大的一半的元素
假设大顶堆中的元素大于等于小顶堆中的元素,即:
奇数个元素,大顶堆中右n/2+1个,小顶堆中有n/2个
偶数个元素,大顶堆中右n/2个,小顶堆中有n/2个
因此
奇数个元素时:中位数即大顶堆的堆顶元素
偶数个元素时,中位数即(大顶堆的堆顶元素+小顶堆的堆顶元素)/2
实现细节:
当添加第一个第二个元素时,直接依次加入大顶堆和小顶堆即可
当添加第3个元素以及后面的元素时,假设添加的元素是x, 分为以下两种情况:
大顶堆中偶数个元素,此时x应该加入大顶堆,但不能直接加入,如果x比小顶堆堆顶元素小,直接加入大顶堆;如果x比小顶堆堆顶元素大,先将x加入小顶堆中排下序,再将小顶堆堆顶元素加入大顶堆,从而保证大顶堆中是较小的一半元素
大顶堆中奇数个元素,此时x应该加入小顶堆,但不能直接加入,如果x比大顶堆堆顶元素大,直接加入小顶堆;;如果x比大顶堆堆顶元素小,先将x加入大顶堆中排下序,再将大顶堆堆顶元素加入小顶堆,从而保证小顶堆中是较大的一半元素