java堆排序实现原理_最大堆原理及Java版实现

优先队列普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用堆数据结构来实现。

堆​ 堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树。

堆的实现通过构造二叉堆(binary heap),实为二叉树的一种;由于其应用的普遍性,当不加限定时,均指该数据结构的这种实现。这种数据结构具有以下性质。

任意节点小于(或大于)它的所有后裔,最小元(或最大元)在堆的根上(堆序性)。

堆总是一棵完全树。即除了最底层,其他层的节点都被元素填满,且最底层尽可能地从左到右填入。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

我们这里讲的是二叉堆。

堆的入队和出队的时间复杂度都是O(log n)

daced7a5193807c37f5e8b1f2e311134.png

上图就是一个最大堆的事例

下面我们使用数组来构建一个最大堆,在这里为了便于理解,数组索引为0的节点不存放数值,从第二个节点开始存放数据。

21efe6a2853e61e6a0edc96d0ccc096a.png

当前节点的父节点、左孩子、右孩子的索引就会有如下的关系:

父节点的索引:index/2 (index为当前节点的索引)

左孩子的索引:index*2

右孩子的索引:index*2+1

如果从数组的第一个节点开始存放数据的话,当前节点的父节点、左孩子、右孩子的索引就会有如下的关系:

父节点的索引:(index-1)/2 (index为当前节点的索引)

左孩子的索引:index*2+1

右孩子的索引:index*2+2

最大堆的基础架构importjava.util.ArrayList;

publicclassMaxHeap>{

//这里使用数组来实现

privateArrayListdata;

publicMaxHeap(intcapacity){

data=newArrayList<>(capacity);

}

publicMaxHeap(){

data=newArrayList<>();

}

//返回堆中元素的个数

publicintgetSize(){

returndata.size();

}

//判断堆是否为空

publicbooleanisEmpty(){

returndata.isEmpty();

}

//返回完全二叉树中索引为index的节点的父节点的索引

privateintparent(intindex){

if(index==0)

thrownewIllegalArgumentException("index-0 doesn't have parent");

return(index-1)/2;

}

//返回完全二叉树中索引为index的节点的左孩子的索引

privateintleftChild(intindex){

returnindex*2+1;

}

//返回完全二叉树中索引为index的节点的右孩子的索引

privateintrightChild(intindex){

returnindex*2+2;

}

//交换索引为i、j的值

privatevoidswap(inti,intj){

E t=data.get(i);

data.set(i,data.get(j));

data.set(j,t);

}

}

往堆中添加元素再向堆中添加元素时,除了要维持完全二叉树的结构,还要注意堆的约束条件:根节点的值要大于左右子树的值。

在这里因为我们使用数组来实现的堆,所以添加元素时,我们可以先将元素添加到数组的末尾,然后循环的与父节点比较大小,比父节点大就与父节点交换位置,之后就继续与新的父节点比较大小,直到小于等于父节点。

774c573ba54613e65b6457ceb6a6d569.png

如上图所示,我们要在这个堆中添加一个元素36

439c729b450d80c1b77117b5a09f637f.png

先将元素添加到数组的末尾。

0ffa297f64f67833159495efb8445390.png

然后通过当前的索引计算出父节点的索引,通过索引得到父节点的值16,通过比较新添加的节点比其父节点大,所以将新添加的值与父节点交换在数组中的位置。之后再与新的父节点41比较,36<41,结束操作。

添加元素的代码实现//向堆中添加元素

publicvoidadd(E e){

data.add(e);

siftUp(data.size()-1);

}

privatevoidsiftUp(intk){

//k不能是根节点,并且k的值要比父节点的值大

while(k>0&&data.get(parent(k)).compareTo(data.get(k))<0){

swap(k,parent(k));

k=parent(k);

}

}

删除堆顶元素

删除堆顶元素要注意维持堆的特殊性质。这里举个例子。

a5b34a491a3f84908ef0e621540d9533.png

要将这个堆中删除最大值,也就是堆顶元素62,先将62取出。

361aaf5f068dbacd9acab41587a72d16.png

将堆顶元素和堆的最后一个元素互换,也就是数组的首尾元素互换。

811fef5508e51fe01ba3d58c246cd9d1.png

删除最后一个元素,也就是堆中的最大值

c776e68d7af2ac91d59f2ce265ee1925.png

将当前的堆顶元素16的左右孩子41、30进行比较,找出最大的一个41,再与根节点16进行比较,左孩子41比根节点16大,所以将根节点与其左孩子互换,如上图所示。

重复上面的操作,直到当前节点的值大于其左右子树。过程如下所示。

cf0194760bb0a37a796e9ecf47fe38ea.png

e7055f99941447e719cfef1d4caada23.png

删除堆顶元素的代码实现//看堆中最大的元素

publicE findMax(){

if(data.size()==0)

thrownewIllegalArgumentException("Can not findMax when heap is empty");

returndata.get(0);

}

//取出堆中最大的元素

publicE extractMax(){

E ret=findMax();

swap(0,data.size()-1);

data.remove(data.size()-1);

siftDown(0);

returnret;

}

privatevoidsiftDown(intk){

//leftChild存在

while(leftChild(k)

intj=leftChild(k);

//rightChild存在,且值大于leftChild的值

if(j+1

data.get(j).compareTo(data.get(j+1))<0)

j=rightChild(k);

//data[j]是leftChild和rightChild中最大的

if(data.get(k).compareTo(data.get(j))>=0)

break;

swap(k,j);

k=j;

}

}

Replace操作

Replace是指将堆中的最大元素取出,替换另一个进去。

自然地我们会想到使用之前的extractMax()和add()来实现,但是这样的时间复杂度将会是两次的O(log n),因此我们可以直接将堆顶元素替换以后执行sift down操作,这样时间复杂度就只有O(log n)。

//取出堆中最大的元素,替换为元素e

publicE replace(E e){

E ret=findMax();

data.set(0,e);

siftDown(0);

returnret;

}

Heapify操作

Heapify是指将数组转化为堆

这里我们先将数组直接看成是一个完全二叉树,然后找到这棵二叉树的最后一个非叶子节点的节点,也就是该树的最后一个节点的父节点。然后从这个节点开始到根节点结束,执行sift down操作。

这样的时间复杂度为O(n)

Heapify代码实现//heapify操作:将数组转化为堆

publicMaxHeap(E[]arrs){

data=newArrayList<>(Arrays.asList(arrs));

for(inti=parent(data.size()-1);i>=0;i--){

siftDown(i);

}

}

堆排序思路

1、先数组初始化建立最大堆:时间复杂度为O(n)

2、然后第一个值和最后一个值替换:时间复杂度为O(1)

3、除开最后一个值再剩下的建立最大堆:时间复杂度为O(logn)

4、重复步骤2和3直到全部排序完,则数组排序成功:时间复杂度为O(n+nlogn)

所以堆排序的时间复杂度为:O(n)+O(n+nlogn)=O(nlogn)

版权声明:本文为CSDN博主「岁月长ch」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_37044026/java/article/details/86714130

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值