排序算法(三)堆排序及有界堆排序Java实现及分析

1.堆排序
基数排序适用于大小有界的东西,除了他之外,还有一种你可能遇到的其它专用排序算法:有界堆排序。如果你在处理非常大的数据集,你想要得到前 10 个或者前k个元素,其中k远小于n,它是很有用的。

例如,假设你正在监视一 个Web 服务,它每天处理十亿次事务。在每一天结束时,你要汇报最大的k个事务(或最慢的,或者其它最 xx 的)。一个选项是存储所有事务,在一天结束时对它们进行排序,然后选择最大的k个。需要的时间与nlogn成正比,这非常慢,因为我们可能无法将十亿次交易记录在单个程序的内存中。我们必须使用“外部”排序算法。

我们首先了解一下堆,这是一个类似于二叉搜索树(BST)的数据结构。有一些区别:

在 BST 中,每个节点x都有“BST 特性”:x左子树中的所有节点都小于x,右子树中的所有节点都大于x。
在堆中,每个节点x都有“堆特性”:两个子树中的所有节点都大于x。
堆就像平衡的 BST;当你添加或删除元素时,他们会做一些额外的工作来重新使树平衡。因此,可以使用元素的数组来有效地实现它们。
现在讨论的是小根堆。如果子树中的节点都小于根节点,则为大根堆。

堆中最小的元素总是在根节点,所以我们可以在常数时间内找到它。在堆中添加和删除元素需要的时间与树的高度h成正比。而且由于堆总是平衡的,所以h与log n成正比。

JavaPriorityQueue使用堆实现。PriorityQueue提供Queue接口中指定的方法,包括offer和poll:

offer:将一个元素添加到队列中,更新堆,使每个节点都具有“堆特性”。需要logn的时间。
poll:从根节点中删除队列中的最小元素,并更新堆。需要logn的时间。
给定一个PriorityQueue,你可以像这样轻松地排序的n个元素的集合 :

使用offer,将集合的所有元素添加到PriorityQueue。
使用poll从队列中删除元素并将其添加到List。
因为poll返回队列中剩余的最小元素,所以元素按升序添加到List。这种排序方式称为堆排序 。

向队列中添加n个元素需要nlogn的时间。删除n个元素也是如此。所以堆排序的运行时间是O(n logn)。

