数据结构堆以及堆排序的要点

1 堆 
堆的重要性质:任意节点的值总是大于等于(或者小于等于)子节点的值

  • 如果任意节点的值总是 ≥  子节点的值  称为  最大堆 大根堆 大顶堆
  • 如果任意节点的值总是 ≤  子节点的值  称为  最小堆 小根堆 小顶堆

2  二叉堆 
二叉堆的逻辑结构是一个完全二叉树 也叫完全二叉堆
鉴于完全二叉树的一些特性,二叉堆的底层(物理结构)一般用数组实现 

索引 i 的规律,(n是元素的数量)

  • 如果 i = 0,它是根节点
  • 如果 i > 0,它的父节点索引为 floar( (i-1) / 2)     这里floar表示向下取整
  • 对于索引为 i 的节点,如果  2i+1 ≤ n-1(不能超过最大索引),则其左叶子节点索引为  2i+1
  • 对于索引为 i 的节点,如果  2i+1 > n-1(超过最大索引),则没有左子节点,因为是完全二叉树当然就不可有右字节点
  • 对于索引为 i 的节点,如果  2i+2 ≤ n-1(不能超过最大索引),则其右叶子节点索引为  2i+2
  • 对于索引为 i 的节点,如果  2i+2 > n-1(超过最大索引),则没有右子节点,但是可能有左字节点也可能没有
  • 因为是完全二叉树,所以其第一个非叶子节点索引n/2 -1  注意 n 为 元素数量

3 最大堆的建立

两种方式: 1、自上而下的上虑      2、自下而上的下滤
上虑:把当前元素向上寻找其该在的位置,下滤:把当前元素向下寻找其该在的位置
对于大顶堆来说,上虑操作:当前元素与其父节点元素比较,如果小于父节点元素就进行交换位置,交换位置后再与其父节点元素进行比较,直到找到第一个小于其父节点元素的位置。而下滤操作是:当前元素其子节点中较大的元素进行比较,如果小于子节点,就进行交换位置,交换位置后再次与其子节点较大的元素进行比较,直到找到第一个大于其子节点元素的位置。因为下滤操作是当前点元素与子节点比较,所以对于叶子节点来说其没有子节点,因此没有下虑的必要,可以从第一个非叶子节点开始进行下虑,即索引为(n/2-1)的元素开始。需要注意的是,下滤过程是父节点与子节点中相对较大的节点进行交换位置,即先从子节点中比较得出较大的子节点,再将较大的元素与父节点进行比较。

 

显然:对于自上而下的上虑操作来说,每上滤一个元素,所有已经上虑完成的元素可以构成一个大顶堆,所以当上虑完成所有的元素的时候,整个堆会变成一个大顶堆。而对于自下而上的下滤操作来说,每完成一个下滤元素,该元素与左右子树构成一个大顶堆,以上图为例,第一次下虑完成后,[13, 17] 构成大顶堆,第二次下虑完成后,[7,19,25]构成大顶堆,第三次下滤时,父节点元素2的左右子树都已经时大顶堆了,元素2进行下滤时,其右子节点25要大于左子节点,所以元素2与右子节点25进行交换位置,当交换完成后,显然[25,17,13]并没有影响到左子树的大顶堆结构,如果进行交换的元素是17,显然右子树[13,2]其大顶堆结构会被破环,同时[2,17,25]也不能构成大顶堆结构,显然这样交换是不合理的。这里就解释了为什么自下而上的下滤操作可以构建堆结构,原因就是:每完成一个下滤操作,该节点与其所有以下滤的左右子树可以构成大顶堆,所以根节点下滤完成整个的堆结构完成构建过程。

4 堆的删除操作(删除堆顶元素)

以大顶堆为例,因为对于二叉堆结构来说,底层是数组结构,删除堆顶元素即数组的第一个元素,为减少数组的元素移动,是将数组的最后一个元素放到数组的第一个位置即堆顶位置,显然堆顶(根节点)的左右子树仍然保持了其大顶堆的结构,这是只需要对堆顶元素进行下滤操作(当前元素向下寻找其合适的位置)即可重新构建其堆结构。

自上而下的上虑、自下而上的下滤  两者效率比较(结论:自下而上的下滤操作效率要高

5 堆排序

排序原理,将数组元素进行建堆,这里以大顶堆为例,整个数组完成大顶的建立后,将堆顶元素与数组的最后一个元素进行交换位置,在数组的前n-1一个元素再次进行建堆操作,完成建堆操作后再将堆顶元素与数组的倒数第二个元素进行交换,这样数组的最后两个元素就是整个数组的最大的两个元素,依次类推,直到完成整个数组的排序。整个排序过程其实就是利用了堆定元素的删除重新建堆的操作。

代码实现

public static void main(String[] args) {
		int[] arr = {2,7,13,25,19,17,45,5,15};
		heapSort(arr);
		System.out.println(Arrays.toString(arr));
	}
	
	public static void heapSort(int[] arr) {
		// 原地建堆
		int size = arr.length;
		int index = (size >> 1) - 1;// 第一个非叶子节点开始下滤
		System.out.println(Arrays.toString(arr));
		while (index >= 0) {
			siftDown(arr, index--, size);
			System.out.println(Arrays.toString(arr));
		}

		System.out.println("----------------------");
		while (size > 1) {
			swap(0,  size - 1 ,arr);
			size--;
			siftDown(arr, 0, size);
			System.out.println(Arrays.toString(arr));
		}
	}


	/**
	 * 下滤操作
	 * @param arr   下滤操作的数组
	 * @param index 当前下滤操作元素在数组中索引
	 * @param size  要要下了操作的数组的元素个数(并不一定是对所有的数组元素进行下滤,
     *              可以是数组的前面的一部分进行下滤)
	 */
	private static void siftDown(int[] arr, int index, int size) {
		while (2 * index + 1 <= size - 1) {// 只要存在子节点
			int bigger = 2 * index + 1;
			if (2 * index + 2 <= size - 1) {
				// 找到左右子节点相对较大的元素
				bigger = compare(2 * index + 1, 2 * index + 2, arr);
			}
			if (arr[bigger] > arr[index]) {
				swap(bigger, index, arr); // 交换位置
				index = bigger;
			} else {
				break;
			}
		}
	}

	/**
	 * 交换位置
	 */

	private static void swap(int i, int j, int[] arr) {
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
		
	}

	/**
	 * 返回较大的元素
	 */
	private static int compare(int left, int right, int[] arr) {
		return arr[left] - arr[right] > 0 ? left : right;
	}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值