堆排序

1、首先了解堆是什么

堆是一种数据结构,一种叫做完全二叉树的数据结构。

2、堆的性质

这里我们用到两种堆,其实也算是一种。

大顶堆:每个节点的值都大于或者等于它的左右子节点的值。

小顶堆:每个节点的值都小于或者等于它的左右子节点的值。

如上所示,就是两种堆。

如果我们把这种逻辑结构映射到数组中,就是下边这样

95823471
13542897

这个数组arr逻辑上就是一个堆。

从这里我们可以得出以下性质(重点)

对于大顶堆:arr[i] >= arr[2i + 1] && arr[i] >= arr[2i + 2]

对于小顶堆:arr[i] <= arr[2i + 1] && arr[i] <= arr[2i + 2]

3、堆排序的基本思想

了解了以上内容,我们可以开始探究堆排序的基本思想了。

堆排序的基本思想是:1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。

假设给定的无序序列arr是:

45823971

1、将无序序列构建成一个大顶堆。

首先我们将现在的无序序列看成一个堆结构,一个没有规则的二叉树,将序列里的值按照从上往下,从左到右依次填充到二叉树中。

 

根据大顶堆的性质,每个节点的值都大于或者等于它的左右子节点的值。所以我们需要找到所有包含子节点的节点,也就是非叶子节点,然后调整他们的父子关系,非叶子节点遍历的顺序应该是从下往上,这比从上往下的顺序遍历次数少很多,因为,大顶堆的性质要求父节点的值要大于或者等于子节点的值,如果从上往下遍历,当某个节点即是父节点又是子节点并且它的子节点仍然有子节点的时候,因为子节点还没有遍历到,所以子节点不符合大顶堆性质,当子节点调整后,必然会影响其父节点需要二次调整。但是从下往上的方式不需要考虑父节点,因为当前节点调整完之后,当前节点必然比它的所有子节点都大,所以,只会影响到子节点二次调整。相比之下,从下往上的遍历方式比从上往下的方式少了父节点的二次调整。

那么,该如何知道最后一个非叶子节点的位置,也就是索引值?

对于一个完全二叉树,在填满的情况下(非叶子节点都有两个子节点),每一层的元素个数是上一层的二倍,根节点数量是1,所以最后一层的节点数量,一定是之前所有层节点总数+1,所以,我们能找到最后一层的第一个节点的索引,即节点总数/2(根节点索引为0),这也就是第一个叶子节点,所以第一个非叶子节点的索引就是第一个叶子结点的索引-1。那么对于填不满的二叉树呢?这个计算方式仍然适用,当我们从上往下,从左往右填充二叉树的过程中,第一个叶子节点,一定是序列长度/2,所以第一个非叶子节点的索引就是arr.length / 2 -1。

现在找到了最后一个非叶子节点,即元素值为2的节点,比较它的左右节点的值,是否比他大,如果大就换位置。这里因为1<2,所以,不需要任何操作,继续比较下一个,即元素值为8的节点,它的左节点值为9比它本身大,所以需要交换

交换后的序列为:

45923871

因为元素8没有子节点,所以继续比较下一个非叶子节点,元素值为5的节点,它的两个子节点值都比本身小,不需要调整;然后是元素值为4的节点,也就是根节点,因为9>4,所以需要调整位置

交换后的序列为:

95423871

此时,原来元素值为9的节点值变成4了,而且它本身有两个子节点,所以,这时需要再次调整该节点

交换后的序列为:

95823471

到此,大顶堆就构建完毕了。满足大顶堆的性质。

2、排序序列,将堆顶的元素值和尾部的元素交换

交换后的序列为:

15823479

然后将剩余的元素重新构建大顶堆,其实就是调整根节点以及其调整后影响的子节点,因为其他节点之前已经满足大顶堆性质。

交换后的序列为:

85723419

然后,继续交换,堆顶节点元素值为8与当前尾部节点元素值为1的进行交换

交换后的序列为:

15723489

重新构建大顶堆

交换后的序列为:

75423189

继续交换

交换后的序列为:

15423789

重新构建大顶堆

构建后的序列为:

53421789

继续交换

交换后的序列为:

13425789

重新构建大顶堆

构建后的序列为:

43125789

继续交换

交换后的序列为:

23145789

重新构建大顶堆

构建后的序列为:

32145789

继续交换

交换后的序列为:

12345789

重新构建大顶堆

构建后的序列为:

21345789

继续交换

交换后的序列为:

12345789

此时,序列排序完成。以上就是整个堆排序的逻辑。

4、堆排序的代码实现(java版本)

public class HeapSort {

	public static void heapSort(int[] arr) {
		if (arr == null || arr.length == 0) {
			return;
		}
		int len = arr.length;
		// 构建大顶堆,这里其实就是把待排序序列,变成一个大顶堆结构的数组
		buildMaxHeap(arr, len);

		// 交换堆顶和当前末尾的节点,重置大顶堆
		for (int i = len - 1; i > 0; i--) {
			swap(arr, 0, i);
			len--;
			heapify(arr, 0, len);
		}
	}

	private static void buildMaxHeap(int[] arr, int len) {
		// 从最后一个非叶节点开始向前遍历,调整节点性质,使之成为大顶堆
		for (int i = (int)Math.floor(len / 2) - 1; i >= 0; i--) {
			heapify(arr, i, len);
		}
	}

