几句废话先:
本人最近在学习研究数据结构和算法,之所以为什么学习它,第一:是我看到到了现在学习的瓶颈,一直停留在使用别人的什么什么框架或者算法之类的东西,没有对程序更深层次的学习探究。第二:想在IT行业走得更加的长远,数据结构是必须要迈的一道坎。
另外本人从上一篇博客开始已经步入javascript,不在或者很少探讨.NET平台相关内容。
主题:
arr.heap-size
至于什么事堆,我在这里也不再的啰嗦,阅读本博文,已经默认你认识它,如果您是大牛,请忽视本博文或者找我的错,我很欢迎 :)
在堆排序之前我们要先知道一个非常基础的一个东西就是arr.heap-size。我刚开始看的时候,对这个不怎么重视,觉得,它不就是等于arr.length吗?并且在学习之初的建堆上,也没有多大的用处,但是在后面的排序中,优化性能方面它发挥了很大的作用。它的定义说白了就是在一个数组arr中有效的元素个数,换句话说:arr[1,arr.length]中可能有数据,但是在arr[1,arr.heap-size]中存放的是有效的数据。
最大堆
在有些书上,对于最大堆的定义有些啰嗦(个人觉得),其实说白了就是除了根节点外,任何一个孩子的根节点相对其孩子来说是最大的。至于最小堆就是它的反向。最小堆是用于构造优先队列的,这里暂时不说,毕竟现在都23点了,明儿还要早起,扯远了。。在以后的博文中会陆续讲解。最大堆的性质也就是它的定义所说的,维护最大堆的性质的时间复杂度是大Ο(log2 N) ,这里的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之。
本博文属于原创,允许转载,但请标明原始连接来源!!