上一篇写了数据结构之二叉搜索树、AVL自平衡树,此次来写堆。html
好久之前排序算法的时间复杂度一直是O(n^2), 当时学术界充斥着“排序算法不可能突破O(n^2)”的声音,直到1959年,由D.L.Shell提出了一种排序算法,希尔排序(Shell Sort),才打破了这种不可能的声音,把排序算法的时间复杂度提高到了O(n^3/2)!算法
当科学家们知道这种"不可能"被突破以后,又相继有了更快的排序算法,“不可能超越O(n^2)”完全成为了历史。数组
在1964年,没错,是55年前!堆排序这种奇思妙想的,十分精彩的,排序算法诞生了!时间复杂度为O(nlogn),远甩O(n^2)数据结构
由Robert W. Floyd(罗伯特·弗洛伊德)和J.W.J. Williams(威廉姆斯)共同发明了著名的堆排序,同时也发明了“堆”这样的数据结构, Floyd在1978年得到了图灵奖!真是个狼人!!(比很人还要多一点)性能
有时候了解下历史,也是十分有趣的!虽然你可能会以为并没什么卵用~code
以前第一次听到堆这个词的时候,感受像是一堆什么东西,彻底跟树连想不到一块儿,后来才知道,原来堆也是一颗二叉树,并且是彻底二叉树htm
堆的性质:blog
堆中某个节点的值老是不大于或不小于其父节点的值;
堆老是一棵彻底二叉树。排序
咱们能够把堆,存放在一个数组中,根据索引来获取节点,那么如何经过索引表示父子关系呢?
堆是一颗彻底二叉树,因此知足以下条件索引
假如当前的节点索引为:k
父节点索引:(k-1) / 2
左孩子节点:2 * k + 1
右孩子节点:2 * k + 2
根据这个规律,咱们就能够用索引来计算出父子节点的位置了。这样就能把堆存放在数组中使用,会更加节省内存。
堆排序算法就是造成一个堆后,假如是大顶堆,堆顶确定是最大的元素,那咱们每次都把堆顶的最大元素拿走,而后把堆末尾的元素放到堆顶来,可是这个元素不必定是当前最大的,因此还要对这个元素在堆里进行比较,把最大的元素放到堆顶,再取出来。如此咱们每次取出的都是剩余元素中最大的元素,就能获得一组从大到小有序的元素。下面咱们来用大顶堆对一组数据进行堆排序计算。
数据为:[50, 10, 90, 30, 70, 40, 80, 60, 20]
算法分为两个部分
1.如何将一组无序的数据构建出一个初始的大顶堆?
2.在拿走堆顶元素以后,如何计算出新的堆顶元素?
首先咱们要实现一个操做:若是一个节点的子节点比它更大,就交换位置,若是子节点还有子节点,就要继续比下去,直到末尾。这个操做咱们称为:HeapOne
public void HeapOne(List<int> list, int len, int s) { int temp, j; temp = list[s];//先把指定要下沉节点的值取出来 for (j = (2 * s)+1; j < len; j = (j*2)+1) { if (j < (len - 1) && list[j] < list[j + 1])//看看左右两个子节点谁更大,就取谁 ++j; if (temp >= list[j])//子节点比父节点小,就无论 break; list[s] = list[j];//先把子节点的值给父节点 s = j;//继续从这个子节点往下比较下去 } list[s] = temp; }
实现这个操做以后,就能够开始咱们的第一个部分了,造成初始大顶堆。
从最后一个非叶子节点开始,对该节点进行HeapOne,一直从下往上,直到把全部的父节点都HeapOne了一遍,一个初始的大顶堆就造成了。
public void HeapSort(List<int> list) { int i; for (i = (list.Count - 1) / 2; i >= 0; i--)//第一部分,造成一个初始大顶堆 { HeapOne(list, list.Count, i); } for (i = list.Count -1; i > 0; i--)//每拿走一个元素,都从新计算新堆 { int temp = list[0]; list[0] = list[i]; list[i] = temp; HeapOne(list, i, 0); } }
算法第二部分
一直重复这个操做后,直到最后一个堆顶被取出,放到数组末尾,堆的长度也就为0了,咱们的数组也就造成了一组从大到小的数列。
如此,堆排序就完成了
堆排序性能比较稳定,时间复杂度包含初始堆+排序时重建堆为:O(nlogn)。
在游戏开发中也会常用到堆
百度百科-堆排序 《大话数据结构》-程杰