数据结构堆(Heap)详解-堆的建立、插入、删除、最大堆、最小堆、堆排序等

基本概念:

1、完全二叉树:若二叉树的深度为h,则除第h层外,其他层的结点全部达到最大值,且第h层的所有结点都集中在左子树。

2、满二叉树:满二叉树是一种特殊的的完全二叉树,所有层的结点都是最大值。

什么是堆?

堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;

  • 堆总是一棵完全二叉树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

           

堆是非线性数据结构,相当于一维数组,有两个直接后继。

堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。

(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)

若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。
注意: 在二叉树中,若当前节点的下标为 i, 则其父节点的下标为 i/2,其左子节点的下标为 i*2,其右子节点的下标为i*2+1;

堆的插入:

每次插入都是将先将新数据放在数组最后,由于从这个新数据的父结点到根结点必然为一个有序的序列,现在的任务是将这个新数据插入到这个有序序列中——这就类似于直接插入排序中将一个数据并入到有序区间中。

我们通过一个插入例子来看看插入操作的细节。我们将数字 16 插入到这个堆中:

堆的数组是: [ 10, 7, 2, 5, 1 ]

第一步是将新的元素插入到数组的尾部,数组变成:[ 10, 7, 2, 5, 1, 16 ];

相应的树变成了:

16 被添加最后一行的第一个空位。

不行的是,现在堆属性不满足,因为 216 的上面,我们需要将大的数字在上面(这是一个最大堆)

为了恢复堆属性,我们需要交换 162

现在还没有完成,因为 10 也比 16 小。我们继续交换我们的插入元素和它的父节点,直到它的父节点比它大或者我们到达树的顶部。这就是所谓的 shift-up,每一次插入操作后都需要进行。它将一个太大或者太小的数字“浮起”到树的顶部。

最后我们得到的堆:

现在每一个父节点都比它的子节点大。

堆的删除:

堆中每次都只能删除堆顶元素。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于根结点数据的“下沉”过程。

我们将这个树中的 (10) 删除:

现在顶部有一个空的节点,怎么处理?

当插入节点的时候,我们将新的值返给数组的尾部。现在我们来做相反的事情:我们取出数组中的最后一个元素,将它放到树的顶部,然后再修复堆属性。

现在来看怎么 shift-down (1)。为了保持最大堆的堆属性,我们需要树的顶部是最大的数据。现在有两个数字可用于交换 72。我们选择这两者中的较大者称为最大值放在树的顶部,所以交换 71,现在树变成了:

继续堆化直到该节点没有任何子节点或者它比两个子节点都要大为止。对于我们的堆,我们只需要再有一次交换就恢复了堆属性:

最大堆:

1,构造最大堆

原始数据为a[] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7},采用顺序存储方式,对应的完全二叉树如下图所示:
                                                                

基本思想
首先将每个叶子节点视为一个堆,再将每个叶子节点与其父节点一起构造成一个包含更多节点的对。所以,在构造堆的时候,首先需要找到最后一个节点的父节点,从这个节点开始构造最大堆;直到该节点前面所有分支节点都处理完毕,这样最大堆就构造完毕了。
假设树的节点个数为n,以1为下标开始编号,直到n结束。对于节点i,其父节点为i/2;左孩子节点为i*2,右孩子节点为i*2+1。最后一个节点的下标为n,其父节点的下标为n/2。
我们边针对上边数组操作如下图所示,最后一个节点为7,其父节点为16,从16这个节点开始构造最大堆;构造完毕之后,转移到下一个父节点2,直到所有父节点都构造完毕。
                              

代码实现如下:

strcut MaxHeap
{
	Etype *heap; //数据元素存放的空间,下标从1开始存数数据,下标为0的作为工作空间,存储临时数据
	int HeapSize;//数据元素的个数
	int MaxSize; //存放数据元素空间的大小
};
MaxHeap H;

void MaxHeapInit (MaxHeap &H)
{
	for(int i = H.HeapSize/2; i>=1; i--)
	{
		H.heap[0] = H.heap[i];
		int son = i*2;
		while(son <= H.HeapSize)
		{
			if(son < H.HeapSize && H.heap[son] < H.heap[son+1])
				son++;
			if(H.heap[0] >= H.heap[son])
				break;
			else
			{
				H.heap[son/2] = H.heap[son];
				son *= 2;
			}
		}
		H.heap[son/2] = H.heap[0];
	}
}

