2020-11-28

1、堆的定义

堆是一棵完全二叉树,并且堆中每一个节点的值都必须大于等于(或者小于等于)其子树的每个节点的值。

2、堆的分类

大顶堆:堆中任一节点的值都大于等于子树中的节点值。
小顶堆:堆中任一节点的值都小于等于子树中的节点值。
在这里插入图片描述

3、堆的存储结构

因为堆是一棵完全二叉树,因此使用顺序结构(数组)存储更好!
回忆前面二叉树内容 二叉树两种存储方式
如下图为大顶堆的存储:

在这里插入图片描述

4、堆的基本操作

4.1、向堆中插入元素【自底向上堆化】

参考链接:堆的原理与实现

如下图的大顶堆,当数组尾部插入元素15后,破坏了大顶堆的性质,因此必须重新调整(和AVL树失衡后调整一样的道理)
在这里插入图片描述
发现15>父节点9,交换二者。
在这里插入图片描述
又发现交换后的15>父节点14,再次交换二者。
在这里插入图片描述
最后15<父节点18,满足大顶堆。

4.2、删除堆顶元素【自顶向下堆化】

(1)、一种错误思路:删除堆顶元素,即用第二大元素(第二层左右子树中最大元素)替代,依次迭代下去。错误原因在于:最后形成的不是完全二叉树。
(2)、正确操作:将数组最后一个元素替代堆顶元素;再判断此时是否为大顶堆(堆顶与左右子树比较并交换,依次迭代)
如下图:删除大顶堆的堆顶元素18,先让最后元素9替代它。
在这里插入图片描述
在这里插入图片描述
此时形成的不是大顶堆,要调整。
在这里插入图片描述
在这里插入图片描述
分析后可知,堆的插入、删除时间复杂度为O(log n),取决于完全二叉树的高度。

public class Heap {
	// 存放堆内元素的数组【从下标1开始存储元素,方便计算】
	private int data[];
	// 堆内存储元素最多个数
	private int MAXSIZE;
	// 堆内已经存储元素的个数
	private int count;

	public Heap() {
		this.data = new int[11];
		this.count = 0;
		this.MAXSIZE = 10;
		this.data[0] = Integer.MAX_VALUE;// 避免插入元素时,data[0]=0参与比较
	}

	public Heap(int capacity) {
		this.data = new int[capacity + 1];// 从下标1开始存储堆内元素
		this.count = 0;
		this.MAXSIZE = capacity;
		this.data[0] = Integer.MAX_VALUE;
	}

	public int[] getData() {
		return data;
	}

	public void setData(int[] data) {
		this.data = data;
	}

	public int getMAXSIZE() {
		return MAXSIZE;
	}

	public void setMAXSIZE(int mAXSIZE) {
		MAXSIZE = mAXSIZE;
	}

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}

	/* 向堆Heap中插入元素value,使其仍然为一个大顶堆,这个过程称为堆化。
	 * 	第一步:将元素插入数组尾部;
	 *  第二步:如果插入节点<父节点,则交换二者;一直重复此操作直到形成大/小顶堆。
	*/
	public static boolean insertHeap(Heap heap, int value) {
		if (heap.count == 0) {// 当前堆为空
			System.out.println("当前堆为空,新插入元素为:" + value);
			heap.data[1] = value;
			heap.count = 1;
			return true;
		}

		int length = heap.data.length;
		int count = heap.count;// 堆内元素个数

		if (length - count == 1) {
			System.out.println("堆已满,要扩容");
			return false;
		}

		// 正常插入时:heap.data[++count] = value,但是可能会破坏大顶堆

		heap.data[++heap.count] = value;// 插入元素,数组长度+1

		// 开始判断插入元素后是否还满足大顶堆性质
		int j = heap.count;
		while (j > 0 && (heap.data[j] > heap.data[j / 2])) {
			int temp = heap.data[j];
			heap.data[j] = heap.data[j / 2];
			heap.data[j / 2] = temp;

			j = j / 2;
		}

		return true;
	}

	/*
	 * 删除堆顶元素:
	 * 	  第一步:让数组最后一个元素替代堆顶元素;
	 * 	  第二步:此时堆顶元素与左右子树判断,是否符合大顶堆。
	 */
	public static boolean deleteHeap(Heap heap) {
		if (heap.count == 0) {
			System.out.println("堆内没有元素了,不能珊瑚");
			return false;
		}

		heap.data[1] = heap.data[heap.count];
		heap.data[heap.count] = 0;
		heap.count--;// 替换并删除

		// 开始判断替换后的堆顶元素是否还满足大顶堆性质
		int j = 1;
		while (j < heap.count && (heap.data[j] < heap.data[j * 2] || heap.data[j] < heap.data[j * 2 + 1])) {
			int leftValue = heap.data[j * 2];// 左子树值
			int rightValue = heap.data[j * 2 + 1];// 右子树值

			if (leftValue > rightValue) {
				int temp = heap.data[j];
				heap.data[j] = heap.data[j * 2];
				heap.data[j * 2] = temp;
				j = j * 2;
			} else {
				int temp = heap.data[j];
				heap.data[j] = heap.data[j * 2 + 1];
				heap.data[j * 2 + 1] = temp;
				j = j * 2 + 1;
			}
		}
		return true;
	}

	// 打印堆内元素
	public static void printHeap(Heap heap) {
		for (int i = 1; i <= heap.count; i++) {
			System.out.print(heap.data[i] + "  ");
		}
		System.out.println("\n--------------打印结束---------------");
	}

	public static void main(String[] args) {
		Heap heap = new Heap();
		// insertHeap(heap, 20);
		// insertHeap(heap, 10);
		// insertHeap(heap, 15);
		// insertHeap(heap, 18);// 调整
		// insertHeap(heap, 16);
		// insertHeap(heap, 12);
		// insertHeap(heap, 8);
		// insertHeap(heap, 30);// 调整
		// printHeap(heap);
		//
		// deleteHeap(heap);// 删除堆顶元素30
		// printHeap(heap);

		// insertHeap(heap, 20);
		// insertHeap(heap, 10);
		// insertHeap(heap, 16);
		// insertHeap(heap, 5);
		// insertHeap(heap, 15);
		// printHeap(heap);
		// deleteHeap(heap);// 删除堆顶元素20
		// printHeap(heap);

		insertHeap(heap, 10);
		insertHeap(heap, 5);
		insertHeap(heap, 8);
		insertHeap(heap, 20);
		insertHeap(heap, 15);
		insertHeap(heap, 16);
		insertHeap(heap, 30);
		printHeap(heap);
	}
}

