堆结构与堆排序

news2024/12/23 1:13:03

二叉树的概念

满二叉树:二叉树的每一层的节点数都达到最大值                

 

完全二叉树:满二叉树或是从左往右依次变满的树             

 

二叉树的数组表示

01234567

堆结构(优先级队列结构)  

完全二叉树

大根堆:每一个子树的头节点的值为子树的最大值

小根堆:每一个子树的头节点的值为子树的最小值

heapinsert过程

数组

53677

从左往右

指针i = 0       i = 1        i = 2                                             i = 3                            heapinsert过程结束

heapinsert过程代码

//用户添加了一个数,放在了index位置,持续向上调整使得整体的二叉树变成大根堆/小根堆
    public static void heapInsert(int[] arr, int index) {
        while (arr[index] > arr[(index - 1) / 2]) {//当子节点的值大于父节点的值时,进行交换,并且持续向上比较
        //停止的条件一: 子节点的值不再大于父节点的值
        //停止的条件二: index = 0,有(0-1)/2 = 0,上式依然不满足
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

heapify 堆化过程

heapinsert过程可以实现取出当前heapsize大小的数组中的最大值——arr[0]

取出最大值后我们把当前数组中的最后一位数移到最前,同时heapsize-1,此时的这个结构仍然是一个二叉树结构、但并不一定是堆结构

我们将头节点的数与其子节点的数比较,如果小于某个子节点,则与其交换;不断重复此操作使最后得到的二叉树为大根堆

635234

heapify过程代码

public static void heapify(int[] arr, int index, int heapsize) {
        //index 当前数组下标;  heapSize heapInsert过程中数组的大小,判断左右节点是否越界
        int left = index * 2 + 1;//左子节点的下标
        int largest = 0;
        while (left < heapsize) {//下方还有节点的时候
            // 左子节点不越界,那当前一定有子节点;左子节点越界,那右子节点一定越界
            //两个子节点中,谁的值大,就把下标给largest
            if (left + 1 < heapsize && arr[left + 1] > arr[left]) {//存在右子节点&&右子节点的数较大
                largest = left + 1;//右子节点
            } else {
                largest = left;
            }
            //父节点与较大子节点中,谁的值大,就把下标给largest
            if (arr[index] > arr[largest]) {
                largest = index;
            }
            if (largest == index) {//当前父节点是三个节点中的最大
                break;
            }
            //当前节点作为父节点,与其两子节点进行比较,选出其中最大的与当前位置进行交换
            swap(arr, largest, index);
            index = largest;//index时向下走的,index获得的值是原来较大子节点的索引
            //将当前节点作为下一层的父节点,再次向下选取最大值
            left = index * 2 + 1;
        }
    }

当用户修改了在有效区域内的一个值,

如果修改的值大于未修改的值,当前的数往上进行一个heapInsert过程

如果修改的值小于未修改的值,当前的数往下进行一个heapify过程

最终使得修改后仍然是一个堆结构

用户增加/删除一个数,调整结构的复杂度是O(logN)

堆排序

  1. 进行一个heapInsert过程,将无序的数组变为大根堆的堆结构,在此过程中,记录堆结构大小的heapsize逐渐增加直到heapsize == arr.length
  2. 取出索引为0的值(堆结构中的最大值),将它与数组中的最后一个位置交换,heapsize-1,堆结构的有效位置减一
  3. 对index = 0的位置进行一个heapify过程,使得堆结构仍然为一个大根堆结构
  4. 取出索引为0的值,将它与数组中的最后一个位置交换,heapsize-1
  5. 对index = 0的位置进行一个heapify
  6. ........
  7. 直到最终heapsize的值为0

 

优化

原方式:通过heapInsert逐个将数添加到堆结构中

优化:实现二叉树的层序遍历(A → B → C → D),最后一层的数向下进行heapify,逐层向上,对头节点heapify后,

最终实现将整个数组转化为堆结构

 优化后的复杂度计算:

T(N) = \frac{N}{2}+\frac{N}{4}*2+\frac{N}{8}*3+\frac{N}{16}*4+\frac{N}{32}*5+...+\frac{N}{2^{t}}*t

得出时间复杂度为O(N)

最终代码

package algorithm;

import org.junit.Test;

public class Heap {
    @Test
    public void test() {
        int[] arr = new int[]{3, 5, 6, 4, 7};
        heapSort(arr);
        for (int i : arr) {
            System.out.println(i);
        }
    }

    public static void heapSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }
//        for (int i = 0; i < arr.length; i++) {//O(N)
//            heapInsert(arr, i);//O(logN)
//            //通过heapInsert逐个添加数到堆结构中
//        }
        //优化
        for (int i = arr.length; i >= 0; i--) {
            heapify(arr, i, arr.length);
        }

        int heapsize = arr.length;//全部添加完成后堆结构的大小 = 数组的大小
        swap(arr, 0, --heapsize);//将索引为0的数与堆结构最后位置的数进行交换,heapsize-1,将最后一位置去除堆结构
        while (heapsize > 0) {//O(N)
            heapify(arr, 0, heapsize);//O(logN)
            //交换上来的数向下进行heapify过程
            swap(arr, 0, --heapsize);//O(1)
        }
    }

    //用户添加了一个数,放在了index位置,持续向上调整使得整体的二叉树变成大根堆/小根堆
    public static void heapInsert(int[] arr, int index) {
        while (arr[index] > arr[(index - 1) / 2]) {//当子节点的值大于父节点的值时,进行交换,并且持续向上比较
            //停止的条件一: 子节点的值不再大于父节点的值
            //停止的条件二: index = 0,有(0-1)/2 = 0,上式依然不满足
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    public static void heapify(int[] arr, int index, int heapsize) {
        //index 当前数组下标;  heapSize heapInsert过程中数组的大小,判断左右节点是否越界
        int left = index * 2 + 1;//左子节点的下标
        int largest = 0;
        while (left < heapsize) {//下方还有节点的时候
            // 左子节点不越界,那当前一定有子节点;左子节点越界,那右子节点一定越界
            //两个子节点中,谁的值大,就把下标给largest
            if (left + 1 < heapsize && arr[left + 1] > arr[left]) {//存在右子节点&&右子节点的数较大
                largest = left + 1;//右子节点
            } else {
                largest = left;
            }
            //父节点与较大子节点中,谁的值大,就把下标给largest
            if (arr[index] > arr[largest]) {
                largest = index;
            }
            if (largest == index) {//当前父节点是三个节点中的最大
                break;
            }
            //当前节点作为父节点,与其两子节点进行比较,选出其中最大的与当前位置进行交换
            swap(arr, largest, index);
            index = largest;//index时向下走的,index获得的值是原来较大子节点的索引
            //将当前节点作为下一层的父节点,再次向下选取最大值
            left = index * 2 + 1;
        }
    }

    public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

}

拓展       

假设k = 6,那么我们先将前七个数排成一个小根堆,保证在索引为0的位置上是前七个数的最小值。由于数组本身几乎有序,其现在所在的位置距离它排完序后的位置不会超过k,那么索引为0位置上的数一定是整个数组上的最小值

我们将索引为0的数移出小根堆,将下一个数(也就是索引为7的数字,第8个数字)加入小根堆,在索引1到7的小根堆中的最小值即为索引为1上的值

如此向后推进,index = arr.length 后,仍然移出最前的数直到堆结构为空

复杂度O(N*logk)

package algorithm;

import org.junit.Test;

import java.util.PriorityQueue;

public class SortArrayDistanceLessK {

    @Test
    public void test(){
        int[] arr = new int[]{8,4,4,9,10};
        sortArrayDistanceLessK(arr,3);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

    public static void sortArrayDistanceLessK(int[] arr, int k) {
        PriorityQueue<Integer> heap = new PriorityQueue<>();//优先级队列,默认小根堆
        int index = 0;
        for (index = 0; index < Math.min(arr.length, k); index++) {//一般认为k < arr.length 防止取k的值过大导致错误
            heap.add(arr[index]);//将前k+1个数添加到堆结构中
        }
        int i = 0;
        for (i = 0; index < arr.length; i++, index++) {
            heap.add(arr[index]);//添加后一个数,当index = arr.length时,跳出for循环结构
            arr[i] = heap.poll();//移出最小值
        }
        while(!heap.isEmpty()){//index不再增加,i自增,堆结构不断减小
            arr[i++] = heap.poll();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/560514.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图数据库评估难?一篇教你搞定图数据库产品评估

随着数字经济时代全面开启&#xff0c;数据作为重要的生产要素&#xff0c;赋能作用日渐凸显&#xff0c;企业逐渐开始关注自身数字化水平和数据资产价值。而当各企业数智水平提升&#xff0c;其业务环境和计算场景呈现数据间关系交错复杂的特点。在面对需要深度挖掘数据间复杂…

C语言——存储类型

目录 1. auto 自动型2. static 静态2.1 修饰变量要知道&#x1f447;&#xff08;数据在Linux内核中的分配图&#xff09; 2.2 static 的特点⭐⭐⭐⭐⭐&#xff1a;2.2 修饰函数 3. extern4. register 寄存器类型 存储类型 存储类型有&#xff1a;auto static extern register…

2023最全selenium面试题及答案,测试员没有碰到算我输.....

一、前言 Selenium&#xff0c;是一个开源的框架&#xff0c;主要用于做HTML页面的UI自动化测试。不过&#xff0c;selenium IDE在去年官方已宣告放弃维护了。官网上放着一句话&#xff0c;selenium IDE is Dead。Selenium IDE是火狐浏览器的一个插件&#xff0c;是Selenium的…

上午面了个腾讯拿 38K 出来的,让我见识到了基础的天花板

今年的校招基本已经进入大规模的开奖季了&#xff0c;很多小伙伴收获不错&#xff0c;拿到了心仪的 offer。 各大论坛和社区里也看见不少小伙伴慷慨地分享了常见的面试题和八股文&#xff0c;为此咱这里也统一做一次大整理和大归类&#xff0c;这也算是划重点了。 俗话说得好…

低功耗IC后端培训 | 盘点Power Switch Cell在实际项目中应用注意事项

下面直接进入今天的技术干货分享——全面盘点power gating cell在数字IC后端实现中的各种注意事项。 什么是Power Gating? 随着工艺制程越做越小和芯片规模越来越大&#xff0c;芯片的leakage的比重越来越高&#xff0c;数字后端实现时就得考虑leakage的优化。而leakage优化…

交通标志识别系统-卷积神经网络

介绍 使用Python作为主要开发语言&#xff0c;基于深度学习TensorFlow框架&#xff0c;搭建卷积神经网络算法。并通过对数据集进行训练&#xff0c;最后得到一个识别精度较高的模型。并基于Django框架&#xff0c;开发网页端操作平台&#xff0c;实现用户上传一张图片识别其名…

Linux 软件包管理工具

rpm命令管理软件包 1.学会看rpm包&#xff0c;通过rpm包的名字来了解这个软件包的一些基础信息xfsprogs-4.19.0-2.el8.x86_64.rpm xfsprogs 软件名字 4.19.0 版本号 2 发行次数 el8 适用于哪个操作系统&#xff08;rel8&#xff09; x86_64 软…

Streamlit应用程序使用Streamlit-Authenticator进行用户的安全身份验证实践(解决升级问题)

在Streamlit官方文档中&#xff0c;没有提供提供安全身份验证组件。目前&#xff0c;第三方streamlit-authenticator提供此功能&#xff0c;详见引用我原来的博文&#xff0c;在《Streamlit应用程序使用Streamlit-Authenticator进行用户的安全身份验证实践》文中&#xff0c;原…

Vue计算属性

1&#xff0c;为什么Vue会设计计算属性(computed property)&#xff1f; 答&#xff1a;一定程度上&#xff0c;Vue的作用就是管理呈现到HTML页面上的所有数据data的&#xff0c;每当一个data发生变化&#xff0c;Vue实例就会自动的去更新模板里面使用到data的地方&#xff0c;…

pytorch完整模型训练套路

文章目录 CIFAR10数据集简介训练模型套路1、准备数据集2、加载数据集3、搭建神经网络4、创建网络模型、定义损失函数、优化器5、训练网络6、测试数据集7、添加tensorboard8、转化为正确率9、保存模型 完整代码 本文以 CIFAR10数据集为例&#xff0c;介绍一个完整的模型训练套路…

机器学习-线性代数-向量、基底及向量空间

概述 文章目录 概述向量理解向量运算 基底与向量的坐标表示基底与向量的深入基底与向量选取与表示基底的特殊性张成空间 向量 理解 直观理解 行向量&#xff1a;把数字排成一行A [ 4 5 ] [4~ 5] [4 5]列向量&#xff1a;把数字排成一列A [ 4 5 ] \ \left [ \begin{matrix}…

多线性开发实例分享

一. 概述 首先&#xff0c;在这里有必要和大家复现一下我使用该技术的背景&#xff1a; 在使用若依框架的时候&#xff0c;由于实际开发的需要&#xff0c;我需要配置四个数据源&#xff0c;并且通过mapper轮流去查每个库的指定用户数据&#xff0c;从而去判断改库是否存在目标…

构建一个简易数据库-用C语言从头写一个sqlite的克隆 0.前言

英文源地址 一个数据库是如何工作的? 数据是以什么格式存储的(在内存以及在磁盘)?何时从内存中转移到此磁盘上?为什么每张表只能有一个主键?回滚一个事务是如何工作的?索引是以什么格式组织的?什么时候会发生全表扫描, 以及它是如何进行的?准备好的语句是以什么格式保…

#C2#S2.2~S2.3# 加入 factory/objection/virtual interface 机制

2.2 加入factory 机制 factory机制的实现被集成在了一个宏中&#xff1a;uvm_component_utils。这个宏所做的事情非常多&#xff0c;其中之一就是将my_driver登记在 UVM内部的一张表中&#xff0c;这张表是factory功能实现的基础。只要在定义一个新的类时使用这个宏&#xff0…

斐波那契数列相关简化4

看这篇文章前需要看下前面三篇文章&#xff0c;最起码第一第二篇是需要看一下的 斐波那契数列数列相关简化1_鱼跃鹰飞的博客-CSDN博客 斐波那契数列数列相关简化2_鱼跃鹰飞的博客-CSDN博客 算法玩的就是套路&#xff0c;练练就熟悉了 再来一个&#xff1a; 用1*2的瓷砖&am…

如何在 CentOS Linux 上安装和配置 DRBD?实现高可用性和数据冗余

DRBD&#xff08;Distributed Replicated Block Device&#xff09;是一种用于实现高可用性和数据冗余的开源技术。它允许在不同的服务器之间实时同步数据&#xff0c;以提供数据的冗余和容错能力。本文将详细介绍如何在 CentOS Linux 上安装和配置 DRBD。 1. 确认系统要求 在…

一文带你了解MySQL之InnoDB统计数据是如何收集的

前言 本文章收录在MySQL性能优化原理实战专栏&#xff0c;点击此处查看更多优质内容。 我们前边唠叨查询成本的时候经常用到一些统计数据&#xff0c;比如通过show table status可以看到关于表的统计数据&#xff0c;通过show index可以看到关于索引的统计数据&#xff0c;那…

MySQL之事务初步

0. 数据源 /*Navicat Premium Data TransferSource Server : localhost_3306Source Server Type : MySQLSource Server Version : 80016Source Host : localhost:3306Source Schema : tempdbTarget Server Type : MySQLTarget Server Version…

在线OJ常用输入规则

一、字符串输入规则 1.1 单行无空格字符串输入 输入连续字符串&#xff0c;cin默认空格/换行符为分割标志。 string s; //输入连续字符串&#xff0c;cin默认空格/换行符为分割标志。 cin >> s; 1.2 单行有空格字符串输入 getline函数接受带有空格的输入流&#xff…

C++——初识模板

文章目录 总述为什么要有模板函数模板概念函数模板使用方法函数模板的原理函数模板的实例化隐式示例化显式实例化 模板参数的匹配规则 类模板类模板的实例化 总述 本篇文章将带大家简单的了解一下c的模板方面的知识&#xff0c;带大家认识什么是模板&#xff0c;模板的作用&…