	private static void heapify(int[] arr, int i, int len) {
		// 先根据堆性质,找出它左右节点的索引
		int left = 2 * i + 1;
		int right = 2 * i + 2;
		// 默认当前节点(父节点)是最大值。
		int largestIndex = i;
		if (left < len && arr[left] > arr[largestIndex]) {
			// 如果有左节点,并且左节点的值更大,更新最大值的索引
			largestIndex = left;
		}
		if (right < len && arr[right] > arr[largestIndex]) {
			// 如果有右节点,并且右节点的值更大,更新最大值的索引
			largestIndex = right;
		}

		if (largestIndex != i) {
			// 如果最大值不是当前非叶子节点的值,那么就把当前节点和最大值的子节点值互换
			swap(arr, i, largestIndex);
			// 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。
			heapify(arr, largestIndex, len);
		}
	}

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

5、复杂度分析

因为堆排序无关乎初始序列是否已经排序已经排序的状态,始终有两部分过程,构建初始的大顶堆的过程时间复杂度为O(n),交换及重建大顶堆的过程中,需要交换n-1次,重建大顶堆的过程根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。所以它最好和最坏的情况时间复杂度都是O(nlogn),空间复杂度O(1)。

  • 394
    点赞
  • 1201
    收藏
    觉得还不错? 一键收藏
  • 44
    评论
### 回答1: : ``` import heapq def sort_k_messed_array(arr, k): n = len(arr) heap = arr[:k+1] heapq.heapify(heap) ans = [] for i in range(k+1, n): ans.append(heapq.heappop(heap)) heapq.heappush(heap, arr[i]) while heap: ans.append(heapq.heappop(heap)) return ans ``` 这是一个将长度为 `n` 的数组排序的代码,其中 `k` 是排序前数组中的最大错排数。它使用了 Python 的 `heapq` 库,该库实现了最小堆。 整个代码的思路是:首先将前 `k+1` 个数存储在堆中,然后在剩余的数中逐个弹出堆顶元素,并将当前元素推入堆中。最后,当堆为空时,将所有剩余元素弹出并将其添加到答案数组中。 ### 回答2: 堆排序(Heap Sort)是一种基于比较的排序算法,使用二叉堆的数据结构来实现。 以下是一个Python实现的堆排序代码: ```python def heapify(arr, n, i): largest = i # 将当前节点设为最大值 left = 2 * i + 1 # 左子节点的索引 right = 2 * i + 2 # 右子节点的索引 # 如果左子节点存在且大于根节点,则将其设为最大节点 if left < n and arr[i] < arr[left]: largest = left # 如果右子节点存在且大于根节点和左子节点,则将其设为最大节点 if right < n and arr[largest] < arr[right]: largest = right # 如果最大节点不是根节点,则交换它们 if largest != i: arr[i], arr[largest] = arr[largest], arr[i] # 对交换后的子节点递归调用heapify函数 heapify(arr, n, largest) def heap_sort(arr): n = len(arr) # 构建最大堆 for i in range(n//2 - 1, -1, -1): heapify(arr, n, i) # 依次取出堆顶元素,并重新构建最大堆 for i in range(n-1, 0, -1): arr[i], arr[0] = arr[0], arr[i] heapify(arr, i, 0) return arr ``` 上述代码中,`heapify`函数用于将数组调整为一个最大堆,`heap_sort`函数用于对数组进行堆排序。首先,`heapify`函数通过递归的方式将当前节点与其子节点相比较,如果发现子节点比当前节点大,则交换两者,在交换后需要对子节点进行递归调用以确保调整后的堆仍然满足最大堆的条件。 在`heap_sort`函数中,首先构建一个最大堆,从树的倒数第二层开始(即`n//2 - 1`),往上逐层调用`heapify`函数进行调整。然后,通过循环依次取出最大堆的堆顶元素(即根节点),并将其与数组最后一个元素进行交换。交换后,需要对交换后的堆顶元素重新进行调整,以保证剩余元素仍然满足最大堆的条件。最后得到的数组就是按照从小到大排序的结果。 堆排序的时间复杂度是O(nlogn),其中n是数组的长度。在实际应用中,堆排序常用于需要稳定排序的场景,以及需要部分排序的场景(如找出前k个最大/最小元素)。 ### 回答3: 堆排序(Heap Sort)是一种使用最大堆(Max Heap)或最小堆(Min Heap)数据结构进行排序的算法。下面是一个使用Python实现的堆排序代码: ```python # 堆排序函数 def heap_sort(arr): n = len(arr) # 构建最大堆 for i in range(n // 2 - 1, -1, -1): heapify(arr, n, i) # 逐个将最大元素移到末尾 for i in range(n-1, 0, -1): arr[i], arr[0] = arr[0], arr[i] # 交换根节点和最后一个节点的值 heapify(arr, i, 0) # 调整堆函数 def heapify(arr, n, i): largest = i left = 2 * i + 1 right = 2 * i + 2 # 检查左子节点是否存在并大于根节点 if left < n and arr[largest] < arr[left]: largest = left # 检查右子节点是否存在并大于根节点 if right < n and arr[largest] < arr[right]: largest = right # 如果根节点不是最大值,则交换根节点和最大子节点的值 if largest != i: arr[i], arr[largest] = arr[largest], arr[i] # 递归调整堆 heapify(arr, n, largest) # 测试用例 arr = [12, 11, 13, 5, 6, 7] heap_sort(arr) print("排序后的数组:") for i in range(len(arr)): print(arr[i]) ``` 这段代码中,首先构建一个最大堆,然后进行堆排序。构建堆的操作是从数组中的中间位置往前遍历,每个节点都进行调整堆的操作,保证每个父节点的值都大于等于其子节点的值。然后,将堆顶元素与末尾元素交换,再对剩余元素进行调整堆的操作。重复这个过程,直到整个数组排序完成。最后,打印排序后的结果。运行结果为:5 6 7 11 12 13。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值