堆与栈的区别

news2024/12/26 10:48:18

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.coloradmin.cn/o/1006418.html

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

相关文章

重庆两融利率最低给到多少,利率可以调整吗?

​ 在金融市场中&#xff0c;融资融券是投资者常用的一种加杠杆的金融手段。在重庆地区&#xff0c;融资融券也是广泛应用的投资方式。融资是指投资者向券商借入资金进行股票交易&#xff0c;而融券则是指投资者向券商借入股票进行卖出交易。这两个交易方式都需要支付利息费用…

学习记忆——图像篇——图像记忆

一、图像记忆原理 我们要记忆的内容一般包括图像、声音、文字和 数字这几种&#xff0c;按由易到难的顺序将它们排列&#xff1a; 图像——声音——文字——数字 记忆材料 图像 二、超级记忆的基本方法 记忆的三种类型&#xff1a; 1、瞬间记忆 2、短期记忆 短效记忆、长效记…

LED显示屏控制软件发展历程

LED显示屏控制软件的发展历程经历了多个阶段&#xff0c;从最初的简单控制到今天的复杂、高度定制化的解决方案。免费提供户外led显示屏解决方案。 以下是LED显示屏控制软件的主要发展历程&#xff1a; 早期单机控制&#xff1a; 早期的LED显示屏控制软件通常是基于单台计算机的…

smallWhiteDot Tech Suppor

Preview mail: 352056038qq.com

【测试开发】 测试题总结

祝天天开心 文章目录 1. 测试用例编写2. 如何对bug进行描述3. bug状态转换4. 测试人员和开发人员产生争执5. 登录功能测试用例设计6. 测试生命周期7. 1. 测试用例编写 编写水杯的测试用例 注意&#xff0c;测试用例有一个万能公式 功能测试性能测试页面测试安全性测试兼容性测…

【FAQ】视频监控管理平台/视频汇聚平台EasyCVR安全检查相关问题及解决方法3.0

智能视频监控系统/视频云存储/集中存储/视频汇聚平台EasyCVR具备视频融合汇聚能力&#xff0c;作为安防视频监控综合管理平台&#xff0c;它支持多协议接入、多格式视频流分发&#xff0c;视频监控综合管理平台EasyCVR支持海量视频汇聚管理&#xff0c;可应用在多样化的场景上&…

重庆电建:数据中台建设探索与实践

“数字中国”建设浪潮下&#xff0c;强化数据能力、激发数据价值成为各行各业实现高质量发展的重要抓手和关键引擎。然而&#xff0c;确保数据质量、一致性和充分利用其价值&#xff0c;对中大型企业而言&#xff0c;依然具有挑战。数据中台&#xff0c;能够为这些难题提供有效…

Altium Designer如何查看制定了哪些快捷键?

随着时代高速发展&#xff0c;Altium Designer&#xff08;AD&#xff09;、Allegro、Pads等是全球主流的三大EDA软件&#xff0c;因此越来越多工程师被要求学习这些软件&#xff0c;在使用EDA软件设计PCB过程时&#xff0c;熟悉和合理配置快捷键是提高工作效率的关键之一&…

Cesium 地理坐标系和投影坐标系

Cesium 地理坐标系和投影坐标系 投影坐标系墨卡托投影&#xff08;Mercator projection&#xff09;高斯-克吕格投影&#xff08;Gauss-Kruger&#xff09;UTM投影&#xff08;Universal Transverse Mercator&#xff09;网络墨卡托投影&#xff08;Web Mercator&#xff09; 地…

​LeetCode解法汇总2596. 检查骑士巡视方案

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 描述&#xff1a; 骑士在一张…

接口测试 —— Requests库GET请求

Requests库GET请求是使用HTTP协议中的GET请求方式对目标网站发起请求。 &#xff08;不带参数的GET请求请看上一篇文章的练习&#xff09; 1、Requests库待参数的GET请求 使用Get方法带参数请求时&#xff0c;是params参数字典&#xff0c;而不是data参数字典。data参数字典…

苹果2023发表会:四款产品一次看完

本次新品发布会中&#xff0c;苹果更新了Apple Watch和iPhone两款产品。包含Apple Watch S9、Apple Watch Ultra 2&#xff0c;以及iPhone 15 / iPhone 15 Plus 和高阶款的iPhone 15 Pro / iPhone 15 Pro Max。 以下为你汇整了2023 苹果秋季发布会&#xff08; iPhone 15 发表…

半夏威风博客 vue+spingboot生成二维码小系统

概述 前台输入框&#xff0c;进行录入信息&#xff0c;进行提交&#xff0c;即可生成包含你信息的二维码 详细 半夏威风博客 vuespingboot生成二维码&#x1f60e;小系统 嗨&#xff01;大家好&#xff01;好久不见&#xff0c;想不想我哦 台词不能忘&#xff1a;你愿不愿…

linux os系统nginx版本升级

Nginx 查看nginx版本 rpm -qa | grep nginx下载地址&#xff1a; https://nginx.org/en/download.html&#xff0c; 下载linux stable稳定版本。 解压&#xff1a; tar -xvf nginx-1.22.0.tar.gz编译&#xff1a; //如果不知道nginx原目录在哪&#xff0c;用whereis 命令查…

【编程实践】利用pcl实现点云凸包点生成

1 运行结果 生成的凸包点与原点云的可视化 2 代码实现 // convex hull#include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/io/vtk_io.h> #include <pcl/surface/convex_hull.h> #include <pcl/visualization/pcl_visualize…

【pythonflask-1】简单实现加减乘除输入界面

app.py import flask from flask import Flask, render_template, request # 计算精确的浮点结果&#xff0c;float加法也计算不出来 from decimal import Decimalapp Flask(__name__)app.route(/) def home():return render_template(index.html)app.route(/calculate, meth…

【rgbd_benchmark_tools】TUM RGBD数据集基准测试工具使用调试记录,SLAM评估

cgmcgm:~/文档/rgbd_benchmark_tools$ /bin/python /home/cgm/文档/rgbd_benchmark_tools/src/rgbd_benchmark_tools/evaluate_ate.py Traceback (most recent call last):File "/home/cgm/文档/rgbd_benchmark_tools/src/rgbd_benchmark_tools/evaluate_ate.py", li…

警惕!1本SCI解除“On Hold”,Chemosphere等11本期刊仍被标记!

期刊动态&#xff1a;警惕期刊“On Hold”状态&#xff01; 2023年8月&#xff0c;小编从科睿唯安官网整理出12本期刊处于“On Hold”状态&#xff01; 参考往期推文&#xff1a; 警惕&#xff01;10本“On Hold”期刊已被踢&#xff0c;仍有12本期刊被标记&#xff01; 期…

TC测试自动化Shell脚本

在使用TC测试的发现手动进行丢包延迟抖动等场景的组合以及TC命令的切换效率很低&#xff0c;写了一个脚本可以提升效率&#xff0c;也可以根据自己的需求进行脚本更改&#xff01; 使用方法&#xff1a; 1&#xff09;运行sh脚本 2&#xff09;输入TC想要限制的网卡名和服务器…

【板栗糖GIS】——如何在两个电脑间同步简悦插件高级账户

【板栗糖GIS】——如何在两个电脑间同步简悦插件高级账户 目录 1. 找到账户UID 2. 复制UID 3. 在另一台装简悦插件的电脑修改成同样的UID 简悦是一款很好用的资料收集以及阅读插件,具体介绍可以在官网进行查看 简悦 SimpRead - 如杂志般沉浸式阅读体验的扩展 今天我只是想…