堆与栈的区别

OVERVIEW

  • 栈与堆的区别
      • 一、程序内存分区中的堆与栈
        • 1.栈
        • 2.堆
        • 3.堆&栈
      • 二、数据结构中的堆与栈
        • 1.栈
        • 2.堆
      • 三、堆的深入
        • 1.堆插入
        • 2.堆删除:
        • 3.堆建立:
        • 4.堆排序:
        • 5.堆实现优先队列:
        • 6.堆与栈的相关练习

栈与堆的区别


自整理,以下为主要参考文章:

  • 堆与栈的对比:一文读懂堆与栈的区别_堆和栈的区别_恋喵大鲤鱼的博客-CSDN博客
  • 堆与栈的理解:什么是堆? 什么是栈? - 知乎 (zhihu.com)
  • 用堆实现优先队列:用堆实现优先级队列(Priority Queue)_优先队列用堆实现_t_wu的博客-CSDN博客
  • 用堆实现优先队列:什么是优先队列 - 知乎 (zhihu.com)
  • 深入底层:内存中的堆和栈到底是什么 | 洛斯里克的大书库 (kimihe.com)
  • Python版本:Codify (wzy-codify.com)

在理解堆与栈概念时需要放到具体的场景下,因为不同场景下堆与栈代表不同的含义:

  1. 场景1:程序内存布局场景下,堆与栈表示两种内存管理方式。
  2. 场景2:数据结构场景下,堆与栈表示两种常用的数据结构。

一、程序内存分区中的堆与栈

堆与栈的空间都是分配在内存上,

在这里插入图片描述

1.栈

  1. 其中函数中定义的局部变量按照先后定义的顺序依次压入栈中,也就是说相邻变量的地址之间不会存在其它变量。
  2. 栈的内存地址生长方向与堆相反,由高到低,所以后定义的变量地址低于先定义的变量。
  3. 栈中存储的数据的生命周期随着函数的执行完成而结束。

2.堆

  1. 但需要注意的是,后申请的内存空间并不一定在先申请的内存空间的后面,原因是先申请的内存空间一旦被释放,后申请的内存空间则会利用先前被释放的内存,从而导致先后分配的内存空间在地址上不存在先后关系。
  2. 堆的内存地址生长方向与栈相反,由低到高
  3. 堆中存储的数据若未释放,则其生命周期等同于程序的生命周期。

注:关于堆上内存空间的分配过程,首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确地释放本内存空间。由于找到的堆节点的大小不一定正好等于申请的大小,系统会自动地将多余的那部分重新放入空闲链表。

3.堆&栈

堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:

不同点
1.管理方式不同栈由操作系统自动分配释放,无需手动控制;堆的申请和释放工作由开发者控制,容易产生内存泄漏;
2.空间大小不同每个进程拥有的栈大小要远远小于堆大小理论上进程可申请的堆大小为虚拟内存大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB;
3.生长方向不同堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低
4.分配方式不同堆都是动态分配的,没有静态分配的堆栈有 2 种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca()函数分配,但是栈的动态分配和堆是不同的,它的动态分配是由操作系统进行释放,无需手动实现。
5.分配效率不同栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多
6.存放内容不同栈存放的内容有,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,这需要使用栈来实现。
首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。
出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。
堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。
7.产生内存碎片栈与数据结构中的栈原理相同,在弹出一个元素之前上个元素已经弹出了,不会产生内存碎片而不停的调用malloc/new、free/delete则会产生很多的内存碎片。
8.线程安全栈内存是为线程留出的临时空间,每一个线程都有其固定的栈空间,栈空间存储的数据只能被当前线程访问(线程安全)堆内存大小不固定、空间由开发者动态分配可以动态扩容,堆内存可以被一个进程内的所有线程访问(线程不安全),
9.访问权限不同函数之间的栈数据不能够共享,在启动线程的时候实际上是启动函数,其具有自己的栈,线程之间的栈数据时不能够共享的堆属于在进程上的堆,只要在进程上application内,所有的线程都可以访问堆上的数据。堆在不同的语言下管理释放的方式不同:垃圾回收机制、free、