2.代码实现:
/**
* @Author Ragty
* @Description 堆排序
* @Date 19:15 2019/6/12
**/
public void heapSort(List<T> list,Comparator<T> comparator) {
PriorityQueue<T> heap = new PriorityQueue<T>(list.size(),comparator);
heap.addAll(list);
list.clear();
while(!heap.isEmpty()) {
list.add(heap.poll());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
测试代码:

list = new ArrayList<Integer>(Arrays.asList(3, 5, 1, 4, 2));
sorter.heapSort(list, comparator);
System.out.println(list);
1
2
3

3.有界堆排序
有界堆是一个限制为最多包含k个元素的堆。如果你有n个元素,你可以跟踪这个最大的k个元素:

最初堆是空的。对于每个元素x:

分支 1:如果堆不满,请添加x到堆中。
分支 2:如果堆满了,请与堆中x的最小元素进行比较。如果x较小,它不能是最大的k个元素之一,所以你可以丢弃它。
分支 3:如果堆满了,并且x大于堆中的最小元素,请从堆中删除最小的元素并添加x。
使用顶部为最小元素的堆,我们可以跟踪最大的k个元素。我们来分析这个算法的性能。对于每个元素,我们执行以下操作之一:

分支 1:将元素添加到堆是O(log k)。
分支 2:找到堆中最小的元素是O(1)。
分支 3:删除最小元素是O(log k)。添加x也是O(log k)。
在最坏的情况下,如果元素按升序出现,我们总是执行分支 3。在这种情况下,处理n个元素的总时间是O(n log k),对于n是线性的。

4.代码实现:
/**
* @Author Ragty
* @Description 有界堆排序
* @Date 19:49 2019/6/12
**/
public List<T> topK(int k,List<T> list,Comparator<T> comparator) {
PriorityQueue<T> heap = new PriorityQueue<T>(list.size(),comparator);
for (T element : list) {
if (heap.size() < k) {
heap.offer(element);
continue;
}
int cmp = comparator.compare(element,heap.peek());
if (cmp>0) {
heap.poll();
heap.offer(element);
}
}
List<T> res = new LinkedList<T>();
while (!heap.isEmpty()) {
res.add(heap.poll(http://www.my516.com));
}
return res;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
测试代码:

list = new ArrayList<Integer>(Arrays.asList(6, 3, 5, 8, 1, 4, 2, 7));
List<Integer> queue = sorter.topK(4, list, comparator);
System.out.println(queue);
1
2
3

5.空间复杂性
到目前为止,我们已经谈到了很多运行时间的分析,但是对于许多算法,我们也关心空间。例如,归并排序的一个缺点是它会复制数据。在我们的实现中,它分配的空间总量是O(n log n)。通过优化,可以将空间降至O(n)。

相比之下,插入排序不会复制数据,因为它会原地排序元素。它使用临时变量来一次性比较两个元素,并使用一些其它局部变量。但它的空间使用不取决于n。

我们的堆排序实现创建了新PriorityQueue,来存储元素,所以空间是O(n); 但是如果你能够原地对列表排序,则可以使用O(1)的空间执行堆排序 。

刚刚实现的有界堆栈算法的一个好处是,它只需要与k成正比的空间(我们要保留的元素的数量),而k通常比n小得多 。

软件开发人员往往比空间更加注重运行时间,对于许多应用程序来说,这是适当的。但是对于大型数据集,空间可能同等或更加重要。例如:

如果一个数据集不能放入一个程序的内存,那么运行时间通常会大大增加,或者根本不能运行。如果你选择一个需要较少空间的算法,并且这样可以将计算放入内存中,则可能会运行得更快。同样,使用较少空间的程序,可能会更好地利用 CPU 缓存并运行速度更快。
在同时运行多个程序的服务器上,如果可以减少每个程序所需的空间,则可以在同一台服务器上运行更多程序,从而降低硬件和能源成本。
---------------------

原文地址:https://www.cnblogs.com/ly570/p/11106210.html

时间: 2024-10-14 19:21:40

排序算法(三)堆排序及有界堆排序Java实现及分析的相关文章

(高效率排序算法三)堆排序

一.堆的介绍         动态效果图         堆有如下特点的二叉树: 1.他是完全的二叉树.也就是说,除了树的最后一层布需要时满的,其他的每一层从左到右都是满的.(如下图的完全二叉树跟不完全二叉树) 2.它常常用一个数组在实现.(如下图显示了堆它与数组之间的关系.堆在存储器中的表示是数组:堆只是概念上的表示.注意树是完全二叉树,并且所有的节点满足堆的条件) 3.堆中的每一个节点都满足堆的条件,也就是说每一个节点的值都大于或者等于这个节点的子节点的值(如上图) 二,堆的移除 1.只能移

排序算法(三)之堆排序

预备知识 堆排序 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序.首先简单了解下堆结构. 堆 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆:或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆.如下图: 同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子 该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是: 大顶

三种排序算法(归并排序、快速排序,堆排序)

归并排序:建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.将已有序的子序列合并,得到完全有序的序列:即先使每个子序列有序,再使子序列段间有序.若将两个有序表合并成一个有序表,称为二路归并. 归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))的额外空间,时间复杂度为O(nlog(n)),算法不是自适应的,不需要对数据的随机读取. 工作原理: 1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后

【排序算法】(9)堆排序

堆排序 2019-11-10  11:45:11  by冲冲 1.概念 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序.首先简单了解下堆结构. 堆 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆:或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆.如下图: 同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子: 该数组从逻辑上讲就是一

十大经典排序算法最强总结(含Java代码实现)

最近几天在研究排序算法,看了很多博客,发现网上有的文章中对排序算法解释的并不是很透彻,而且有很多代码都是错误的,例如有的文章中在"桶排序"算法中对每个桶进行排序直接使用了Collection.sort()函数,这样虽然能达到效果,但对于算法研究来讲是不可以的.所以我根据这几天看的文章,整理了一个较为完整的排序算法总结,本文中的所有算法均有JAVA实现,经本人调试无误后才发出,如有错误,请各位前辈指出. 0.排序算法说明 0.1 排序的定义 对一序列对象根据某个关键字进行排序. 0.2

排序算法之冒泡排序的思想以及Java实现

1 基本思想 设排序表长为n,从后向前或者从前向后两两比较相邻元素的值,如果两者的相对次序不对(A[i-1] > A[i]),则交换它们,其结果是将最小的元素交换到待排序序列的第一个位置,我们称它为一趟冒泡.下一趟冒泡时,前一趟确定的最小元素不再参与比较,待排序序列减少一个元素,每趟冒泡的结果把序列中最小的元素放到了序列的"最前面". 2,算法的实现(Java) package Algorithm; public class BubleSort { /** * @param ar

常用排序算法(三)直接插入排序

直接插入排序 概要 本章介绍排序算法中的直接插入排序.内容包括:1. 直接插入排序介绍2. 直接插入排序图文说明3. 直接插入排序的时间复杂度和稳定性4. 直接插入排序实现4.1  直接插入排序C实现4.2  直接插入排序C++实现4.3  直接插入排序Java实现 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3596881.html 更多内容:数据结构与算法系列 目录 直接插入排序介绍 直接插入排序(Straight Insertion Sort

常用算法之排序算法三【快速排序】

快速排序是东尼·霍尔在1962提出的划分交换排序,并采用一种分治的策略.在这,我们先总结一下:快速排序 = 划分交换排序 + 分治.然后我们在一一介绍他. 划分交换排序 在讨论它时,感觉有种看了崔颢<黄鹤楼>之后,再去写黄鹤楼的感觉,因为MoreWindows写 得白话经典算法系列之六 快速排序 快速搞定已经足够出色了.我在这只是进行简单的复述,你需要了解更多请看他的博文. 先来看看划分交换排序的具体算法描述: 1.从数列中选出一个数作为基准 2.从数组中选出比它大的数放右边,比它小的数放左边

排序算法三:堆排序(Heapsort)

堆排序(Heapsort)是一种利用数据结构中的堆进行排序的算法,分为构建初始堆,减小堆的元素个数,调整堆共3步. (一)算法实现 1 protected void sort(int[] toSort) { 2 buildHeap(toSort); 3 for (int i = toSort.length - 1; i > 0; i--) { 4 CommonUtils.swap(toSort, 0, i); 5 adjustHeap(toSort, 0, i); 6 } 7 } 8 9 /**