排序算法-堆排序

不知不觉又周六了,明天终于可以睡个懒觉了,很开心。今天呢我给大家分析第四个排序算法-堆排序,话不多说,直接进入正题。

原创不易,如果转载请注明链接:
https://blog.youkuaiyun.com/qq_38305457/article/details/96503472
我不能保证写的每个地方都是对的,但是至少能保证不复制,不黏贴,保证每一句话,每一行代码都经过了认真的推敲,仔细的斟酌。每一篇文章的背后,希望都能看到自己对于技术,对于生活的态度。
我相信乔布斯说的,只有那些疯狂到认为自己可以改变世界的人才能真正的改变世界。
面对压力,我可以挑灯夜战,不眠不休;面对困难,我愿意迎难而上,永不退缩。 其实我想说的是:我只是一个程序员,这就是我现在纯粹人生的全部。

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

{kiki⩽k2i⩽k2i+1或{kiki⩾k2i⩾k2i+1(i=1,2,···,⌊n2⌋){ki⩽k2iki⩽k2i+1或{ki⩾k2iki⩾k2i+1(i=1,2,···,⌊n2⌋)

把此序列对应的二维数组看成一个完全二叉树。那么堆的含义就是:完全二叉树中任何一个非叶子节点的值均不大于(或不小于)其左,右孩子节点的值。 由上述性质可知大顶堆的堆顶的关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。因此我们可使用大顶堆进行升序排序, 使用小顶堆进行降序排序。

1、基本思想

此处以大顶堆为例,堆排序的过程就是将待排序的序列构造成一个堆,选出堆中最大的移走,再把剩余的元素调整成堆,找出最大的再移走,重复直至有序。

2、算法描述

①. 先将初始序列K[1…n]K[1…n]建成一个大顶堆, 那么此时第一个元素K1K1最大, 此堆为初始的无序区.
②. 再将关键字最大的记录K1K1 (即堆顶, 第一个元素)和无序区的最后一个记录 KnKn 交换, 由此得到新的无序区K[1…n−1]K[1…n−1]和有序区K[n]K[n], 且满足K[1…n−1].keys⩽K[n].keyK[1…n−1].keys⩽K[n].key
③. 交换K1K1 和 KnKn 后, 堆顶可能违反堆性质, 因此需将K[1…n−1]K[1…n−1]调整为堆. 然后重复步骤②, 直到无序区只有一个元素时停止.

动图效果如下所示:
在这里插入图片描述

堆排序算法的演示。首先,将元素进行重排,以匹配堆的条件。图中排序过程之前简单的绘出了堆树的结构。

3、代码实现

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆函数,二是反复调用建堆函数以选择出剩余未排元素中最大的数来实现排序的函数。

总结起来就是定义了以下几种操作:
•最大堆调整(Max_Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
•创建最大堆(Build_Max_Heap):将堆所有数据重新排序
•堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算

对于堆节点的访问:
•父节点i的左子节点在位置:(2i+1);
•父节点i的右子节点在位置:(2
i+2);
•子节点i的父节点在位置:floor((i-1)/2);

/**
 * 堆排序
 *
 * 1\. 先将初始序列K[1..n]建成一个大顶堆, 那么此时第一个元素K1最大, 此堆为初始的无序区.
 * 2\. 再将关键字最大的记录K1 (即堆顶, 第一个元素)和无序区的最后一个记录 Kn 交换, 由此得到新的无序区K[1..n−1]和有序区K[n], 且满足K[1..n−1].keys⩽K[n].key
 * 3\. 交换K1 和 Kn 后, 堆顶可能违反堆性质, 因此需将K[1..n−1]调整为堆. 然后重复步骤②, 直到无序区只有一个元素时停止.
 * @param arr  待排序数组
 */
public static void heapSort(int[] arr){
 for(int i = arr.length; i > 0; i--){
 max_heapify(arr, i);

 int temp = arr[0];      //堆顶元素(第一个元素)与Kn交换
 arr[0] = arr[i-1];
 arr[i-1] = temp;
 }
}

private static void max_heapify(int[] arr, int limit){
 if(arr.length <= 0 || arr.length < limit) return;
 int parentIdx = limit / 2;

 for(; parentIdx >= 0; parentIdx--){
 if(parentIdx * 2 >= limit){
 continue;
 }
 int left = parentIdx * 2;       //左子节点位置
 int right = (left + 1) >= limit ? left : (left + 1);    //右子节点位置,如果没有右节点,默认为左节点位置

 int maxChildId = arr[left] >= arr[right] ? left : right;
 if(arr[maxChildId] > arr[parentIdx]){   //交换父节点与左右子节点中的最大值
 int temp = arr[parentIdx];
 arr[parentIdx] = arr[maxChildId];
 arr[maxChildId] = temp;
 }
 }
 System.out.println("Max_Heapify: " + Arrays.toString(arr));
}

在这里插入图片描述以上,
①. 建立堆的过程, 从length/2 一直处理到0, 时间复杂度为O(n);
②. 调整堆的过程是沿着堆的父子节点进行调整, 执行次数为堆的深度, 时间复杂度为O(lgn);
③. 堆排序的过程由n次第②步完成, 时间复杂度为O(nlgn).
在这里插入图片描述提示:由于堆排序中初始化堆的过程比较次数较多, 因此它不太适用于小序列. 同时由于多次任意下标相互交换位置, 相同元素之间原本相对的顺序被破坏了, 因此, 它是不稳定的排序.

### 如何在麒麟操作系统安装配置 Docker #### 准备工作 确保系统已更新至最新状态并具备必要的依赖项。对于麒麟操作系统而言,建议先执行系统的全面升级。 ```bash sudo apt update && sudo apt upgrade -y ``` #### 获取 Docker 安装包 考虑到网络环境差异,在线获取可能遇到困难;因此推荐采用官方文档或其他可靠渠道下载适用于麒麟操作系统Docker版本[^1]。 #### 执行具体安装过程 完成上述准备工作之后,按照如下指令序列来实现Docker安装: - 添加Docker APT仓库密钥: ```bash curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg ``` - 设置稳定版存储库: ```bash echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null ``` - 更新APT索引文件,并准备开始安装Docker Engine: ```bash sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io ``` 以上命令会从指定源处下载并安装Docker及其关联组件[^2]。 #### 初始化与验证服务运行状况 为了使Docker能够随系统启动而自动激活,需执行以下命令以加载服务单元定义、开启Docker守护进程以及确认其当前活动状态正常: ```bash sudo systemctl daemon-reload sudo systemctl start docker sudo systemctl enable docker sudo systemctl status docker ``` 此时应该可以看到有关`docker.service`的状态报告,表明该服务正在活跃运行中[^3]。 #### 测试安装成果 最后一步是测试新安装的服务是否可以正常使用。可以通过拉取一个简单的镜像来进行这项检验: ```bash sudo docker run hello-world ``` 这条命令将会尝试从互联网抓取名为`hello-world`的小型测试镜像,并展示一条消息证明一切运作良好[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值