堆树和堆排序

一、堆树

1、定义

堆树的定义如下:
(1)堆树是一颗完全二叉树。
(2)堆树的每一个结点值都大于等于或者小于等于其左右子结点的值。
(3)堆树中每个结点的子树都是堆树。

为什么是大于等于或者小于等于呢?

  • 如果值都大于等于,那么根就是最大的数,这样的堆树可以称为大顶堆。
  • 如果值都小于等于,那么根就是最小的数,这样的堆树可以称为小顶堆。

如下图所示(图来自网络):

在这里插入图片描述

2、堆树是如何来存储的?

完全二叉树:除了最后一层,其他层每个结点都是满的,并且最后一层的结点全部靠左排列。

完全二叉树的最佳存储结构就是数组。因为它有着特殊的属性,直接利用下标就可以表示左右结点。

所以,堆树也可以很方便的用数组来存储表示:

假设下标从0开始,如果父结点索引是 i,那么它两个子结点的索引就是 2i+1和 2i+2。

3、堆树的操作

3.1 插入操作

堆的插入有两种实现方式:

  • (1)从下往上
  • (2)从上往下

堆树插入之后要进行一个堆化的操作,也就是让这棵树满足堆树的性质。
其插入过程就叫做堆化。

(1)从下往上

因为完全二叉树用数据构造之后,那么新插入的数据都在最后,但是插入之后,可能就不满足堆树的要求了,所以需要进行变动。以上图的大顶堆为例,

从下往上:我们在最后插入9之后是不满足堆树的性质的,所以我们需要与其父结点进行交换,直到依次往上做到不能交换位置为止。

在这里插入图片描述
(2)从上往下

从上往下:其实就是把插入的点放到堆顶,然后依次往下比较交换即可。

3.2 删除操作

假设我们要删除掉根结点10,并且删除之后,如何才能删除之后还能满足堆树的性质呢?

其实就是将要删除的元素和最后一个元素交换之后,然后删除最后一个元素之后,最后再从上往下进行堆化的操作即可。

在这里插入图片描述

3.3 修改操作

修改数据之后,同样要进行堆化操作,根据修改之后的数据和他父结点和子结点比较来决定是向上还是向下进行堆化操作。

二、堆排序算法

1、堆排序算法简介

堆排序(Heap Sort):是指利用堆树(大顶堆、小顶堆)这种数据结构所设计的一种排序算法。

堆树是一个完全二叉树的结构,并同时满足如下性质:即它的每一个子结点的键值或索引总是小于(或者大于)它的父结点。

堆排序算法演示动画:https://www.cs.usfca.edu/~galles/visualization/HeapSort.html

1.1 基本思想

堆排序的基本思想:就是先将数组序列构造成堆树,再进行排序。

那么怎么将一个数组序列构造成堆树?

  1. 先按照序列顺序存储在完全二叉树中(建堆)。
  2. 从最后一个非叶子节点从下往上堆化(得到堆树)。
    因为最后一个叶子节点没必要堆化。最后一个叶子结点的父结点就是最后一个非叶子结点。
  3. 将堆首和堆尾元素进行交换,交换之后进行一次堆化,依次进行这个操作即可完成排序。

注意:堆排序完全依赖于大顶堆(小顶堆)的相关操作。

运行过程图如下:

比如:这个数组:[8 4 20 7 3 1 25 14 17]。

1)完全二叉树结构:

在这里插入图片描述

2)大顶堆的构建:假设构建大顶堆:

在这里插入图片描述

3)堆排序过程:

在这里插入图片描述

1.2 性能分析

  • 堆排序的时间复杂度为:O(nlogn);
  • 空间复杂度为:O(1);
  • 堆排序是不稳定的排序算法。

2、代码实现

1)实现数组序列转堆树和堆排序:

public class HeapSort {

