首先,堆和栈是两个数据结构上的概念,有时候说堆栈指的是栈,英文定义更明确,heap是堆,stack是栈。
其次,堆和栈也指(操作系统/单片机的)内存模型,更多的叫做堆区和栈区。
此外,本将简单讲讲在不同的编程语言中,如何实现或使用这些数据结构。
数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。
堆是一种特殊的树形数据结构,堆通常是一个可以被看做一棵完全二叉树的数组对象。
1.堆中某个节点的值总是不大于或不小于其父节点的值。
2.堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
从完全二叉树生成堆,使用筛选法建堆的时间复杂度O(n),插入结点、删除普通元素和删除最小元素的时间复杂度O(logn)。
栈是一种只能在一端(栈顶)进行插入和删除操作的特殊线性表,另一端叫栈底,无法操作。最明显特点是后进先出,即LIFO(Last-In-First-Out)。类似于生活中的桶,放进去的东西总在顶端,也只能从顶端拿东西。
栈的应用:递归。当我们在算法设计时说要用栈解决某个问题,那么通常就准备要写递归函数了。
说到栈自然就不得不说到队列。
队列是一种先进先出(FIFO)的线性表。
之前两次提到了线性表,那么什么是线性表?
一个线性表是n个具有相同特性的数据元素的有限序列。栈和队列都属于线性表。
线性表包括顺序表和链式表,顺序表通常用数组实现,链式表自然是用链表实现。
因此,栈的实现自然也分为数组实现的顺序栈和链表实现链式栈。
队列也可以分为数组队列和链表队列。数组队列一般实现为循环队列,也就是head和tail指向数组中的队列首尾位置。
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
每个数据元素通常叫做一个节点。
单链表就是链式存储的线性表,他是链表中的一种,此类链表的每个结点中只包含一个指针域,故又称单链表或线性链表。
其他链表,例如用来实现二叉树的二叉链表(二叉树可以是基于数组,也可以是基于链表)
数据结构中的树和图,也可以通过非线性的链表实现。
所以有的资料里面把链表等同于线性表中的一种是不够精确的,链表的概念远不止单链表
栈区:由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区: 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)回收,分配方式倒是类似于链表。
栈区使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。
堆区则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
一个由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数名,局部变量的名等。其操作方式类似于数据结构中的栈。
2、堆区(heap)— 由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、静态区(static)— 全局变量和局部静态变量的存储是放在一块的。程序结束后由系统释放。
4、文字常量区 — 常量字符串就是放在这里的,程序结束后由系统释放 。
5、程序代码区 — 存放函数体的二进制代码。
你会发现,c/c++中对于堆区和栈区的概念定义是一样的。这是因为现存的绝大多数操作系统的核心都是基于c/c++写的(通常会在加上一些汇编)。不论是Unix,linux,还是windows,底层主要都是C,而Android是基于linux的,iOS和MacOS都是类Unix操作系统,底层也是C。
c/c++内存分配例子如下:
//main.cpp
int a = 0; //全局初始化区
int a = 0; //全局初始化区
char *p1; //全局未初始化区
main() {
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c = 0; //全局(静态)初始化区
p1 = (char *)malloc(10);//堆区
p2 = (char *)malloc(20);//堆区
//分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。
与C++不同,Java自动管理栈区和堆区,程序员不能直接地设置栈或堆。Java的垃圾收集器会自动回收堆区的数据,程序员不用操心。
Java的数据分为基本类型(primitivetypes),共有8种,即int,short, long, byte, float, double, boolean, char,都保存在栈中。
剩下的都是包装类数据,包括String,Integer,Double,其他类,这些数据全都存在于堆中。(String不是基础类,任何String的变量都是一个String对象的引用,当修改值的时候实际上是指向了一个新的引用。)
Java的JVM(例如最常用的OpenJDK和OracleJDK),是C/C++和汇编混写的,所以最终Java的数据是通过JVM来到底层操作系统上的栈区和堆区。
由于C语言是很多语言的基础,所以他们也对应的有堆区和栈区的概念,并且要么是类似C的,要么是类似Java的方式。
这是因为C语言被誉为“上帝语言”,它不但奠定了软件产业的基础,还创造了很多其它语言,例如:
1).C++ 和 Objective-C 直接在C语言的基础上进行扩展,增加一些新功能后变成了新的语言。
2).PHP、Python 、JavaScript、Lua等都是用C语言(或者c/c++)开发出来的。
比如Python,他的堆区和栈区和Java就很像:
栈区:存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。
堆区:new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。
再比如JavaScript,他的堆区和栈区定义和Java也很像:
栈(stack)中主要存放一些基本类型的变量和对象的引用,(包含池,池存放常量),其优势是存取速度比堆要快,并且栈内的数据可以共享,但缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性;
堆(heap)用于复杂数据类型(引用类型)分配空间,例如数组对象、object对象;它是运行时动态分配内存的,因此存取速度较慢。
所有c的代码,都可以在c/c++编程中使用,但是推荐使用c++的STL中自带的容器,绝大多数情况下比自己实现的要更加稳定高效。
c++STL容器相关文档:
可参考c++容器库(cppreference.com版本)
或者是c++容器库(apiref.com版本)
csdn: 优先队列(堆) - C语言实现
堆是一种特殊的树形数据结构,堆通常是一个可以被看做一棵完全二叉树的数组对象。
#include //头文件
priority_queue q; //声明
q.empty(); q.size(); q.top();
q.push(); //入队
q.pop(); //出队,此时出的是最大或者最小元素,默认是最大。
priority_queue, greater > q; //这种默认是优先出最小元素。(greater就是越来越大的意思)
此外还可以自定义判断大小的函数,来改变队列顺序,稍后另写一篇。
一般用来做一些刚好有需要的特殊算法,例如游戏开发中的A*寻路,可以用优先队列优化。
csdn: C++之优化队列(详解篇)
栈的应用十分广泛 ,在函数调用、中断处理、表达式求值、内存分配等操作中都需要用到栈。
基本数据结构 – 栈简介(C语言实现)
c语言栈的实现以及操作
头文件#include
。基于双队列deque
实现。
使用如下:
stack stk;//声明
stk.empty(); stk.size(); stk.top();
stk.push(); //放入
stk.pop(); //取出
stk.swap(); //交换[c++11]
使用参考:C++中stack详解
虽然deque
有迭代器Iterator,但是c++的stack非常严格,不允许遍历。
想做一个可以遍历的栈,可以考虑使用vector数组,一般使用的时候,只使用push_back
和back()
。
递归函数的每一层,都相当于push压栈一层,函数中定义的相关参数就在这个栈上。当函数向外退出时,就相当于pop出栈一层,当时申请的相关参数也就随之pop释放掉了。当调用层数太多的时候,分配的栈空间不够用,产生堆栈溢出,将导致程序线程退出。这时候可以减少栈中的变量,或是对某些递归函数做尾递归优化,减少栈溢出的概率。
常见的以单链表形式实现。(单链队列使用链表作为基本数据结果,因此不存在伪溢出的问题,队列长度也没有限制。但插入和读取的时间代价会比较高)
网上例子很多,可以参考这些:
简书 C语言队列(文章还行,但是front写成font了)
知乎 C语言实现队列
头文件#include
使用
queue q;//声明
q.empty(); q.size();
q.front() //返回队首元素的值,但不删除该元素
q.back() //返回队列尾元素的值,但不删除该元素
q.push() //在队尾压入新元素
q.pop() // 删除队列首元素但不返回其值
q.swap(); //交换[c++11]
优先队列PriorityQueue是基于Queue接口的实现。
import java.util.PriorityQueue;
Queue<Integer> q = new PriorityQueue<>();//默认升序,从小到大,
Queue<Integer> qq = new PriorityQueue<>(cmp);//使用比较器,可以从大到小,
q.peek();//返回队首元素
q.poll();//返回队首元素,队首元素出队列
q.add();//添加元素
q.offer();//添加元素
q.size();//返回队列元素个数
q.isEmpty();//判断队列是否为空,为空返回true,不空返回false
比较器例子
static Comparator<Integer> cmp = new Comparator<Integer>() {
public int compare(Integer e1, Integer e2) {
return e2 - e1;
}
};
可参考:
Java的优先队列PriorityQueue详解
JAVA中PRIORITYQUEUE详解
java工具包中的Stack是继承于Vector(数组)实现的,不是链表。
import java.util.Stack;
Stack stack = new Stack();
stack.empty();
stack.push();
stack.pop();
stack.search(); //查找obj在stack中的位置
stack.peek(); //获取栈顶元素
参考资料java之Stack详细介绍
由于底层是数组,可以很容易的遍历。可以通过get函数,或者是迭代器(Iterator)。
import java.util.Queue; //引入
Queue<String> queue = new LinkedList<String>(); //创建
queue.offer("a"); //添加,超过队列长度限制返回false。【推荐使用】
queue.add("b"); //添加,超过队列长度限制报异常unchecked 。
queue.put("c"); //添加,超长则阻塞等待到有空间为止。
queue.poll(); //返回队首元素并删除,无元素返回null。【推荐使用】
queue.remove(); //返回队首元素并删除,无元素报异常
queue.peek(); //返回队首元素,无元素返回null。【推荐使用】
queue.element(); //返回队首元素,无元素报异常
queue.take(); //返回队首元素,无元素则阻塞等待到有元素位置。
for(String q : queue){System.out.println(q);} //遍历
可参考java队列——queue详细分析
java默认支持对queue的遍历,但是c++并不支持。
c++想要遍历队列,需要自己做循环,pop的同时push。
维基百科:编程语言历史
百度:数据结构
百科:堆 (数据结构)
百科:栈 (计算机术语)
百科:队列
百科:线性表
百科:二叉链表
csdn: 关于堆栈的讲解(我见过的最经典的) (这篇主要讲操作系统里的)
csdn: 浅谈堆、栈、堆区、栈区的概念和区别
cnblogs:线性表,线性表和链表的区别
csdn: 主流操作系统的开发语言
csdn: C语言是一个什么样的语言
csdn: Python内存管理中的堆和栈以及id,is,== 的区别和使用
csdn: Js中的堆区,栈区
cnblogs: c++容器的底层数据结构