从以上可以看到,

  • 堆由于大量malloc()/free()或new/delete的使用,容易造成大量的内存碎片,并且可能引发用户态和核心态的切换,效率较低。
  • 栈在程序中应用较为广泛,最常见的是函数的调用过程由栈来实现,函数返回地址、EBP、实参和局部变量都采用栈的方式存放。虽然栈有众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,主要还是用堆。
  • 无论是堆还是栈,在内存使用时都要防止非法越界,越界导致的非法内存访问可能会摧毁程序的堆、栈数据,轻则导致程序运行处于不确定状态,获取不到预期结果,重则导致程序异常崩溃,这些都是我们编程时与内存打交道时应该注意的问题。

二、数据结构中的堆与栈

1.栈

栈:栈是一种运算受限的线性表,其限制是指只仅允许在表的一端进行插入和删除操作。具有FILO先进后出的特性。

2.堆

堆:堆是一种常用的树形结构,是一种特殊的完全二叉树,满足所有节点的值不大于或不小于其父节点的值的完全二叉树被称之为堆。

堆这一特性称为堆序性,因此在一个堆中根节点是最大 or 最小的节点。如果根节点最小称之为小根堆,根节点最大称之为大根堆。堆的左右孩子没有大小的顺序。

在这里插入图片描述

  • 堆的存储一般都用数组来存储堆,
  • i节点的父节点下标就为(i–1)/2
  • 它的左右子节点下标分别为 2∗i+12∗i+2

堆可以用来排序,也可以用来实现优先级队列,而优先级队列是搜索的基础。

三、堆的深入

1.堆插入

每次插入都是将新数据放在数组最后,如果新构成的二叉树不满足堆的性质,需要对堆进行调整使其满足堆的性质(上浮操作)

// 新加入i节点,其父节点为(i-1)/2
// 参数:a:数组,i:新插入元素在数组中的下标  
void minHeapFixUp(int a[], int i) {  int j, temp;temp = a[i];  j = (i-1)/2;      //父节点  while (j >= 0 && i != 0) {  if (a[j] <= temp) break;//如果父节点不大于新插入的元素,停止寻找a[i]=a[j];//把较大的子节点往下移动,替换它的子节点  i = j;  j = (i-1)/2;  }  a[i] = temp;  
}  

因此,插入数据到最小堆时:

// 在最小堆中加入新的数据data  
// a:数组,index:插入的下标,
void minHeapAddNumber(int a[], int index, int data) {  a[index] = data;  minHeapFixUp(a, index);  
}

2.堆删除:

删除一个元素总是发生在堆顶,因为堆顶的元素是最小的(小顶堆中)。数组中最后一个元素用来填补空缺位置,然后对顶部元素进行下沉,如果左右孩子有比自己小的,则选择选择最小的那个进行交换。重复进行下沉操作,以满足堆的条件。

按照堆删除的说明,堆中每次都只能删除第0个数据。为了便于重建堆,实际的操作是将数组最后一个数据与根节点交换,然后再从根节点开始进行一次从上向下的调整。

调整时先在左右儿子节点中找最小的,如果父节点不大于这个最小的子节点说明不需要调整了,反之将最小的子节点换到父节点的位置。此时父节点实际上并不需要换到最小子节点的位置,因为这不是父节点的最终位置。但逻辑上父节点替换了最小的子节点,然后再考虑父节点对后面的节点的影响。堆元素的删除导致的堆调整,其整个过程就是将根节点进行“下沉”处理。

// minHeapFixDown 小顶堆结点下沉操作。
// a 为数组,len 为结点总数;从 index 结点开始调整,index 从 0 开始计算 index 其子结点为 2*index+1, 2*index+2;len/2-1 为最后一个非叶子结点。
void minHeapFixDown(int a[],int len,int index) {// index 为叶子节点不用调整。if(index>(len/2-1)) return;int tmp=a[index];lastIndex=index;// 当下沉到叶子节点时,就不用调整了。while(index<=len/2-1) {// 如果左子节点小于待调整节点if(a[2*index+1]<tmp) {lastIndex = 2*index+1;}//如果存在右子节点且小于左子节点和待调整节点if(2*index+2<len && a[2*index+2]<a[2*index+1]&& a[2*index+2]<tmp) {lastIndex=2*index+2;}//如果左右子节点有一个小于待调整节点,选择最小子节点进行上浮if(lastIndex!=index) {a[index]=a[lastIndex];index=lastIndex;} else break;             // 否则待调整节点不用下沉调整}// 将待调整节点放到最后的位置。a[lastIndex]=tmp;
}