	/**
	 * 堆排序
	 * @param arr
	 */
	public static void heapSort(int[] arr) {
		int len = arr.length;
		/**
		 * 数组构造堆树,从倒数第一个非叶子结点开始从下往上依次进行堆化操作。<br/>
		 * 索引从0开始。两个子结点索引是 2i+1和 2i+2,所以最后一个非叶子结点的索引就是 len/2 - 1
		 */
		for (int i = len / 2 - 1; i >=0 ; i--) { //时间复杂度nlogn
			createMaxHeap(arr, i, len);
		}

		for (int i = len - 1; i > 0 ; i--) { //时间复杂度nlogn
			int maxData = arr[0]; //第一个数最大
			arr[0] = arr[i];
			arr[i] = maxData;
			/**
			 * 交换堆首和堆尾元素,然后重新构造大顶堆。
			 * 最后到 i为止都是排好序的,堆化的时候不需要再操作了
			 */
			createMaxHeap(arr, 0, i); // 因为 len~i 已经排好序了。每循环一次就构造好了排好序的数的位置,
		}
	}

	/**
	 * 大顶堆构造及堆化过程
	 * @param arr
	 * @param start - 每次都从堆顶开始
	 * @param end  - end之后是已经排好序的,所以需要end下标来判断结束
	 */
	public static void createMaxHeap(int[] arr, int start, int end) {
		int parentIndex = start;
		// 左子结点(下标是从0开始的就要加1)
		int leftChildIndex = 2 * parentIndex + 1;
		while (leftChildIndex < end) {
			// temp表示: 左右左结点,最大的那一个。
			int tempIndex = leftChildIndex;
			//比较左右结点谁大,记录谁的下标
			if (leftChildIndex + 1 < end && arr[leftChildIndex] < arr[leftChildIndex + 1]) {
				tempIndex = leftChildIndex + 1;
			}
			//父结点比孩子大,不交换
			if (arr[parentIndex] > arr[tempIndex]) {
				return;
			}else { //交换数据,刷新父结点继续执行堆化操作
				int tempData = arr[parentIndex];
				arr[parentIndex] = arr[tempIndex];
				arr[tempIndex] = tempData;
				parentIndex = tempIndex; // 继续堆化
				leftChildIndex = 2 * parentIndex + 1;
			}
		}
	}
	
}

2)测试

    public static void main(String[] args) {
        int data[] = { 8, 4, 20, 7, 3, 1, 25, 14, 17 };

        HeapSort.heapSort(data);
        System.out.println(Arrays.toString(data));
    }

在这里插入图片描述

参考文章:

– 求知若饥,虚心若愚。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
二叉排序树(Binary Search Tree,BST)和堆(Heap)是两种常见的数据结构,它们有以下区别: 1. 结构特点: - 二叉排序树:BST是一种二叉树,其中每个节点的大于其左子树中的所有节点的小于其右子树中的所有节点的。 - 堆:堆是一种完全二叉树,其中每个节点的大于等于其子节点的(最大堆),或者每个节点的小于等于其子节点的(最小堆)。 2. 插入和删除操作: - 二叉排序树:在BST中插入和删除节点的操作相对简单,只需按照特定规则找到合适的位置进行插入或删除即可。 - 堆:堆的插入和删除操作相对复杂,需要维护堆的性质。插入操作会将新元素放在堆的末尾,并通过上浮操作将其调整到合适位置;删除操作会将堆顶元素移除,并通过下沉操作将新的堆顶元素调整到合适位置。 3. 应用场景: - 二叉排序树:BST常用于实现动态集合,可以高效地支持查找、插入和删除操作。它还可以用于实现有序遍历等操作。 - 堆:堆常用于实现优先队列,可以高效地找到最大或最小元素。它还可以用于排序算法(如堆排序)和图算法(如Dijkstra算法)等。 4. 时间复杂度: - 二叉排序树:在平衡的情况下,BST的查找、插入和删除操作的平时间复杂度为O(log n),但在最坏情况下可能退化为链表,时间复杂度为O(n)。 - 堆:堆的插入和删除操作的时间复杂度为O(log n),其中n为堆中元素的个数。堆的查找操作不是其主要优势,时间复杂度为O(n)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值