一、数据结构——堆
堆是计算机科学中一类特殊的数据结构的统称。堆通常可以被看作是一棵特殊的完全二叉树的数组对象。小编从以下三个方面全面结束堆结构:
①完全二叉树
定义:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。下图1中的二叉树就是完全二叉树,如果节点⑥或节点⑦或者节点⑧等任何一个节点是空缺的,则都不属于完全二叉树。
图1
②特殊性
堆中某个节点的值总是不大于或不小于其父结点的值。将根结点最大的堆叫做大根堆,根结点最小的堆叫做小根堆。图2就属于大根堆。
图2
③数组对象
所有的堆都是一个数组对象,其中数组{15,13,7,5,8,6}是大根堆结构,用二叉树的形式表示如图3所示。而数组{7,4,8,3,6,9}不是堆结构,如图3用二叉树形式表示时不满足特殊性。
图3 图4
二、堆排序算法
堆结构的特点:以大根堆为例,堆中0索引位置的数一定是最大的数。
堆排序算法的基本思想:
1.首先将待排序的数组构造成一个大根堆结构,此时,整个数组的最大值就是堆结构的顶端;
2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1(最后一个树已经是最大的,不用管它);
3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组;
堆排序算法是一个空间复杂度为o(1)的排序算法,只使用了有限个变量,它的时间复杂度为o(N*logN),运行效率上也比冒泡排序,插入排序,选择排序等o(N^2)的排序算法要更高,但是堆排序不具备稳定性(排序算法的稳定性是指数组中大小相等的元素在排序前后其相对位置不发生改变)。
举个例子,将下面数组进行排序,图5是将数组按完全二叉树的形式表示
图5
在用堆排序算法对数组进行排序前,小编先理一下父节点与子节点在数组索引中对应的关系:①若父节点的索引为Index,左子节点的索引为2*Index+1,右子节点的索引为2*Index+2;②若子节点的索引为Index,则其父节点的索引为(Index-1)/2。
现在开始用堆排序算法对数组进行排序:
1.构造大根堆:遍历数组,当前索引对应的值分别跟其父节点索引对应的值比较,如果该索引位置的值比其父节点索引位置的值大,那么交换两个值的位置。遍历结束,那么数组就是一个大根堆结构了;
2.将索引0位置的值(数组中最大的值)和数组中最后位置的值交换,并且不管最后一个值了(因为最后一个位置就是最大的值);
3.调整堆:第2步之后数组前n-1个值不一定是堆结构了,需调整成堆结构。从根节点开始,用根节点的值和其左右子节点中最大的值比较,如果根节点的值还是最大,那么数组前n-1个值已经是堆结构了,重复步骤2;如果根节点的值比其左右子节点中最大的值小,则根节点的值和值大的那个子节点进行位置交换,当前节点的索引更新为值大的那个子节点的索引,然后重复步骤3,直到结束,数组就排好序了。堆排序算法的Java代码实现如下:
public class HeapSort1 {
public static void heapSort(int[] arr) {
//将数组arr中的数调整为大根堆,一开始堆的大小heapSize=0,然后一个个加,直到数组中N个数都加到堆里面,此时heapSize=N
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
//heapSize是标志位,如果再往数组添加一个数,这个数就加在heapSize位置上,并且heapSize++,heapSize就代表堆的大小
int heapSize = arr.length;
//数组中0位置和堆上最后位置上的数交换
swap(arr, 0, --heapSize);
while (heapSize > 0) {//只要堆的大小还大于0
heapify(arr, 0, heapSize);//每次0位置和堆上最后位置上的数交换后,数组最后一个数(肯定是最大的数,放在数组后面不用管了)
// 与堆切断联系,让剩下的数重新调整为大根堆,依次类推
swap(arr, 0, --heapSize);
}
}
//数组(此时的数组是乱的,不是按大根堆排列)中某个数现在处于index位置,根据大根堆的特点不断向上调整
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
//某个数(最小的数)现在处于index(等于0)位置,根据大根堆特点不断向下调整
public static void heapify(int[] arr, int index, int heapSize) {
int leftSon = 2 * index + 1;//左孩子的下标
while (leftSon < heapSize) {//说明index位置的数有左孩子
// leftSon + 1 < heapSize,说明index位置的数有右孩子
//左右两个孩子的数大,就把谁的位置索引给largerIndex
int largerIndex = leftSon + 1 < heapSize && arr[leftSon + 1] > arr[leftSon] ? leftSon + 1 : leftSon;
//父和大孩子之间,谁的数大,就把谁的下标索引给largerIndex
largerIndex = arr[largerIndex] > arr[index] ? largerIndex : index;
if (largerIndex == index) {
break;
}
swap(arr, largerIndex, index);
index = largerIndex;
leftSon = 2 * index + 1;
}
}
//交换两个值的位置
public static void swap(int[] arr, int i, int j) {
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//代码测试
public static void main(String[] args) {
int[] arr = {2, 8, 12, 6, 9, 4, 2, 6, 15, 3, 27};
heapSort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
代码运行结果如图6所示:
图6