墨风如雪博客

  • 源码小店
  • 导航站
  • 登录
  • java
  • 资源分享
让AI使用变得如此简单
  1. 首页
  2. 算法
  3. 正文

每日一道算法题:堆排序详解

2023年 7月 22日 138点热度 0人点赞 0条评论

引言

堆排序是一种高效的排序算法,时间复杂度为O(nlogn),其核心是堆数据结构。本文将详细介绍堆的定义、性质、堆排序的实现、优化以及其应用和变种。

堆的定义与性质

推的定义与性质

推是一种特殊的完全二叉树,满足以下两个性质:

  • 父节点的值大于或者等于子节点的值,称之为大根堆。
  • 父节点的值小于或者等于子节点的值,称之为小根堆。

堆的定义与性质

堆是一种基于推的数据结构,通常把大根推简称为堆。堆满足以下性质:

  • 堆是一棵完全二叉树。
  • 堆中每个节点的值都必须满足堆的性质。

堆排序详解

建堆

大根堆

大根堆中父节点的值大于等于它的子节点的值,建立大根堆的过程是从最后一个非叶子节点开始,依次向上调整堆。下面是建立大根堆的代码实现:

private void adjustHeap(int[] arr, int i, int len) {
    int temp = arr[i];
    for (int j = 2 * i + 1; j < len; j = 2 * j + 1) {
        if (j + 1 < len && arr[j] < arr[j + 1]) {
            j++;
        }
        if (arr[j] > temp) {
            arr[i] = arr[j];
            i = j;
        } else {
            break;
        }
    }
    arr[i] = temp;
}

public void buildMaxHeap(int[] arr, int len) {
    for (int i = len / 2 - 1; i >= 0; i--) {
        adjustHeap(arr, i, len);
    }
}

小根堆

小根堆中父节点的值小于等于它的子节点的值,建立小根堆的过程与建立大根堆过程类似,只需将相应的判断符号反转即可。下面是建立小根堆的代码实现:

private void adjustHeap(int[] arr, int i, int len) {
    int temp = arr[i];
    for (int j = 2 * i + 1; j < len; j = 2 * j + 1) {
        if (j + 1 < len && arr[j] > arr[j + 1]) {
            j++;
        }
        if (arr[j] < temp) {
            arr[i] = arr[j];
            i = j;
        } else {
            break;
        }
    }
    arr[i] = temp;
}

public void buildMinHeap(int[] arr, int len) {
    for (int i = len / 2 - 1; i >= 0; i--) {
        adjustHeap(arr, i, len);
    }
}

排序

大根堆排序

大根堆排序的过程是将待排序序列构建成大根堆,然后将根节点与序列最后一个元素交换位置,再将剩余的序列重新构建成大根堆,重复此过程,直到所有的元素均被排序。下面是大根堆排序的代码实现:

public void heapSort(int[] arr) {
    int len = arr.length;
    buildMaxHeap(arr, len);
    for (int i = len - 1; i > 0; i--) {
        swap(arr, 0, i);
        len--;
        adjustHeap(arr, 0, len);
    }
}

小根堆排序

小根堆排序的过程与大根堆排序类似,只需将建堆和调整堆的方法改为小根堆即可。下面是小根堆排序的代码实现:

public void heapSort(int[] arr) {
    int len = arr.length;
    buildMinHeap(arr, len);
    for (int i = len - 1; i > 0; i--) {
        swap(arr, 0, i);
        len--;
        adjustHeap(arr, 0, len);
    }
}

堆排序的实现

代码实现

堆排序的核心是建堆和调整堆,我们可以根据这两个过程分别封装成一个方法。下面是建堆的代码实现:

private void adjustHeap(int[] arr, int i, int len) {
    int temp = arr[i];
    for (int j = 2 * i + 1; j < len; j = 2 * j + 1) {
        if (j + 1 < len && arr[j] < arr[j + 1]) {
            j++;
        }
        if (arr[j] > temp) {
            arr[i] = arr[j];
            i = j;
        } else {
            break;
        }
    }
    arr[i] = temp;
}

public void buildMaxHeap(int[] arr, int len) {
    for (int i = len / 2 - 1; i >= 0; i--) {
        adjustHeap(arr, i, len);
    }
}

下面是调整堆的代码实现:

public void heapSort(int[] arr) {
    int len = arr.length;
    buildMaxHeap(arr, len);
    for (int i = len - 1; i > 0; i--) {
        swap(arr, 0, i);
        len--;
        adjustHeap(arr, 0, len);
    }
}

时间和空间复杂度

堆排序的时间复杂度为O(nlogn),空间复杂度为O(1)。因为堆排序是一种原地排序,不需要额外的空间。

堆排序的优化

堆的复用

建堆的过程中会重新分配内存空间,效率不高。我们可以通过将待排序序列装换为堆的方式来优化。下面是将待排序序列转换成大根堆的代码实现:

private void adjustHeap(int[] arr, int i, int len) {
    int temp = arr[i];
    for (int j = 2 * i + 1; j < len; j = 2 * j + 1) {
        if (j + 1 < len && arr[j] < arr[j + 1]) {
            j++;
        }
        if (arr[j] > temp) {
            arr[i] = arr[j];
            i = j;
        } else {
            break;
        }
    }
    arr[i] = temp;
}

