博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
算法相关——Java排序算法之堆排序(七)
阅读量:4045 次
发布时间:2019-05-24

本文共 3989 字,大约阅读时间需要 13 分钟。

0. 前言

本系列文章将介绍一些常用的排序算法。排序是一个非常常见的应用场景,也是开发岗位面试必问的一道面试题,有人说,如果一个企业招聘开发人员的题目中没有排序算法题,那说明这个企业不是一个正规的企业,哈哈,虽然有点戏谑,但是也从侧面证明了排序算法的重要性。

本文将介绍的是常见排序算法中的堆排序

 

 

堆排序

7.1  基本思想

7.1.1  堆的概念

堆是一种特殊形式的完全二叉树,堆又分为最大堆和最小堆。最大/小堆指每个节点的值均不大于/小于其父节点的值(如果有父节点的话)。因此最大/小堆的根节点值一定是最大/小的。堆有几种基本操作必须掌握:

1堆的创建。可以声明一个容量足够的数组来存放堆内数据,下标为0的位置存放堆中的元素数。详情可见7.2代码实现中Heap的构造方法。

2堆的插入。堆的插入存在堆满、堆空、和其他,三种情况。堆满则不能再插入,这里以最大堆为例,堆空则直接插入到数组下标为1的位置作为根节点。其他的情况呢,就是先把要插入的数value放在数组最后面,再和其父节点的值相比较,若比父节点还要大,则父节点的值下移,直到value比父节点的父节点(等等若干个父节点)的值小了,或者value成了根节点,则退出循环,最后插入value,整个插入过程结束。详情可见7.2代码实现中Heapinsert()方法。

3堆的删除。堆的删除并不是指定一个元素删除,而是删除该堆此时的根节点,即最大堆的最大值lastRoot。这时,再把数组的最后一个元素lastValue,即二叉树中最下排最右边的元素lastValue放在根节点的位置,这时因为不知道该值是否配做“老大”,接下来就是对堆进行重建的时候,重建的过程也很容易理解,就是让lastValue值和第二排的最大值做比较,他们仨谁大谁上来,lastValue值一直下沉,直到它到了合适的位置,或者已经成了叶子节点则退出循环,后插入lastValue,整个删除过程结束。详情可见7.2代码实现中Heapdelete()方法。该方法返回lastRoot值。

 

7.1.2  堆排序的基本思想

堆排序的过程,其实就是把所有待排的n个元素依次insert()进堆,并且进行n次delere()的过程,每次都将delete()的返回值放在数组最后的位置。最后的序列就是一个有序数列了。

 

 

7.2  代码实现

首先是Heap类:

