1.简介
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序,
二叉堆满足二个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)
每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子
该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
2.基本思想
将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
3.构造初始堆
将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)
a.假设给定无序序列结构如下
b.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
c.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
此时,我们就将一个无需序列构造成了一个大顶堆。
4.将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换
a.将堆顶元素9和末尾元素4进行交换
b.重新调整结构,使其继续满足堆定义
c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
再简单总结下堆排序的基本思路:
a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
5.代码实现
def heapSort(list: Array[Int]): Array[Int] = { for (i <- list.indices) { val len = list.length buildMaxHeapTree(list, len - 1 - i) //每次loop 后用array(0) 最大值跟最后一个元素互换 ,后面循环时候排序最后一个元素 swapFunction(list, 0, len - 1 - i) } list } def buildMaxHeapTree(array: Array[Int], lastIndex: Int): Unit = { //根据最后一个节点计算父节点,最有一个节点可能是左节点 也可能是右节点 //val parent = (lastIndex -1) / 2 for (parent <- Range((lastIndex - 1) / 2, -1, -1) if lastIndex > 0) { //根据父节点计算左叶子节点 val leftChild = 2 * parent + 1 //定义一个变量等于leftchild 索引 ,用于跟父节点做swap 交换 var swapChild = leftChild //根据左边节点计算右边节点 val rightChild = leftChild + 1 //判断如果右边节点存在,那么 比较左右几点大小 if (rightChild <= lastIndex && array(leftChild) < array(rightChild)) { swapChild += 1 } //判断父节点跟较大左右节点中的数据大小 if (array(parent) < array(swapChild)) { swapFunction(array, parent, swapChild) } } } def swapFunction(array: Array[Int], parent: Int, child: Int): Unit = { val temp = array(parent) array(parent) = array(child) array(child) = temp }