简介
堆是一种数据结构,一种叫做完全二叉树的数据结构。
分类
-
大顶堆:每个节点的值都大于或者等于它的左右子节点的值。
-
小顶堆:每个节点的值都小于或者等于它的左右子节点的值。
基本思想
- 将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2.
- 将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;
- 重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。
- 最后,就得到一个有序的序列了。
过程
话不多说,就直接上图看吧,简洁明了
待排序数组为:
4 | 5 | 8 | 2 | 3 | 9 | 7 | 1 |
---|
-
构建大根堆,从第一个非叶子节点开始,比较自身和左右孩子的大小,如果小于左右孩子,则和孩子互换位置。这里是从2开始,2比1大,所以不用换,接着从下往上一个个的找非叶子节点,重复比较,发现8比9小,互换,然后4比9小再换,但是我们发现4被换下来之后,4比8小,所以还得接着换。最后排序序列,将堆顶的元素值和尾部的元素交换。
-
继续对剩余位置重复上述操作(从左到右,从上到下观看)
-
排序完毕
代码
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--;
adjust(arr, 0, len);//交换之后需要调整堆顶的元素
}
}
private static void buildMaxHeap(int[] arr, int len) {
// 从最后一个非叶节点开始向前遍历,调整节点性质,使之成为大顶堆
for (int i = (int)Math.floor(len / 2) - 1; i >= 0; i--) {
adjust(arr, i, len);
}
}
private static void adjust(int[] arr, int i, int len) {//调整第i个节点
// 先根据堆性质,找出它左右节点的索引
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);
// 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。
adjust(arr, largestIndex, len);
}
}
private static void swap (int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
应用
215. 数组中的第K个最大元素
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
- 你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
代码
class Solution {
public int findKthLargest(int[] nums, int k) {
int len = nums.length;
// 构建大顶堆,这里其实就是把待排序序列,变成一个大顶堆结构的数组
buildMaxHeap(nums, len);
// 交换堆顶和当前末尾的节点,重置大顶堆
for (int i = len - 1; i > 0; i--) {
swap(nums, 0, i);
if(k==1) return nums[len-1];
len--;
k--;
adjust(nums, 0, len);
}
return nums[0];
}
private static void buildMaxHeap(int[] arr, int len) {
// 从最后一个非叶节点开始向前遍历,调整节点性质,使之成为大顶堆
for (int i = (int)Math.floor(len / 2) - 1; i >= 0; i--) {
adjust(arr, i, len);
}
}
private static void adjust(int[] arr, int i, int len) {//调整第i个位置
// 先根据堆性质,找出它左右节点的索引
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);
// 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。
adjust(arr, largestIndex, len);
}
}
private static void swap (int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
1046. 最后一块石头的重量
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
- 如果 x == y,那么两块石头都会被完全粉碎;
- 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
- 最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。
示例:
输入:[2,7,4,1,8,1]
输出:1
解释:
先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1],
再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1],
接着是 2 和 1,得到 1,所以数组转换为 [1,1,1],
最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。
提示:
- 1 <= stones.length <= 30
- 1 <= stones[i] <= 1000
代码
class Solution {
public int lastStoneWeight(int[] stones) {
if (stones.length == 1) {
return stones[0];
}
int len = stones.length;
// 构建大顶堆
buildMaxHeap(stones, len);
while(len>1){
// 取出第一大的元素,重构堆
int x=stones[0];
swap(stones,0,len-1);
len--;
buildMaxHeap(stones, len);
// 取出第二大的元素,作比较
int y=stones[0];
if(x==y){
swap(stones,0,len-1);
len--;
}else{
stones[0]=Math.abs(x-y);
}
buildMaxHeap(stones, len);
}
if(len==1) return stones[0];
return 0;
}
private static void buildMaxHeap(int[] arr, int len) {
// 从最后一个非叶节点开始向前遍历,调整节点性质,使之成为大顶堆
for (int i = (int)Math.floor(len / 2) - 1; i >= 0; i--) {
adjust(arr, i, len);
}
}
private static void adjust(int[] arr, int i, int len) {//调整第i个节点
// 先根据堆性质,找出它左右节点的索引
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);
// 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。
adjust(arr, largestIndex, len);
}
}
private static void swap (int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
23. 合并K个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
提示:
- k == lists.length
- 0 <= k <= 10^4
- 0 <= lists[i].length <= 500
- -10^4 <= lists[i][j] <= 10^4
- lists[i] 按 升序 排列
- lists[i].length 的总和不超过 10^4
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
int k=lists.length;
for (int i = k-1; i >= 0; --i) {
if(lists[i]==null) lists[i]=lists[--k];
}
if(k==0) return null;
if(k==1) return lists[0];
ListNode head=new ListNode();
ListNode tail=head;
while(k>0){
buildMinHeap(lists,k);
tail.next=lists[0];
tail=tail.next;
lists[0]=lists[0].next;
if(lists[0]==null) lists[0]=lists[--k];
}
return head.next;
}
private static void buildMinHeap(ListNode[] lists, int len) {
// 从最后一个非叶节点开始向前遍历,调整节点性质,使之成为大顶堆
for (int i = (int)Math.floor(len / 2) - 1; i >= 0; i--) {
adjust(lists, i, len);
}
}
private static void adjust(ListNode[] lists, int i, int len) {//调整第i个位置
int index = 2 * i + 1;
if(index<len){
if (index+1 < len && lists[index].val > lists[index+1].val) {
index++;
}
if(lists[i].val>lists[index].val){
swap(lists, i, index);
adjust(lists, index, len);
}
}
}
private static void swap (ListNode[] lists, int i, int j) {
ListNode temp = lists[i];
lists[i] = lists[j];
lists[j] = temp;
}
}