/**@author Calvin*@blog http://blog.csdn.net/seu_calvin/article/details/58673035*@data 2017/02/28*/public class Heap {		private int[] element;		public Heap(int maxSize){		//数组的第0位维护一个数组中有效元素的个数		element = new int[maxSize+1];		element[0] = 0;	}		public boolean isEmpty(){		return element[0] == 0;	}		public boolean isFull(){		return element[0] == element.length-1;	}		public void insert(int value){		if(isFull()){			throw new IndexOutOfBoundsException("该堆已满");		}		if(isEmpty()){			element[1] = value;		}else{			//新增元素下标			int position = element[0] + 1;			while(position != 1 && value > element[position/2]){				//若比父节点值大,则父节点下移				element[position] = element[position/2];				//判断是否需要继续与父节点进行大小比较				//跳出循环条件:直到(1)该值不比父节点大,或者(2)该值为最大,即到element[1]位置。				position/=2;			}			//最终把该值放在合适位置			element[position] = value;		}				//最后记录元素数加1		element[0] ++;		}		public int delete(){		if(isEmpty()){			throw new IndexOutOfBoundsException("该堆为空");		}		//要删除的根元素位置先赋值为最后一个有效元素的值		int deleteElement = element[1];		element[1] = element[element[0]];		int temp = element[1];		element[0]--;				//重建堆		int parent = 1;		int child = 2;		while(child <= element[0]){			if(element[child] < element[child+1]){				//如果右孩子更大则孩子下标加1				child++;			}			if(temp > element[child]){				//如果上来的元素比最大的孩子都大,那重建结束				break;			}else{				//大孩子上来				element[parent] = element[child];				parent = child;				child *= 2;//直到child > element[0]退出循环			}					}		//移动上来的根节点最终放置的位置为parent		element[parent] = temp;		//返回删除的最开始根节点		return deleteElement;	}		public void sort(){		if(isEmpty()){			throw new IndexOutOfBoundsException("该堆为空");		}		//依次删除元素,并把delete返回的结果放在最后		int size = element[0];		for(int i = 0; i < size; i++){			int deleteElement = delete();			element[element[0]+1] = deleteElement;		}		//输出排序结果		for(int j = 1; j < size+1; j++){			System.out.println(element[j]);		}	}	public void printAll() {		for(int j = 1; j < element[0]+1; j++){			System.out.println(element[j]);		}	}}

接着是主函数,将待排元素依次insert()进,再调用其sort()方法,该方法就是依次删除的过程,Heap中的代码逻辑已经注释的很清楚了,认真看看其实也不难。

/**@author Calvin*@blog http://blog.csdn.net/seu_calvin/article/details/58673035*@data 2017/02/28*/public class Order {          public static void main(String[] args) {          int[] array = new int[]{3,1,5,9,6,5,0,2,4,12};         Heap order = new Heap(10);         for(int i = 0; i < array.length; i++){            order.insert(array[i]);        }        order.sort();    }    }

输出结果略。

 

 

7.3  性能特点

堆排序的插入操作时间复杂度为O(logn)n次插入总时间为nO(logn),遍历删除也一样是nO(logn)。因此堆排序的时间复杂度是O(nlogn)空间复杂度为O(1),性能是非常好的。堆排序是不稳定的。因为堆排序是要建堆的,因此更适合对大量数据进行排序时使用。但是堆排比较的几乎都不是相邻元素,对cache极不友好,这也是其比较少被采用的原因。

 

7.4  堆排序与快排的性能比较

那么堆排序和快排哪个效率更好呢?两者时间复杂度相同,但是空间复杂度堆排序貌似更胜一筹,答案是快速排序比堆排序的效率高很多,并且随着数据规模的扩大,二者的差距不断扩大,快速排序的优势越来越明显。因此堆排比较少被使用。原因总结如下:

1)首先要明确第一点,数学上的时间复杂度不代表实际运行时的情况。

2堆排比较的几乎都不是相邻元素,对cache极不友好,堆排中比较父节点和子节点的值大小的时候,虽然计算下标会很快完成,但在大规模的数据中对数组指针寻址也需要一定的时间。而快速排序只需要将数组指针移动到相邻的区域即可。

3)堆排序中删除堆顶后的将最后的元素放到堆顶,再让其自我调整。这样有很多比较将是被浪费的,因为被拿到堆顶的那个元素几乎肯定是很小的,而靠近堆顶的元素又几乎肯定是很大的,最后一个元素能留在堆顶的可能性微乎其微,而且很有可能最终再被移动到底部。

 

你可能感兴趣的文章
/etc/resolv.conf
查看>>
container_of()传入结构体中的成员,返回该结构体的首地址
查看>>
linux sfdisk partition
查看>>
ipconfig,ifconfig,iwconfig
查看>>
opensuse12.2 PL2303 minicom
查看>>
网络视频服务器移植
查看>>
Encoding Schemes
查看>>
移植QT
查看>>
如此调用
查看>>
计算机的发展史
查看>>
带WiringPi库的交叉编译如何处理一
查看>>
带WiringPi库的交叉笔译如何处理二之软链接概念
查看>>
Spring事务的七种传播行为
查看>>
ES写入找不到主节点问题排查
查看>>
Java8 HashMap集合解析
查看>>
欢迎使用CSDN-markdown编辑器
查看>>
Android计算器实现源码分析
查看>>
Android系统构架
查看>>
Android 跨应用程序访问窗口知识点总结
查看>>
各种排序算法的分析及java实现
查看>>