5、堆的应用之堆排序

参考链接:图解堆排序算法
(1)、堆排序的基本思想是:将待排序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
(2)、堆排序步骤:
第一步、构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
第二步:将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

/*
 * 堆排序:
 * 	第一步:构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
 *  第二步:将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
 */
public class HeapSort {

	/*
	 * copy的Heap类代码,这里就不改为插入数组了
	 */
	public static boolean insertHeap(Heap heap, int value) {
		if (heap.getCount() == 0) {// 当前堆为空
			System.out.println("当前堆为空,新插入元素为:" + value);
			heap.setContent(heap, 1, value);
			heap.setCount(1);
			return true;
		}

		int length = heap.getData().length;
		int count = heap.getCount();// 堆内元素个数

		if (length - count == 1) {
			System.out.println("堆已满,要扩容");
			return false;
		}

		heap.setCount(heap.getCount() + 1);
		heap.setContent(heap, heap.getCount(), value);// 插入元素,数组长度+1

		// 开始判断插入元素后是否还满足大顶堆性质
		int j = heap.getCount();
		while (j >= 1 && (heap.getData()[j] > heap.getData()[j / 2])) {
			int temp = heap.getData()[j];
			heap.getData()[j] = heap.getData()[j / 2];
			heap.getData()[j / 2] = temp;

			j = j / 2;
		}

		return true;
	}

	/*
	 * 给大顶堆Heap排序:将堆顶元素与末尾元素进行交换,使末尾元素最大
	 */
	public static int[] sort(Heap heap) {

		int srotedHeap[] = new int[heap.getCount()];// 返回排序后的数组

		for (int i = heap.getCount(); i >= 1; i--) {

			srotedHeap[i - 1] = heap.getData()[1];// 当前最大元素

			heap.setContent(heap, 1, heap.getData()[i]);
			heap.setContent(heap, i, 0);

			// 开始判断替换后的堆顶元素是否还满足大顶堆性质
			int j = 1;
			while (j < (i - 1)
					&& (heap.getData()[j] < heap.getData()[j * 2] || heap.getData()[j] < heap.getData()[j * 2 + 1])) {
				int leftValue = heap.getData()[j * 2];// 左子树值
				int rightValue = heap.getData()[j * 2 + 1];// 右子树值

				if (leftValue > rightValue) {
					int tempValue = heap.getData()[j];

					heap.setContent(heap, j, heap.getData()[j * 2]);

					heap.setContent(heap, j * 2, tempValue);
					j = j * 2;
				} else {
					int tempValue = heap.getData()[j];

					heap.setContent(heap, j, heap.getData()[j * 2 + 1]);

					heap.setContent(heap, j * 2 + 1, tempValue);
					j = j * 2 + 1;
				}
			}
		}

		return srotedHeap;
	}

	public static void main(String[] args) {
		// int data[] = new int[] { 10, 8, 6, 7, 5 };
		int data[] = new int[] { 20, 10, 15, 8, 30, 12, 5, 4, 3, 2, 50, 25, 100 };

		Heap heap = new Heap(15);

		for (int i = 0; i < data.length; i++) {// 完成第一步:构建初始堆
			insertHeap(heap, data[i]);
		}
		heap.printHeap(heap);

		int[] sortedHeap = sort(heap);
		for (int i = 0; i < sortedHeap.length; i++) {
			System.out.print(sortedHeap[i] + "  ");
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值