墨风如雪博客

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

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

2023年 7月 22日 90点热度 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日

墨风如雪

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

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

文章评论

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

墨风如雪

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

最新 热点 随机
最新 热点 随机
告别机械感!OpenAudio S1让AI声音活起来 Sora触手可及!微软必应AI视频生成器,全民创作时代来临? 阿里WebAgent开源:引领自主搜索新纪元 重磅炸弹!字节跳动开源BAGEL:70亿参数,统一多模态理解与生成,AI“全能王”诞生记! 小米MiMo-VL:7B参数,怎么就成了多模态界的“越级打怪王”? 炸裂!DeepSeek 8B 量化版降临:告别显存焦虑,你的 3080 Ti 也能玩转顶级大模型了!
AI圈炸锅了!Mistral Medium 3:性能 SOTA,成本打骨折,企业玩家的新宠?字节终于开源“扣子”同款引擎了!FlowGram:AI 时代的可视化工作流利器告别“微信黑箱”!Chatlog:让你的聊天记录也能拥有“AI大脑”!字节跳动 Seed-Coder-8B:不靠人工洗数据,这80亿参数的小模型如何写出顶尖代码?85倍速的视觉革命:苹果发布 FastVLM,让你的 iPhone ‘看图说话’,快到飞起!告别AI视频“变脸怪”!腾讯混元Hunyuan Custom重磅开源,主体一致性“王炸”来了!
java 数据库连接池技术Apache Commons DBCP的(超详细总结) Spring Cloud 当下最火的Java微服务框架 java 消息队列框架ActiveMQ的(超详细总结) Java中Map集合的三种遍历方式 炸裂!MistralAI 新模型 Devstral-Small 来了:236亿参数,凭啥在软件工程榜单上碾压千亿巨头? 前端知识点:响应式设计
标签聚合
教程 算法 AI spring deepseek 设计模式 java 动态规划

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

Theme Kratos Made By Seaton Jiang

免责声明 - 隐私政策