private void heapify(int[] arr, int len, int i) {
    int left = 2 * i + 1;
    int right = 2 * i + 2;
    int maxIndex = i;
    if (left < len && arr[left] > arr[maxIndex]) {
        maxIndex = left;
    }
    if (right < len && arr[right] > arr[maxIndex]) {
        maxIndex = right;
    }
    if (maxIndex != i) {
        swap(arr, i, maxIndex);
        heapify(arr, len, maxIndex);
    }
}

public void buildMaxHeap(int[] arr, int len) {
    for (int i = len / 2; i >= 0; i--) {
        heapify(arr, len, i);
    }
}

堆排序的优化

我们可以使用交换法和不使用交换法两种方式实现堆排序,交换法需要不断交换堆顶元素和末尾元素,并调整堆,不使用交换法只需要将堆顶元素下移到合适的位置即可。

不使用交换

下面是不使用交换的堆排序的代码实现:

private void adjustHeap(int[] arr, int i, int len) {
    int temp = arr[i];
    int child = 2 * i + 1;
    while (child < len) {
        if (child + 1 < len && arr[child + 1] > arr[child]) {
            child++;
        }
        if (arr[child] > temp) {
            arr[i] = arr[child];
            i = child;
            child = 2 * i + 1;
        } else {
            break;
        }
    }
    arr[i] = temp;
}

public void heapSort(int[] arr) {
    int len = arr.length;
    for (int i = len / 2; i >= 0; i--) {
        adjustHeap(arr, i, len);
    }
    for (int i = len - 1; i > 0; i--) {
        int temp = arr[i];
        arr[i] = arr[0];
        arr[0] = temp;
        adjustHeap(arr, 0, i);
    }
}

时间和空间复杂度

堆排序的时间复杂度为O(nlogn),空间复杂度为O(1)。因为堆排序是一种原地排序,不需要额外的空间。

扩展点

堆与堆排序的应用

优先队列的实现

优先队列是一种特殊的队列,队列中的元素拥有优先级,高优先级的元素先出队。堆可以很方便地实现优先队列。

Top K 问题的解法

Top K 问题是指在一个无序的数据集合中,找到其中最大的K个数。堆可以很方便地实现Top K 问题。

堆排序的变种

索引堆排序

索引堆排序是一种变种的堆排序。索引堆是指一个普通的堆中的元素只能是某种具体的数据类型,而在索引堆中,元素是一个索引,根据这个索引可以得到一个数据类型的元素。索引堆排序需要另外一个数组来存储数据,堆排序的过程中只需要交换索引即可。

带权堆排序

带权堆是指堆中的元素带有权值,堆中元素的排序是根据权值大小进行的。带权堆排序的排序算法与普通堆排序一致,只需修改堆的调整过程即可。

总结

优缺点总结

堆排序的优点:

  • 时间复杂度为O(nlogn),相对较快。
  • 不需要额外的空间,具有原地排序的特点。

堆排序的缺点:

  • 堆的复杂度较高,建立堆的时空复杂度为O(n),相对低效。
  • 堆排序是一种不稳定的排序,相等元素在排序后位置可能变化。

学习心得分享

堆排序是一种高效的排序算法,理解堆的原理是掌握堆排序的关键。本文叙述了堆的定义与性质、建堆、排序,以及堆排序的实现和优化。在实际工程中,可以利用其建立优先队列和解决一些Top K问题。

未来研究方向

堆排序可以继续优化其建堆的过程,进一步提升堆排序的效率。此外,可以对于优先队列和Top K问题进行深入研究。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: java 动态规划 大顶堆 小顶堆 排序 数组 算法
最后更新:2023年 6月 23日

墨风如雪

一个热爱生活,热爱分享的程序员

打赏 点赞
< 上一篇
下一篇 >

文章评论

您需要 登录 之后才可以评论

墨风如雪

一个热爱生活,热爱分享的程序员

最新 热点 随机
最新 热点 随机
AI“游侠”降临A股:16个“大脑”组团“炒股”,30秒“算命”市场! 视频魔法来了!AI能实时“变脸”直播,连游戏画面也能瞬间换装? 告别“听指令”,AI要“自己动手”了!ChatGPT Agent,AI界的“全能选手”已上线! 8B 模型吊打 671B?数学证明界“卷王”Goedel-Prover-V2 来了! Kiro来了!亚马逊放大招,软件开发要被AI“绑架”了吗? 火速围观!Trae IDE 迎来两大明星模型,Kimi K2 硬核登场,Grok-4 (Beta) 闪耀国际!
Kimi变身学术“卷王”,你的论文和报告还好吗?昆仑万维扔出王炸:32B模型干翻671B,代码界迎来全能修理工!8亿参数撬动实时混音!谷歌开源“口袋DJ”,人人都能玩转音乐告别插件时代!OmniGen2:一个模型,通吃所有AIGC神操作2000万次呼唤背后,蓝骑士有了“赛博外挂”智能触手可及:Google Gemma-3n 系列模型,让万物皆能“思考”
开拍!谷歌 Veo 2 正式登陆 Gemini API - 你的视频工作流,准备好被颠覆了吗? 深入理解Web应用中的MVC架构 图像生成新篇章:OpenAI GPT-image-1 模型深度解析与应用前瞻 OWL Agent 实战指南:零成本打造你的全能开源 AI 打工人 每日一道算法题:归并排序详解 Java中synchronized关键字的八个锁问题及解决办法
标签聚合
AI 设计模式 spring 大模型 算法 java 教程 deepseek

COPYRIGHT © 2023 墨风如雪博客. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

免责声明 - 隐私政策