堆排序的学习

几句废话先:


本人最近在学习研究数据结构和算法,之所以为什么学习它,第一:是我看到到了现在学习的瓶颈,一直停留在使用别人的什么什么框架或者算法之类的东西,没有对程序更深层次的学习探究。第二:想在IT行业走得更加的长远,数据结构是必须要迈的一道坎。

另外本人从上一篇博客开始已经步入javascript,不在或者很少探讨.NET平台相关内容。

主题:


arr.heap-size

至于什么事堆,我在这里也不再的啰嗦,阅读本博文,已经默认你认识它,如果您是大牛,请忽视本博文或者找我的错,我很欢迎  :)

在堆排序之前我们要先知道一个非常基础的一个东西就是arr.heap-size。我刚开始看的时候,对这个不怎么重视,觉得,它不就是等于arr.length吗?并且在学习之初的建堆上,也没有多大的用处,但是在后面的排序中,优化性能方面它发挥了很大的作用。它的定义说白了就是在一个数组arr中有效的元素个数,换句话说:arr[1,arr.length]中可能有数据,但是在arr[1,arr.heap-size]中存放的是有效的数据。

最大堆

在有些书上,对于最大堆的定义有些啰嗦(个人觉得),其实说白了就是除了根节点外,任何一个孩子的根节点相对其孩子来说是最大的。至于最小堆就是它的反向。最小堆是用于构造优先队列的,这里暂时不说,毕竟现在都23点了,明儿还要早起,扯远了。。在以后的博文中会陆续讲解。最大堆的性质也就是它的定义所说的,维护最大堆的性质的时间复杂度是大Ο(logN) ,这里的N不是方!!

我们先来看看怎么样去维护一个堆的性质:

下面用JS吧,毕竟现在日常就是用JS,啥语言都一样:

var arr = [4,1,3,2,16,9,10,14,8,7],
    heapLen = arr.length,
    halfLen = heapLen / 2,
    laster;

function buidHeap(i){
	var left = 2 * i + 1,
		right = 2 * i + 2;
	if(left < heapLen && arr[left] > arr[i]){
		laster = left;
	}else{
		laster = i;
	}

	if(right < heapLen && arr[right] > arr[laster]){
		laster = right;
	}

	if(laster !== i){
		var temp;
		temp = arr[i];
		arr[i] = arr[laster];
		arr[laster] = temp;
		buidHeap(laster);
	}
}

 维护最大堆的一个最主要的思想就是始终坚持它的性质,我们在arr[i]、arr[left]和arr[right]中选出最大的,并且将它的下标保存在laster中,但是在交换了之后考虑到还要继续维护它的性质,我们可以进行递归的调用。维护一个最大堆的代价也就是两个:第一:arr[i]、arr[left]和arr[right]的调换是常数时间,任意一个孩子的子树是≤2/3 * n的

所以它的时间T(n) = T(2/3 *N);

在上面我们只是在维护最大堆的性质,那么我们怎么去建堆那?

var arr = [4,1,3,2,16,9,10,14,8,7],
    heapLen = arr.length,
    halfLen = heapLen / 2,
    laster;

function buidHeap(i){
	var left = 2 * i + 1,
		right = 2 * i + 2;
	if(left < heapLen && arr[left] > arr[i]){
		laster = left;
	}else{
		laster = i;
	}

	if(right < heapLen && arr[right] > arr[laster]){
		laster = right;
	}

	if(laster !== i){
		var temp;
		temp = arr[i];
		arr[i] = arr[laster];
		arr[laster] = temp;
		buidHeap(laster);
	}
}


function exe(cb){
	for(var i = heapLen;i >= 0;i--){
		buidHeap(i);
	}
	cb();
}

  最下面的exe()函数就是一个建堆的过程。

到这我们的堆就已经建好了,我们的堆排序怎么实现?

实现堆排序:


这里有两个办法,第一个将已经刷选出的元素放在另外一个数组中,因为在最大堆中,根节点最大,所以我们每移除一次根节点都要在进行一次最大堆性质的维护。但是复制到新的一个数组这样就消耗了两倍的空间,这样做虽然可以实现最终的目的,但是还是不怎么好。第二个办法就是在本博文开始不是说到heap-size了吗,在这里就用上了,它表示的是有效元素,既然是有效的,我们每一次刷选后,把最大元素放在最后,同时heap-size - 1;这样不就节省了空间了吗?

代码:

var arr = [4,1,3,2,16,9,10,14,8,7],
    heapLen = arr.length,
    halfLen = heapLen / 2,
    laster;

function buidHeap(i){
	var left = 2 * i + 1,
		right = 2 * i + 2;
	if(left < heapLen && arr[left] > arr[i]){
		laster = left;
	}else{
		laster = i;
	}

	if(right < heapLen && arr[right] > arr[laster]){
		laster = right;
	}

	if(laster !== i){
		var temp;
		temp = arr[i];
		arr[i] = arr[laster];
		arr[laster] = temp;
		buidHeap(laster);
	}
}


function exe(cb){
	for(var i = heapLen;i >= 0;i--){
		buidHeap(i);
	}
	cb();
}

function buidMaxHeap(cb){
	exe(function(){
		console.log('------最大堆已建好------');
		for(var i = arr.length - 1;i>=1;i--){
			var temp;
			temp = arr[0];
			arr[0] = arr[i];
			arr[i] = temp;
			heapLen -= 1;
			buidHeap(0);
		}
	});
	cb();
};

(function heapsort(){
	buidMaxHeap(function(){
		console.log('------堆排序已派好------');
		for(var i = 0;i<arr.length;i++){
			console.log(arr[i]);
		}
	});
})();

最后想说:


本博文只是对堆排序的浅意义上的说明,没有更深次的探讨它的时间复杂度,第一个原因就是一些数学符号弄不出来,第二个就是:我个人语言表现力不足,你可以Google之。

本博文属于原创,允许转载,但请标明原始连接来源!!

原文地址:https://www.cnblogs.com/struCoder/p/3830645.html