堆排序使用的是二叉堆。首先将所有的元素添加到一个堆上,然后不断移除最大的元素以获得一个排好序的线性表。
堆的存储
堆排序(heap sort)使用二叉堆(binary heap),它是一棵 完全二叉树,每个节点大于或等于它的任意一个孩子。
如果一颗二叉树的每一层都是满的,或者最后一层可以不填满并且最后一层的叶子都是靠左放置的,那么这棵二叉树就是完全的(complete)。
如果堆的大小是事先知道的,那么可将堆存储在一个ArrayList或一个数组中。树根在位置0处,它的两个子节点在位置1和位置2处。
对于位置i处的节点,它的:
- 左子结点在位置 2i+1 处
- 右子结点在位置 2i+2 处
- 父节点在位置 (i-1)/2 处
添加一个新结点
给堆添加一个新结点,首先将它添加到堆的末尾,然后按如下方式重建这棵树:
1 | Let the last node be the current node; |
删除一个结点
经常需要从堆中删除最大的元素,也就是这个堆中的根节点。在删除根节点之后,就必须重建这棵树以保持堆的属性。重建该树的算法如下所示:
1 | Move the last node to replace the root; |
Heap类
Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些类是可以和自己比较的,至于具体和另一个实现了Comparable接口的类如何比较,则依赖compareTo方法的实现,compareTo方法也被称为自然比较方法。如果开发者进入一个Collection的对象想要Collections的sort方法帮你自动进行排序的话,那么这个对象必须实现Comparable接口。compareTo方法的返回值是int,有三种情况:
- 比较者大于被比较者(也就是compareTo方法里面的对象),那么返回正整数
- 比较者等于被比较者,那么返回0
- 比较者小于被比较者,那么返回负整数
Comparator可以认为是是一个外比较器,个人认为有两种情况可以使用实现Comparator接口的方式:
1)一个对象不支持自己和自己比较(没有实现Comparable接口),但是又想对两个对象进行比较;2)一个对象实现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式
Comparator接口里面有一个compare方法,方法有两个参数T o1和T o2,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和Comparable接口一样是int,有三种情况:
- o1大于o2,返回正整数
- o1等于o2,返回0
- o1小于o3,返回负整数
使用Comparable接口对元素排序
1 | import java.util.Comparator; |
使用Comparator接口对元素排序
1 | import java.util.ArrayList; |
使用Heap类进行排序
使用Comparator需要编写测试用例,实现Comparator接口的的GeomatricObjectComparator,以及抽象父类GeometricObject,抽象方法getArea,在子类Circle和Rectangle类中实现。排序算法为HeapSort。
HeapSort排序算法
1 | import java.util.Comparator; |
GeometricObjectComparator类实现java.util.Comparator接口
1 | import java.io.Serializable; |
GeometricObject抽象父类
1 | import java.util.Date; |
Circle类继承自GeometricObject
1 | public class Circle extends GeometricObject { |
Rectangle类继承自GeometricObject
1 | public class Rectangle extends GeometricObject { |
堆排序的时间复杂度
设h表示包含n个元素的堆的高度。 堆的高度为O(logn)。
由于add方法会追踪从叶子节点到根节点的路径,因此向堆中添加一个新元素最多需要h步。所以建立一个包含n个元素的数组的初始堆需要O(nlogn)时间。
由于remove方法要跟踪从根节点到叶子节点的路径,因此从堆中删除根节点后,重建堆最多需要h步。由于要调用n次remove方法,所以产生一个有序数组需要的总时间为O(nlogn)。
堆排序不需要额外的数组空间,空间效率高于归并排序。
实现clone和equals的堆方法
1 | import java.util.Comparator; |