再举一个详细的实例过程:

给定一个整形数组a[]={16,7,3,20,17,8},对其进行堆排序。首先根据该数组元素构建一个完全二叉树,具体过程如下(从左到右,从上到下按顺序一步一步的详细过程):

 


2,最大堆插入节点

最大堆的插入节点的思想就是先在堆的最后添加一个节点,然后沿着堆树上升。跟最大堆的初始化过程大致相同。

void MaxHeapInsert (MaxHeap &H, EType &x)
{
	if(H.HeapSize == H.MaxSize)
		return false;
	int i = ++H.HeapSize;
	while(i!=1 && x>H.heap[i/2])
	{
		H.heap[i] = H.heap[i/2];
		i = i/2;
	}
	H.heap[i] = x;
	return true;
}

3,最大堆堆顶节点删除

最大堆堆顶节点删除思想如下:将堆树的最后的节点提到根结点,然后删除最大值,然后再把新的根节点放到合适的位置。

void MaxHeapDelete (MaxHeap &H, EType &x)
{
	if(H.HeapSize == 0)
		return false;
	x = H.heap[1];
	H.heap[0] = H.heap[H.HeapSize--];
	int i = 1, son = i*2; 
 
	while(son <= H.HeapSize)
	{
		if(son <= H.HeapSize && H.heap[0] < H.heap[son+1])
			son++;
		if(H.heap[0] >= H.heap[son])
			break;
		H.heap[i] = H.heap[son];
		i = son;
		son  = son*2;
	}
	H.heap[i] = H.heap[0];
	return true;
}

最小堆

整体操作和最大堆类似,这里不做赘述。

堆排序

我的十大经典排序算法(C++、Java实现)动图演示及代码解析-冒泡排序、选择排序、插入排序、快速排序、堆排序、希尔排序、归并排序、计数排序、桶排序、基数排序:https://blog.csdn.net/xiaomucgwlmx/article/details/82589626 有各个经典排序算法的动图介绍和源码,可以参考下。

  • 86
    点赞
  • 232
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
堆排序是一种常见的排序算法,具有稳定性高、效率高等优点。下面介绍一下使用Java实现的小根堆排序。 首先,我们需要定义一个小根类,用于存储待排序的数据。该类需要包含以下几个方法: 1. `Heap()`:构造函数,用于初始化小根。 2. `insert(int val)`:插入操作,将一个新的元素插入到小根中。 3. `deleteMin()`:删除操作,删除小根中的最小元素,并返回该元素的值。 4. `size()`:获取小根中元素的个数。 5. `isEmpty()`:判断小根是否为空。 接下来,我们就可以使用小根对待排序的数据进行排序了。具体的步骤如下: 1. 将待排序的数据存入小根中。 2. 依次从小根删除最小元素,并将其存入数组中。 3. 最后,将数组反转,即可得到排序后的结果。 下面是具体的Java代码实现: ```java public class HeapSort { public static void heapSort(int[] arr) { Heap heap = new Heap(arr.length); for (int i = 0; i < arr.length; i++) { heap.insert(arr[i]); } for (int i = 0; i < arr.length; i++) { arr[i] = heap.deleteMin(); } // 反转数组 reverse(arr); } // 反转数组 private static void reverse(int[] arr) { int left = 0; int right = arr.length - 1; while (left < right) { int temp = arr[left]; arr[left] = arr[right]; arr[right] = temp; left++; right--; } } // 小根类 static class Heap { private int[] heap; private int size; public Heap(int capacity) { heap = new int[capacity + 1]; size = 0; } public void insert(int val) { if (size == heap.length - 1) { throw new RuntimeException("Heap is full"); } int i = ++size; while (i != 1 && val < heap[i / 2]) { heap[i] = heap[i / 2]; i /= 2; } heap[i] = val; } public int deleteMin() { if (isEmpty()) { throw new RuntimeException("Heap is empty"); } int min = heap[1]; int last = heap[size--]; int i = 1; int child = 2; while (child <= size) { if (child < size && heap[child] > heap[child + 1]) { child++; } if (last > heap[child]) { heap[i] = heap[child]; i = child; child *= 2; } else { break; } } heap[i] = last; return min; } public int size() { return size; } public boolean isEmpty() { return size == 0; } } } ``` 使用该算法对数组进行排序: ```java int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}; HeapSort.heapSort(arr); System.out.println(Arrays.toString(arr)); // 输出 [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9] ```
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值