堆排序:堆是指每个节点值大于/小于其左右节点值的完全二叉树,根节点一定是堆的所有节点中最大/小节点所在处。节点值大于左右节点值的堆叫大顶堆,根元素为整个堆的最大值。节点值小于左右节点值的堆叫小顶堆。
堆排序过程:
1、将无序数组构建成堆结构。
2、将根节点值与最后一个节点值互换,此时最后一个节点值最大
3、将剩下的n-1个节点重新构造为堆结构,继续步骤2,3,直至数组有序
堆排序是基于完全二叉树的数据结构,在堆排序的过程中会用到一些完全二叉树的性质,如下:(以下n均为节点个数)。
2i > n,则i节点没有左子树,2i + 1 > n,则i节点没有有子树。i从1开始算。
在完全二叉树中,最后一个非叶子节点的是第(n-1)/2个节点,此为完全二叉树的性质,自己尚未验证。但是数组的下标是从0开始的,所以最后一个非叶子节点的下标为(n-1-1)/2 = n/2 -1,n为数组长度。
此代码建立的是大堆,得到升序结果,从最后一个非叶子节点开始调整。
public class HeapSort { public static int[] heapSort(int[] arr){ int length = arr.length; for (int i = length -1;i >0;i--){ //将堆顶元素与最后一个元素互换 int max = arr[0]; arr[0] = arr[i]; arr[i] = max; //再次调整剩余堆 adjustHeap(arr,0,i); } return arr; } /** * @desc 构建初始堆 * @param arr * @return */ public static int[] init(int[] arr){ int length = arr.length; //下标是从0开始的 最后一个节点的下标为length-1,所以(length-1-1)/2 = length/2 -1 for (int k = length/2 -1;k >= 0;k--){ adjustHeap(arr,k,length); } return arr; } /** * @desc 将当前节点i与其所有子树调整为堆结构 * @param arr 待调整数组 * @param i 当前节点 * @param length 数组长度 * @return */ public static int [] adjustHeap(int arr[],int i,int length){ int temp = arr[i]; //默认当前节点的左节点更大 for (int larger = 2*i+1;larger < length;larger = 2*i+1){ int right = larger + 1;//即2*i + 2。i的右节点,可能不存在。 //取左右节点中较大者 //存在右节点&&右节点值>左节点值 if (right < length && arr[right] > arr[larger]){ larger = right; } //将左右节点的较大者与父节点比较,若arr[larger]更大,将arr[larger]上移到i位置,也就是当前的父节点位置 //将父节点位置指向larger,注意此时并没有将父节点的值放到larger,因为经过调整后,larger之后的元素可能不满足堆结构 //所以还要进一步判断找到父节点的最终位置,只是将父节点的位置暂时指向了larger if (arr[larger] > temp){ arr[i] = arr[larger]; i = larger; }else { //父节点比孩子节点都大,即当前位置满足堆结构 break; } } arr[i] = temp; return arr; } }
堆排序是一种选择排序,整体主要由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。