1,定义+图解
直接推荐一篇讲解比较好的博客吧:彻底弄懂最大堆的四种操作(图解+程序)(JAVA)
堆排序算法分析:
- 时间复杂度:平均情况 O(nlogn);最好情况O(nlogn);最坏情况O(nlogn);
- 空间复杂度:O(1)。
- 稳定性:不稳定。
2,堆的插入、删除、初始化、堆排序源码
package manduner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author Manduner_TJU
* @version 创建时间:2019年4月9日下午2:47:46
*/
public class 堆 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>(Arrays.asList(null,45,36,18,53,72,30,48,93,15,35));
//初始化最大堆
// System.out.println("构造最大堆如下:");
// List<Integer> heap1 = initialHeap1(list);
// print(heap1);
// System.out.println("排序后如下:");
// heapSort(heap1);
// print(heap1);
//
// System.out.println("删除index位置上的元素:");
// delete(heap1,3);
// System.out.println("删除操作后的堆:");
// print(heap1);
// System.out.println("删除操作后进行排序的堆:");
// heapSort(heap1);
// print(heap1);
initialHeap2(list);
print(list);
// initialHeap3(list);
// print(list);
heapSort(list);
System.out.println("排序后如下:");
print(list);
}
//1,最大堆插入操作
public static void insert(List<Integer> heap, int value) {
//在数组的尾部添加
if(heap.size()==0) heap.add(0);//数组下标为0的位置不放元素
heap.add(value);
//开始上升操作
heapUp(heap,heap.size()-1);
}
//1.1,上升操作,让插入的数和父节点的数值比较,当大于父节点的时候就和父节点的值交换
public static void heapUp(List<Integer> heap, int index) {
//注意,由于数值是从下标为1开始,当index=1的时候,已经是根节点了
if(index>1) {
//求出父节点索引值
int parent = index/2;
//获取相应位置的数值
int parentValue = heap.get(parent);
int indexValue = heap.get(index);
//如果父亲节点比index的数值小,就交换二者的数值
if(parentValue < indexValue) {
//交换数值
swap(heap,parent,index);
//递归调用
heapUp(heap,parent);
}
}
}
//1.2,交换操作:把堆中的a,b位置的值互换
public static void swap(List<Integer> heap, int a, int b) {
int temp = heap.get(a);
heap.set(a, heap.get(b));
heap.set(b, temp);
}
//2,最大堆的删除操作
/**
* 删除堆中位置是index处的节点
* 操作原理是:当删除节点的数值时,原来的位置就会出现一个孔
* 填充这个孔的方法就是,把最后的叶子的值赋给该孔,然后进行下沉操作(特殊情况需要上浮操作),最后把该叶子删除
* 特殊情况: 删除堆10,9,3,8,5,1,2,7,6中的1节点的时候需要进行上浮操作
* 只允许删除堆顶元素的时候,只需要执行下沉操作就行,不需要考虑特殊情况
* @param heap
*/
public static void delete(List<Integer> heap, int index) {
if(index>heap.size()-1) return;
//把最后的一个叶子节点的数值复制给index位置
heap.set(index, heap.get(heap.size()-1));
//如果删除的不是堆顶元素,需要判断是否需要执行上浮操作
if(index > 1 && heap.get(index) > heap.get(index/2)) {
heapUp(heap,index);
}else {
//下沉操作
//heapDown(heap,index);
heapDown2(heap,index,heap.size()-1);
}
//把最后一个位置的数字删除
heap.remove(heap.size()-1);
}
//2.1, 下沉操作
/**
* 递归实现
* 删除堆中一个数据的时候,根据堆的性质,应该把相应的位置下移,才能保持住堆性质不变。
* @param heap 保持堆元素的数组
* @param index 待下沉的节点位置或者被删除的那个节点的位置
*/
public static void heapDown(List<Integer> heap, int index) {
int n = heap.size()-1;
//记录最大的那个儿子节点的位置
int child=-1;
//2*index>n说明该节点没有左右儿子节点了,那么就返回
if(2*index > n) {
return;
}//如果左右儿子都存在(2*index+1<n时,2*index一定是该节点的左孩子)
else if(2*index+1 < n) {
//定义左儿子节点
child = 2*index;
//如果左儿子小于右儿子的数值,取右儿子的下标
if(heap.get(child) < heap.get(child+1)) child++;
}//如果只有一个儿子(左儿子节点)
else if(2*index==n) {
child = 2*index;
}
if(heap.get(child) > heap.get(index)) {
//交换推中的child和index位置的值
swap(heap,child,index);
//完成交换后递归调用,继续下降
heapDown(heap,child);
}
}
//2.1.2 非递归下沉方法(可以处理部分节点的办法,应用于堆排序),很巧妙(其实还是递归的思想:将一个节点一直下沉到无法下沉为止)
public static void heapDown2(List<Integer> heap, int i, int n) {
int child;
while(i<=n/2) {
child = i*2;
//使child指向值较大的孩子
if(child+1<=n && heap.get(child)<heap.get(child+1)) {
child+=1;
}
if(heap.get(i)<heap.get(child)) {
swap(heap,i,child);
//交换后,以child为根的子树不一定满足堆定义,所以从child处开始调整
i =child;
}else break;
}
}
//3, 初始化操作(根据给定序列,构造最大堆的过程)
/**方法1:插入法:
*从空堆开始,依次插入每一个结点,直到所有的结点全部插入到堆为止。
*时间:O(n*log(n))
*/
public static List<Integer> initialHeap1(List<Integer> list) {
List<Integer> heap = new ArrayList<Integer>();
if(list.isEmpty()) return heap;
for(int i=1; i<list.size();i++) {
insert(heap,list.get(i));
}
return heap;
}
/**方法2:下沉法:
*序列对应一个完全二叉树;从最后一个分支结点(n/2)开始,到根(1)为止,依次对每个分支结点进行调整(下沉),
*以便形成以每个分支结点为根的堆,当最后对树根结点进行调整后,整个树就变成了一个堆。
*时间:O(n)
*/
public static void initialHeap2(List<Integer> heap) {
//根据树的性质建堆,树节点前一半一定是分支节点,即有孩子的,所以我们从这里开始调整出初始堆
if(heap.isEmpty()) return;
for(int i=heap.size()/2;i>0;i--) {
heapDown(heap,i);
}
}
/**方法3:调整法:
*序列对应一个完全二叉树;从最后一个分支结点(n/2)开始,到根(1)为止,依次对每个分支结点进行调整(下沉),
*以便形成以每个分支结点为根的堆,当最后对树根结点进行调整后,整个树就变成了一个堆。
*时间:O(n)
*/
public static void initialHeap3(List<Integer> heap) {
for(int i=heap.size()/2;i>0;i--) {
heapDown2(heap,i,heap.size()-1);
}
}
//4, 堆排序
/**
* (1)将给定序列初始化为一个最大堆
* (2)把根节点跟最后一个元素交换位置,调整剩下的n-1个节点,即可排好序
*/
//假设heapSort的输入heap已经是一个最大堆
public static void heapSort(List<Integer> heap) {
for(int i=heap.size()-1; i>0; i--) {
swap(heap,1,i);
heapDown2(heap,1,i-1);
}
}
//打印链表
public static void print(List<Integer> list) {
for (int i = 1; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
}
}