堆排序 实战算法分享
前言
关于排序算法,很多文章都已经深入分析过,堆排序其空间复杂度为O(1),时间复杂度为O(nlogn),在众多排序算法种可以说性能比较优越。
浏览众多的网上文章,未找到关于排序算法实战算法的文章。最近由于生产环境性能提示需求,去除了使用MySql数据order by的数据库排序功能,必须要做jdk内存中排序,现在将排序算法分享如下。
堆排序的过程
堆排序实际是对一个数组进行内部元素置换,不需额外占用空间,仅消耗点数组内元素比较遍历的时间,期算法可分为如下几步:
1.构建堆。
堆其实是二叉树型结构,一个数组可以按照如下规则构建堆:
i元素为二叉树的父节点,2i+1为左孩子节点,2i+2为右孩子节点。
2.调整堆为顶堆。
顶堆的意思是堆顶的元素要么最大称大顶堆,升序排序使用;要么最小称小顶堆,降序排序使用。
本文以下以升序,大顶堆为例。
调整的过程为对父,左,右三个节点比较,将最大值调整到堆顶。
循环过程从数组长度的一半开始,递减。
3. 调整为大顶后,将堆顶的元素和堆尾的元素交换。
4. 缩小范围,将堆尾的元素剔除,继续重复2,3过程。直到整个数组变得有序。
上面的文字可能不太形象,下面用图示说明一下:
我们以待排序数组[2,3,1,4]为例,进行升序排列。
1.构建堆
2.按照大小调整堆元素
3.整个堆调整为大顶堆
4.交换堆顶与堆尾元素位置,并缩小堆排序范围
5、继续排序剩下的堆
6、直到全部调整完毕,则排序完成
代码实战
1、主方法
/**
* 对象集合,根据对象的某一个属性排序
* @param array 对象集合
* @param fieldName 对象属性名称
* @param isAsc 是否升序
* @param <T> 对象
* @throws Exception
*/
public static <T> void heapSort(List<T> array, String fieldName, boolean isAsc){
if(CollectionUtils.isEmpty(array)){
return;
}
try {
buildTopHeap(array, fieldName, isAsc); // 构建顶堆
for (int i = array.size() - 1; i >= 1; i--) { //调整
Collections.swap(array, 0, i);
Object obj = array.get(i);
changeHeap(array, obj.getClass(), fieldName, i, 0, isAsc);
}
}catch (Exception e){
return;
}
}
2、构建顶堆
public static <T> void buildTopHeap(List<T> array, String fieldName, boolean isAsc) throws Exception{
if (array == null || array.size() <= 1) {
return;
}
int half = array.size() / 2; // 从len/2 开始遍历
for (int i = half; i >= 0; i--) {
Object obj = array.get(i);
changeHeap(array,obj.getClass(),fieldName, array.size(), i,isAsc); //调整堆位置
}
}
3、交换堆元素(递归)
public static <T> void changeHeap(List<T> array,Class<?> clazz, String fieldName, int heapSize, int index,boolean isAsc) throws Exception{
int left = index * 2 + 1;
int right = index * 2 + 2;
int findedIndex = index;
if(isAsc){//升序
if (left < heapSize && compare(array.get(left) ,array.get(findedIndex), clazz, fieldName)>0) {
findedIndex = left;
}
if (right < heapSize && compare(array.get(right) ,array.get(findedIndex), clazz, fieldName)>0) {
findedIndex = right;
}
} else {//降序
if (left < heapSize && compare(array.get(left) ,array.get(findedIndex), clazz, fieldName)<0) {
findedIndex = left;
}
if (right < heapSize && compare(array.get(right) ,array.get(findedIndex), clazz, fieldName)<0) {
findedIndex = right;
}
}
if (index != findedIndex) {
Collections.swap(array,index, findedIndex);
changeHeap(array,clazz,fieldName, heapSize, findedIndex, isAsc);
}
}
4、辅助方法
public static int compare(Object original,Object another, Class<?> clazz, String fieldName) throws Exception{
Object originalFieldValue = getFieldValue(original, clazz, fieldName);
Object anotherFieldValue = getFieldValue(another, clazz, fieldName);
int result = compareObject(originalFieldValue,anotherFieldValue);
return result;
}
public static Object getFieldValue(Object original, Class<?> clazz, String fieldName) throws Exception{
if(original == null){
throw new ClassNotFoundException("original not exist.");
}
Object fieldNameValue = null; //字段名值
List<Field> fields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()));
for (Field field : fields) {
if(fieldName.equals(field.getName())){
field.setAccessible(true);
fieldNameValue = field.get(original);
break;
}
}
return fieldNameValue;
}
public static int compareObject(Object original,Object another){
if(original instanceof Integer && another instanceof Integer){
Integer originalInt = (Integer)original;
Integer anotherInt = (Integer)another;
return originalInt.compareTo(anotherInt);
}else if(original instanceof String && another instanceof String){
String originalStr = (String)original;
String anotherStr = (String)another;
return originalStr.compareToIgnoreCase(anotherStr);
}else if(original instanceof Double && another instanceof Double){
Double originalStr = (Double)original;
Double anotherStr = (Double)another;
return originalStr.compareTo(anotherStr);
}else if(original instanceof Long && another instanceof Long){
Long originalStr = (Long)original;
Long anotherStr = (Long)another;
return originalStr.compareTo(anotherStr);
}else if(original instanceof Byte && another instanceof Byte){
Byte originalStr = (Byte)original;
Byte anotherStr = (Byte)another;
return originalStr.compareTo(anotherStr);
}else if(original instanceof BigDecimal && another instanceof BigDecimal){
BigDecimal originalStr = (BigDecimal)original;
BigDecimal anotherStr = (BigDecimal)another;
return originalStr.compareTo(anotherStr);
}
return 0;
}
5、测试一把
@Test
public void sortTest() throws Exception {
int[] array = {9, 8, 7, 56, 5, 4, 3, 2, 1, 0, -1, 203, -3};
System.out.println("Before heap:");
ArrayUtil.printArray(array);
SortUtil._heapSort(array);
System.out.println("After heap sort:");
ArrayUtil.printArray(array);
List<AA> aaList = Lists.newArrayList();
aaList.add(new AA(1, "a"));
aaList.add(new AA(98, "a"));
aaList.add(new AA(54, "c"));
aaList.add(new AA(2, "b4ruy"));
aaList.add(new AA(5, "r"));
aaList.add(new AA(21, "brj"));
aaList.add(new AA(35, "sdhr"));
aaList.add(new AA(12, "fdb"));
aaList.add(new AA(5, "r"));
aaList.add(new AA(90, "g"));
aaList.add(new AA(-1, "sadgc"));
SortUtil.heapSort(aaList, "name");
System.out.println("list After heap sort:");
ArrayUtil.printArray(aaList);
}
public static class AA {
public Integer id;
public String name;
public AA() {
}
public AA(Integer id, String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
6、结果
list After heap sort:
[
{"id":98,"name":"a"}
{"id":1,"name":"a"}
{"id":2,"name":"b4ruy"}
{"id":21,"name":"brj"}
{"id":54,"name":"c"}
{"id":12,"name":"fdb"}
{"id":90,"name":"g"}
{"id":5,"name":"r"}
{"id":5,"name":"r"}
{"id":-1,"name":"sadgc"}
{"id":35,"name":"sdhr"}
]
使用比较器Comparator<? super T> c 改造
完成上述代码变成之后,心里稍稍有点成就感,但是看看排序算法的调用就知道还有瑕疵,如下:
SortUtil.heapSort(aaList, “name”);
第二个参数还是用字符串来标记对象的属性名称,太不友好了。
追求完美的程序员怎么会就此罢手呢,阅读jdk自带的排序算法:
jdk自带的算法写法如下,第二个参数是一个比较器
Collections.sort(aaList, Comparator.comparing(AA::getHeight));
读完源码后,决定使用比较器Comparator对算法进行改造。
废话少说,直接贴出代码:
/***
* 排序工具类
* 1、对象集合 堆排序算法
*/
public class SortUtil {
/***
* 对象集合,根据对象的某一个属性做升序排序
* @param array 对象集合
* @param c Comparator 确定列表顺序的比较器
* @param <T> 对象
* @throws Exception
*/
public static <T> void heapSort(List<T> array, Comparator<? super T> c){
heapSort(array, c, true);
}
/**
* 对象集合,根据对象的某一个属性排序
* @param array 对象集合
* @param c Comparator 确定列表顺序的比较器
* @param isAsc 是否升序
* @param <T> 对象
* @throws Exception
*/
public static <T> void heapSort(List<T> array, Comparator<? super T> c, boolean isAsc){
if(CollectionUtils.isEmpty(array)){
return;
}
try {
buildTopHeap(array, c, isAsc);
for (int i = array.size() - 1; i >= 1; i--) {
Collections.swap(array, 0, i);
Object obj = array.get(i);
changeHeap(array, obj.getClass(), c, i, 0, isAsc);
}
}catch (Exception e){
return;
}
}
/***
* 构建顶堆
* isAsc=true 构建大顶堆
* isAsc=false 构建小顶堆
* @param array
* @param isAsc
*/
public static <T> void buildTopHeap(List<T> array, Comparator<? super T> c, boolean isAsc) throws Exception{
if (array == null || array.size() <= 1) {
return;
}
int half = array.size() / 2;
for (int i = half; i >= 0; i--) {
Object obj = array.get(i);
changeHeap(array,obj.getClass(),c, array.size(), i,isAsc);
}
}
/**
* 调整堆
* @param array
* @param heapSize
* @param index
*/
public static <T> void changeHeap(List<T> array,Class<?> clazz, Comparator<? super T> c, int heapSize, int index,boolean isAsc) throws Exception{
int left = index * 2 + 1;
int right = index * 2 + 2;
int findedIndex = index;
if(isAsc){//升序
if (left < heapSize && c.compare(array.get(left) ,array.get(findedIndex))>0) {
findedIndex = left;
}
if (right < heapSize && c.compare(array.get(right) ,array.get(findedIndex))>0) {
findedIndex = right;
}
} else {//降序
if (left < heapSize && c.compare(array.get(left) ,array.get(findedIndex))<0) {
findedIndex = left;
}
if (right < heapSize && c.compare(array.get(right) ,array.get(findedIndex))<0) {
findedIndex = right;
}
}
if (index != findedIndex) {
Collections.swap(array,index, findedIndex);
changeHeap(array,clazz,c, heapSize, findedIndex, isAsc);
}
}
}
测试一下,调用测试代码:
@Test
public void sortTest() throws Exception {
int[] array = {9, 8, 7, 56, 5, 4, 3, 2, 1, 0, -1, 203, -3};
System.out.println("Before heap:");
ArrayUtil.printArray(array);
SortUtil._heapSort(array);
System.out.println("After heap sort:");
ArrayUtil.printArray(array);
List<AA> aaList = Lists.newArrayList();
aaList.add(new AA(1, "a",(float)107.2));
aaList.add(new AA(98, "a",(float)197.2));
aaList.add(new AA(54, "c",(float)7.2));
aaList.add(new AA(2, "b4ruy",(float)151.1));
aaList.add(new AA(5, "r",(float)17.2));
aaList.add(new AA(21, "brj",(float)7.2));
aaList.add(new AA(35, "sdhr",(float)475.0998));
aaList.add(new AA(12, "fdb",(float)1017.2));
aaList.add(new AA(5, "r",(float)65.9));
aaList.add(new AA(90, "g",(float)38.111));
aaList.add(new AA(-1, "sadgc",(float)172.2));
SortUtil.heapSort(aaList, Comparator.comparing(AA::getHeight));
System.out.println("list After heap sort:");
ArrayUtil.printArray(aaList);
}
public static class AA {
public Integer id;
public String name;
public float height;
public AA() {
}
public AA(Integer id, String name,float height) {
this.id = id;
this.name = name;
this.height = height;
}
public double getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
测试结果:
[
{"id":21,"name":"brj","height":7.2}
{"id":54,"name":"c","height":7.2}
{"id":5,"name":"r","height":17.2}
{"id":90,"name":"g","height":38.111}
{"id":5,"name":"r","height":65.9}
{"id":1,"name":"a","height":107.2}
{"id":2,"name":"b4ruy","height":151.1}
{"id":-1,"name":"sadgc","height":172.2}
{"id":98,"name":"a","height":197.2}
{"id":35,"name":"sdhr","height":475.0998}
{"id":12,"name":"fdb","height":1017.2}
]
这里特别说明一点,height为7.2,分别是name:c 和 name:brj,在初始化的时候name:brj 排在name:c的数据前面,可是堆排序的结果确是name:c 在 name:brj前。
这刚好说明了堆排序的特点:不稳定。
稳定的意思就是排序前后相同数据的顺序的顺序是一致的。
附:各种排序算法总结
算法总结
图片名词解释:
n: 数据规模
k: “桶”的个数
In-place: 占用常数内存,不占用额外内存
Out-place: 占用额外内存
算法分类