堆与栈的区别

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.rhkb.cn/news/132960.html

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

相关文章

【Cocos Creator 3.5实现赛车游戏】10.实现汽车节点的运动逻辑

转载知识星球 | 深度连接铁杆粉丝&#xff0c;运营高品质社群&#xff0c;知识变现的工具 项目地址&#xff1a;赛车小游戏-基于Cocos Creator 3.5版本实现: 课程的源码&#xff0c;基于Cocos Creator 3.5版本实现 上一节的学习后&#xff0c;您已经完成了对汽车节点的控制逻…

【自动驾驶】PETR 环境安装与测试

1.环境安装 该工程依赖MMCV&#xff0c; MMDetection&#xff0c; MMDetection3d&#xff0c;MMSegmentation Install MMCV pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/{cu_version}/{torch_version}/index.htmlexamples&#xff1a; pip install…

小程序实现一个 倒计时组件

小程序实现一个 倒计时组件 需求背景 要做一个倒计时&#xff0c;可能是天级别&#xff0c;也可能是日级别&#xff0c;时级别&#xff0c;而且每个有效订单都要用&#xff0c;就做成组件了 效果图 需求分析 需要一个未来的时间戳&#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; 前端里程计能给出一个短时间…

MySQL优化第二篇

MySQL优化第二篇 性能分析小表驱动大表慢查询日志日志分析工具mysqldumpslow Show Profile进行SQL分析&#xff08;重中之重&#xff09; 七种JOIN 1、inner join &#xff1a;可以简写为join&#xff0c;表示的是交集&#xff0c;也就是两张表的共同数据 sql语句&#xff1a…

用动态ip登录账号的风险高不高?

使用动态ip登录账号在一定程度上提供了额外的安全保障和匿名性&#xff0c;但与此同时也存在一些风险和风控挑战。本文将解密使用动态ip登录账号的真相&#xff0c;明确安全与风险的并存之道。 1、增强隐私保护&#xff1a; 使用动态ip登录账号可以隐藏您的真实IP地址&#xff…

21 Spring Boot整合Redis

目录 一、Redis简介 二、创建springboot整合redis工程 三、添加依赖 四、配置Yml 五、创建Redis配置类 六、创建Redis工具类&#xff0c;封装Redis的api 七、操作Redis 八、验证 一、Redis简介 简单来说 Redis 就是一个使用 C 语言开发的数据库&#xff0c;不过与传统…

无涯教程-JavaScript - OR函数

描述 如果任何参数为TRUE,则OR函数返回TRUE&#xff1b;如果所有参数为FALSE,则返回FALSE。 语法 OR (logical1, [logical2], ...) 争论 Argument描述Required/Optionallogical1 您要测试的1到255个条件可以是TRUE或FALSE。 您要测试的1到255个条件可以是TRUE或FALSE。 Req…

JDK API文档地址(中文和英文)

JDK1.6 JDK 1.6 中文手册 JDK1.8 Java 8 中文版 - 在线API手册 - 码工具 Java 官方文档 |官方教程|Java 官方文档 API中文手册|Java 官方文档参考文档_w3cschool 网上还有很多百度网盘中也有 JDK17 https://doc.qzxdp.cn/jdk/17/zh/api/index.html 英文文档 所有版本 …

Unity 性能优化之Shader分析处理函数ShaderUtil.HasProceduralInstancing: 深入解析与实用案例

Unity 性能优化之Shader分析处理函数ShaderUtil.HasProceduralInstancing: 深入解析与实用案例 点击封面跳转到Unity国际版下载页面 简介 在Unity中&#xff0c;性能优化是游戏开发过程中非常重要的一环。其中&#xff0c;Shader的优化对于游戏的性能提升起着至关重要的作用。…

学习视觉SLAM需要会些什么?

前言 SLAM是现阶段很多研究生的研究方向&#xff0c;我也是作为一个即将步入视觉SLAM的研究生&#xff0c;网上对于SLAM的介绍很多&#xff0c;但很少有人完整系统的告诉你学习视觉SLAM该有那些基础&#xff0c;那么此贴将告诉你学习SLAM你要有那些方面的基础。 文章目录 前言…

Java 华为真题-选修课

需求&#xff1a; 现有两门选修课&#xff0c;每门选修课都有一部分学生选修&#xff0c;每个学生都有选修课的成绩&#xff0c;需要你找出同时选修了两门选修课的学生&#xff0c;先按照班级进行划分&#xff0c;班级编号小的先输出&#xff0c;每个班级按照两门选修课成绩和的…

计算机网络TCP篇之流量控制

计算机网络TCP篇之流量控制 今天谈一谈我对于tcp流量控制的看法 在网络拓扑中如果发送方节点的发送速率大于接受方节点的接受速率&#xff0c;数据会不断在接受方的缓冲区累积&#xff0c;直到接受方的缓冲区满的时候&#xff0c;发送方继续发送数据&#xff0c;这时候接受方无…

文件上传漏洞~操作手册

目录 上传文件一般过滤方式 客服端校验 服务端校验 黑白名单机制 常规文件上传漏洞绕过 客户端绕过 1.游览器禁用JavaScript 2.正常burp suite抓包改包 服务端绕过 1.Content-Type绕过 2.黑名单绕过 1&#xff09;命名规则绕过 2&#xff09;大小写绕过 3&#x…

怎么用excel管理固定资产

在当今的数字时代&#xff0c;我们已经习惯了使用各种电子工具来提高我们的生产力。其中&#xff0c;Excel无疑是一个强大的工具&#xff0c;它不仅可以帮助我们处理数据&#xff0c;还可以用来进行复杂的计算和分析。然而&#xff0c;你可能不知道的是&#xff0c;Excel也可以…

优思学院|什么是精益项目管理?

正确地使用精益思想和技术是可以减少项目中的浪费、提高客户满意度&#xff0c;并提高项目的利润率。 在现实世界中&#xff0c;项目经理的工作充满了挑战。他们不仅需要专注于产品和团队&#xff0c;还必须确保客户的满意度。同时&#xff0c;他们还必须与矩阵组织打交道&…

异步FIFO设计

1 FIFO简介 FIFO的本质是RAM&#xff0c;具有先进先出的特性。 FIFO的基本使用原则&#xff1a;空时不能读&#xff0c;满时不能写 FIFO的两个重要参数&#xff1a;宽度和深度 FIFO的两种类型&#xff1a; 同步FIFO&#xff1a;读写时钟相同&#xff0c;通常用来做数据缓存…

本地录像视频文件如何推送到视频监控平台EasyCVR进行AI视频智能分析?

安防监控平台EasyCVR支持多协议、多类型设备接入&#xff0c;可以实现多现场的前端摄像头等设备统一集中接入与视频汇聚管理&#xff0c;并能进行视频高清监控、录像、云存储与磁盘阵列存储、检索与回放、级联共享等视频功能。视频汇聚平台既具备传统安防监控、视频监控的视频能…

【算法】前缀和与差分

大家好&#xff01;今天我们来学习前缀和与差分算法。 目录 1. 一维前缀和 1.1 定义 1.2 计算方法 1.3 作用 1.4 适用场景 1.5 模板题 1.6 总结 2. 二维前缀和 2.1 定义 2.2 计算方法 2.3 模板题 2.4 总结 3. 一维差分 3.1 定义 3.2 差分数组 3.3 差分标记 3…

什么是JavaScript中的严格模式(strict mode)?应用场景是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 严格模式&#xff08;Strict Mode&#xff09;&#xff1a;⭐ 使用场景⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&…