堆、栈、队列之于数据结构、操作系统、C和Java

堆、栈、队列之于数据结构、操作系统、C和Java

文章目录

  • 堆、栈、队列之于数据结构、操作系统、C和Java
    • 到底有几个概念?
    • 数据结构中的堆和栈
      • 堆(Heap)
      • 栈(Stack)
      • 队列(queue)
      • 线性表(linear list)
      • 链表(linked list)
    • 操作系统中的堆区和栈区
    • 程序占用内存中的堆区和栈区
      • C/C++中的堆区和栈区
      • Java中的堆区和栈区
      • 其他语言中的堆区和栈区
    • 编程中的使用
      • C/C++
        • C优先队列——堆(heap)
        • C++优先队列(堆)
        • C语言使用栈(stack)
        • C++栈(stack)
        • 栈的用法——递归
        • C语言队列
        • C++队列(queue)
      • Java中的相关数据结构使用
        • Java优先队列()——堆
        • Java栈Stack
        • Java队列Queue
  • 参考资料

到底有几个概念?

首先,是两个数据结构上的概念,有时候说堆栈指的是,英文定义更明确,heap是堆,stack是栈。
其次,也指(操作系统/单片机的)内存模型,更多的叫做堆区栈区
此外,本将简单讲讲在不同的编程语言中,如何实现或使用这些数据结构


数据结构中的堆和栈

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。

堆(Heap)

堆是一种特殊的树形数据结构,堆通常是一个可以被看做一棵完全二叉树数组对象。
1.堆中某个节点的值总是不大于或不小于其父节点的值。
2.堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
从完全二叉树生成堆,使用筛选法建堆的时间复杂度O(n),插入结点、删除普通元素和删除最小元素的时间复杂度O(logn)。

栈(Stack)

栈是一种只能在一端(栈顶)进行插入和删除操作的特殊线性表,另一端叫栈底,无法操作。最明显特点是后进先出,即LIFO(Last-In-First-Out)。类似于生活中的桶,放进去的东西总在顶端,也只能从顶端拿东西。
栈的应用:递归。当我们在算法设计时说要用栈解决某个问题,那么通常就准备要写递归函数了。

队列(queue)

说到栈自然就不得不说到队列。
队列是一种先进先出(FIFO)的线性表。

线性表(linear list)

之前两次提到了线性表,那么什么是线性表?
一个线性表是n个具有相同特性的数据元素的有限序列。队列都属于线性表。
线性表包括顺序表链式表,顺序表通常用数组实现,链式表自然是用链表实现。

因此,栈的实现自然也分为数组实现的顺序栈和链表实现链式栈
队列也可以分为数组队列链表队列。数组队列一般实现为循环队列,也就是head和tail指向数组中的队列首尾位置。

链表(linked list)

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
每个数据元素通常叫做一个节点。
单链表就是链式存储的线性表,他是链表中的一种,此类链表的每个结点中只包含一个指针域,故又称单链表或线性链表
其他链表,例如用来实现二叉树二叉链表(二叉树可以是基于数组,也可以是基于链表)
数据结构中的,也可以通过非线性的链表实现。
所以有的资料里面把链表等同于线性表中的一种是不够精确的,链表的概念远不止单链表


操作系统中的堆区和栈区

栈区:由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区: 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)回收,分配方式倒是类似于链表。

栈区使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。
堆区则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。


程序占用内存中的堆区和栈区

C/C++中的堆区和栈区

一个由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"优化成一个地方。
}

Java中的堆区和栈区

栈(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/c++编程中使用,但是推荐使用c++的STL中自带的容器,绝大多数情况下比自己实现的要更加稳定高效。
c++STL容器相关文档:
可参考c++容器库(cppreference.com版本)
或者是c++容器库(apiref.com版本)

C优先队列——堆(heap)

csdn: 优先队列(堆) - C语言实现

C++优先队列(堆)

堆是一种特殊的树形数据结构,堆通常是一个可以被看做一棵完全二叉树数组对象。

#include 				//头文件
priority_queue q;			//声明
q.empty();	q.size();	q.top();
q.push();		//入队
q.pop();		//出队,此时出的是最大或者最小元素,默认是最大。
priority_queue, greater > q;   //这种默认是优先出最小元素。(greater就是越来越大的意思)

此外还可以自定义判断大小的函数,来改变队列顺序,稍后另写一篇。
一般用来做一些刚好有需要的特殊算法,例如游戏开发中的A*寻路,可以用优先队列优化。
csdn: C++之优化队列(详解篇)

C语言使用栈(stack)

栈的应用十分广泛 ,在函数调用、中断处理、表达式求值、内存分配等操作中都需要用到栈。
基本数据结构 – 栈简介(C语言实现)
c语言栈的实现以及操作

C++栈(stack)

头文件#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_backback()

栈的用法——递归

递归函数的每一层,都相当于push压栈一层,函数中定义的相关参数就在这个栈上。当函数向外退出时,就相当于pop出栈一层,当时申请的相关参数也就随之pop释放掉了。当调用层数太多的时候,分配的栈空间不够用,产生堆栈溢出,将导致程序线程退出。这时候可以减少栈中的变量,或是对某些递归函数做尾递归优化,减少栈溢出的概率。

C语言队列

常见的以单链表形式实现。(单链队列使用链表作为基本数据结果,因此不存在伪溢出的问题,队列长度也没有限制。但插入和读取的时间代价会比较高)
网上例子很多,可以参考这些:
简书 C语言队列(文章还行,但是front写成font了)
知乎 C语言实现队列

C++队列(queue)

头文件#include
使用

queue q;//声明
q.empty();   q.size(); 
q.front()               //返回队首元素的值,但不删除该元素
q.back()                //返回队列尾元素的值,但不删除该元素
q.push()                //在队尾压入新元素
q.pop()                // 删除队列首元素但不返回其值
q.swap();				//交换[c++11]

Java中的相关数据结构使用

Java优先队列()——堆

优先队列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

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)。

Java队列Queue
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++容器的底层数据结构

你可能感兴趣的:(算法与数据结构,c++,堆栈,数据结构,java,算法)