根据堆删除的下沉思想,可以有不同版本的代码实现,以上是和孙凛同学一起讨论出的一个版本,在这里感谢他的参与,读者可另行给出。个人体会,这里建议大家根据对堆调整过程的理解,写出自己的代码,切勿看示例代码去理解算法,而是理解算法思想写出代码,否则很快就会忘记。

3.堆建立:

注:有了堆的插入和删除后,再考虑下如何对一个数据进行堆化操作。

以数组存储元素时具有对应的树表示形式,但树有可能并不满足堆的性质,需要重新排列元素才能建立堆化的树。

  1. 单结点的二叉树是堆(无需调整树中的叶子结点)
  2. 在完全二叉树中所有以叶子结点为根的子树是堆(无需调整)
  3. 堆的调整只需要从最后一个非叶子结点开始即可
  4. 需要依次将以序号为n/2、n/2-1、…1的结点为根的子树均调整为堆即可(筛选需从第n/2个元素开始

在这里插入图片描述

在这里插入图片描述

将初始无序序列调整成小根堆(筛选过程),可以利用以算法实现:

// makeMinHeap 建立最小堆。
// a:数组,n:数组长度。
void makeMinHeap(int a[], int n) {for (int i = n/2-1; i >= 0; i--) {minHeapFixDown(a, i, n);}
}

4.堆排序:

思路是每次都把堆顶的元素和堆尾的元素交换,然后把除了堆尾的那个元素组成的堆进行堆化(就是把堆顶的元素进行下沉),不断重复直至堆为空为止。

堆排序(Heapsort)是堆的一个经典应用,有了上面对堆的了解,不难实现堆排序。由于堆也是用数组来存储的,故对数组进行堆化后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。有点类似于直接选择排序。

因此,完成堆排序并没有用到前面说明的插入操作,只用到了建堆和节点向下调整的操作,堆排序的操作如下:

// array:待排序数组,len:数组长度
void heapSort(int array[],int len) {// 建堆makeMinHeap(array,len); // 最后一个叶子节点和根节点交换,并进行堆调整,交换次数为len-1次for(int i=len-1;i>0;--i) {//最后一个叶子节点交换array[i]=array[i]+array[0];array[0]=array[i]-array[0];array[i]=array[i]-array[0];// 堆调整minHeapFixDown(array, 0, len-i-1);  }
}
  1. 稳定性:堆排序是不稳定排序。
  2. 堆排序性能分析。由于每次重新恢复堆的时间复杂度为O(logN),共N-1次堆调整操作,再加上前面建立堆时N/2次向下调整,每次调整时间复杂度也为O(logN)。两次操作时间复杂度相加还是O(NlogN),故堆排序的时间复杂度为O(NlogN)
  3. 最坏情况:如果待排序数组是有序的,仍然需要O(NlogN)复杂度的比较操作,只是少了移动的操作;
  4. 最好情况:如果待排序数组是逆序的,不仅需要O(NlogN)复杂度的比较操作,而且需要O(NlogN)复杂度的交换操作,总的时间复杂度还是O(NlogN)。
  5. 因此,堆排序和快速排序在效率上是差不多的,但是堆排序一般优于快速排序的重要一点是数据的初始分布情况对堆排序的效率没有大的影响。

5.堆实现优先队列:

待完善

6.堆与栈的相关练习

  1. Leetcode232.用栈实现队列:https://leetcode.cn/problems/implement-queue-using-stacks/description/
  2. Leetcode225.用队列实现栈:https://leetcode.cn/problems/implement-stack-using-queues/
  3. 剑指offer09.用两个栈实现队列:https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/
  4. Leetcode1441.用栈构建数组:https://leetcode.cn/problems/build-an-array-with-stack-operations/

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

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

相关文章

竞赛 基于机器视觉的车道线检测

文章目录 1 前言2 先上成果3 车道线4 问题抽象(建立模型)5 帧掩码(Frame Mask)6 车道检测的图像预处理7 图像阈值化8 霍夫线变换9 实现车道检测9.1 帧掩码创建9.2 图像预处理9.2.1 图像阈值化9.2.2 霍夫线变换 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分…

video 视频编解码一些debug方法

文章目录 一、通过命令去获取一些数据1.2 确定我们xml配置文件: 二、查看我们芯片支持的编码能力三、通过log去获取信息 这个文章的主要内容是为了后期性能方面的debug, 设计到前期的bringup则没有 一、通过命令去获取一些数据 获取媒体相关的参数&#xff1a; # getprop |…

Thinkphp6 配置并使用redis图文详解 小皮面板

这篇文章主要介绍了Thinkphp6 配置并使用redis的方法,结合实例形式详细分析了Redis的安装、配置以及thinkphp6操作Redis的基本技巧,需要的朋友可以参考下 一、安装redis ThinkPHP内置支持的缓存类型包括file、memcache、wincache、sqlite。ThinkPHP默认使用自带的采用think\Ca…

SpringMVC之自定义注解

目录 一.什么是Java注解 1.简介 2.注解的分类 3.JDK元注解 二.自定义注解 1.自定义注解的分类 1.1.标记Annotation: 1.2.元数据Annotation: 2.如何使用自定义注解 3.案例演示 3.1 获取类、方法及属性上的注解值 3.2Inherited 的使用 3.3获取类属性上的注解属性值 3.…

springboot整合mybatis

一、项目结构展示 二、开始整合 1、引入pom依赖 进入Maven中央仓库选择自己所需要的依赖&#xff0c;maven仓库地址&#xff1a;Maven Central 完整Maven依赖如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"h…

markdown工具Atom预览与插件安装

​atom是以命令行作为插件选项的入口 打开命令输入框 Windows: ctrl shift p Mac: command shift p 输入命令安装 输入 markdown preview toggle &#xff0c;可以偷懒只输入mdpt(模糊匹配) 按enter键即可看到预览&#xff0c;如图&#xff0c;左边编辑&#xff0c;右…

Vue中的过滤器 Filters

过滤器 Filters 过滤器一般用于格式化文本内容&#xff0c;通常可以在两个地方使用&#xff0c;主要是模板语法、以及 v-bind 表达式中。例如我想对展示的文本进行一些特殊处理&#xff0c;将金额进行四舍五入后再展示。选项 filters 内可以编写多个自定义过滤器。 用法&…

LightDB 23.3 通过GUC参数控制commit fetch

背景 commit游标提交之后&#xff0c;可以继续使用fetch进行结果集的操作。commit和fetch结合使用功能开发时不考虑分布式。后续&#xff0c;又对分布式进行了测试&#xff0c;发现持有portal后&#xff0c;代码中会对querydesc进行非空判断。当querydesc为空时&#xff0c;Li…

工业交换机常见的硬件故障有哪些?

工业交换机常见的硬件故障主要是由于受到供电电源、室内温度、室内湿度、电磁干扰、静电等机房环境的影响&#xff0c;造成工业交换机电源、背板、模块、端口等部件出现故障。具体可以分为以下几类。 1.电力供应故障&#xff1a; 由于外部供电不稳定、电源线路老化或雷击等原因…

LiveNVR监控流媒体Onvif/RTSP功能-支持海康摄像头海康NVR通过EHOME协议ISUP协议接入分发视频流或是转GB28181

LiveNVR支持海康NVR摄像头通EHOME接入ISUP接入LiveNVR分发视频流或是转GB28181 1、海康 ISUP 接入配置2、海康设备接入2.1、海康EHOME接入配置示例2.2、海康ISUP接入配置示例 3、通道配置3.1、直播流接入类型 海康ISUP3.2、海康 ISUP 设备ID3.3、启用保存3.4、接入成功 4、相关…

亚马逊封买家账号的原因有哪些

亚马逊可能封锁买家账号的原因有多种&#xff0c;主要是出于保护市场和维护平台秩序的考虑。以下是一些可能导致亚马逊封锁买家账号的常见原因&#xff1a; 1、涉及违规行为&#xff1a;如果买家违反了亚马逊的使用政策&#xff0c;如发表虚假评价、滥用退货政策、欺诈或盗窃等…

【视觉SLAM入门】7.3.后端优化 基于KF/EKF和基于BA图优化的后端,推导及举例分析

"时间倾诉我的故事" 1. 理论推导2. 主流解法3. 用EKF估计状态3.1. 基于EKF代表解法的感悟 4. 用BA法估计状态4.1 构建最小二乘问题4.2 求解BA推导4.3 H的稀疏结构4.4 根据H稀疏性求解4.5 鲁棒核函数4.6 编程注意 5.总结 引入&#xff1a; 前端里程计能给出一个短时间…

markdown学习笔记

markdown学习笔记 1.文字&#xff08;依靠HTML&#xff09; 1.1文字缩进-空格转义符 单字符空&#xff1a;&emsp; 半字符空&#xff1a;&ensp;1.2文字对齐 「居中&#xff1a;」<center> 居中 </center> or <p align"center"> 居中 …

吃瓜教程第一二章学习记录

当大多数人听到 "机器学习 "时&#xff0c;他们会联想到机器人&#xff1a;一个可靠的管家或一个致命的终结者&#xff0c;这取决于你问谁。但是&#xff0c;机器学习并不只是未来主义的幻想&#xff0c;它已经存在了。事实上&#xff0c;在一些特殊的应用中&#xf…

upload-labs文件上传漏洞通关

一、环境搭建 upload-labs是一个使用php语言编写的&#xff0c;专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。 下载地址&#xff1a;https://github.com/c0ny1/upload-labs/releases 在 win 环境下 直接解压到phpstudy下即可 二、通关 &#xff08;一&#xff09;16关…

使用凌鲨进行聚合搜索

作为研发人员&#xff0c;我们经常需要在多个来源之间查找信息&#xff0c;以便进行研发工作。除了常用的搜索引擎如百度和必应之外&#xff0c;我们还需要查阅各种代码文档和依赖包等资源。这些资源通常分散在各个网站和文档库中&#xff0c;需要花费一定的时间和精力才能找到…

Redis缓存更新策略、详解并发条件下数据库与缓存的一致性问题以及消息队列解决方案

0、前言 我们知道&#xff0c;缓存由于在内存中&#xff0c;数据处理速度比直接操作数据库要快很多&#xff0c;因此常常将数据先读到缓存中&#xff0c;再进行查询、更新等操作。 但与之而来的问题就是&#xff0c;内存中的数据不仅没有持久化&#xff0c;而且需要保证…

如何在微软Edge浏览器上一键观看高清视频?

编者按&#xff1a;视频是当下最流行的媒体形式之一。但由于视频压缩、网络不稳定等原因&#xff0c;我们常常可以看到互联网上的很多视频其画面质量并不理想&#xff0c;尤其是在浏览器端&#xff0c;这极大地影响了观看体验。不过&#xff0c;近期微软 Edge 浏览器推出了一项…

linux命令查看谁在使用服务器的GPU

命令&#xff1a;查看GPU使用情况 nvidia-smi 可以知悉GPU占用情况和主要使用GPU的进程&#xff0c;如下图所示&#xff1a; 实时查看gpu使用&#xff1a; nvidia-smi -l 1 表示每隔1s刷新一下&#xff0c;数字可更改。 查看进程的归属者 方法一&#xff1a;ps -f -p pid…

发布文章到wordpress

给朋友新建的wp网站,没有内容怎么办,总不能一篇篇的挨个写入吧。用wp提供的录入模块就可以了 参考 wp说明文档 获取docx内容保存到wp 资料有个docx文件,但文件格式混乱,好在有目录,可以基于目录,对文章分割,用正则拆分存入wp 首先用pandoc把docx转为md文件,速度较慢,…