Java基础排序算法堆排序

本文详细介绍了堆排序的基本概念及实现过程,通过实例演示了如何建立最小堆,并逐步讲解了堆排序的具体步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在大家看之前我先声明,本文是在观看了http://blog.youkuaiyun.com/morewindows/article/details/6709644/之后,为加深理解所写。文章可以说是基于链接加上自己理解所作。仅作学习交流之用。

堆排序是一种性能上和快排很像的一种排序算法。在讲解堆排序之前,先让大家看一下什么是堆?怎么排?然后再去看代码就比较容易理解了。

随机一个数组

   

01234567
324613810
                                                                       注:第一行为数组下标,第二行为数组元素。则初始化堆为:


当然数组还是那个数组,只是我们在考虑是可以按照这种二叉堆的形式进行思考。然后我们更进一步处理:如果堆的每一个父元素都比子元素要小,并且对于任何一个分支都满足这种情况。那样不就是有序列了吗?如下,将上面的堆处理一下:


顺着父元素向下,我们可以看到都是有序的。最小的元素在最上面。这就是最小堆。某一下标i的元素父元素下标为(i-1)/2;两个子元素的下标为2*i+1和2*i+2.说了这么多还是不知道怎么排序?其实从上面可以看出a[0]也就是堆的根元素是最小的,那我们每次把最小的元素取走,再重新建堆;再取走,再建堆....这些依次取走的元素不就是有序的吗?

所以排序的思想就是1.建堆;2.取根元素,3,循环,直到只剩下一个元素为止。

1.如何建立最小堆?

  我们建立最小堆的过程是先从最后一个非叶子结点开始比较的,如第一幅图也就是初始堆中的非叶子结点6下标为3开始:

 



从上面下标为3的6开始由于6小于其子元素10,已然有序;下标减1,再比较下标为2的4,发现其子元素中3更小,于是调换位置。再接着下标继续减1比较下标为1的2,发现有更小的元素1于是调换位置。如此下标递减直到为0.但是注意我们这里比较的是整个分支。如上面最后一幅图虚线框住的部分。如果发现调换位置之后出现了更小的子元素,则继续调换。在最后一幅图中下标为0的元素3,发现更小元素1后换位置。可是换过之后发现子子元素2也比自己小于是它要继续下坠。这样最终得到位置图如最上面第二幅。

下面为代码:

public static void main(String[] args){
    int a[]={3,2,4,6,1,3,8,10};
    makeHeap(a,a.length-1);//先建立最小堆
    System.out.println("the first list is:");//输出第一次建成的最小堆
    ArrayUtils.printArray(a);
    heap_sort(a,a.length-1);            //堆排序调用
    System.out.println("the final list is:");
    ArrayUtils.printArray(a);
    }
	public static void heap_sort(int a[],int n){   //这里的n指的都是数组下标最大值。
		for(int i=n;i>=1;i--){
			int temp=a[0];    //这里实现的意思是每次将最小堆的根元素与数组末尾元素调换。然后再次建堆时把元素数减一,不再计算末尾元素。
			a[0]=a[i];
			a[i]=temp;
			insert(a,0,i-1);
		}
	}
	public static void makeHeap(int a[],int n){   //第一次建堆,从倒数第一个非叶子结点开始。
		for(int i=(n-1)/2;i>=0;i--){
			insert(a,i,n);
		}
	}
	public static void insert(int a[],int i,int n){  //这里i代表整理堆时的元素位置,n为该次建堆时的元素最大下标。本次结果是以i为根元素的分支是最小堆
		int j,temp;
		j=2*i+1;
		temp=a[i];
		while(j<=n){   //一直下坠
			if(j+1<=n&&a[j+1]<a[j]) //与子元素比较时,取子元素最小的一个
				j++;
			if(a[j]>temp)
				break;
			a[i]=a[j];
			i=j;
			j=2*i+1;
		}
		a[i]=temp;
	}

注意上面第2部分取根,其实指的就是将a[0]和a[n-1]即每次调换都是堆的最小元素和最尾元素。由于建堆不断发生,我们得到的其实是个从大到小的有序序列。另外一点需要说明的就是为什么在排序时先要建一个堆,其实从排序代码上看我们每次将最小堆的根元素保存在末尾时都是需要保证该根元素一定是此次建堆后全局最小的。对于一个初始数组来说这点很难保证。另外上面的insert函数每次也只是调整一个元素,并且每次调整前也是有个前提:它的子分支必须已经是最小堆了。这也是为什么我们开始建堆要从第一个非叶子结点开始(所有叶子结点都默认为只有一个元素的堆)。PS:如果大家能够不用先建堆就能排好序的话,不妨写在评论中。

第3部分循环建堆。我们是把根元素保存在最后一个元素位置上---交换位置。之所以这么做,就是因为这样下次建堆并将数组数目减1时,只需要调整根元素即可。从这一做法来看,堆只允许删除根元素。否则,你将花费大量时间去重建新堆。


好了,文章是自己消化别人博文所作。如果仍有不解请参阅文首链接。最后强调一点:仅供学习交流,不当之处,敬请留言。相互学习,共同提高。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值