前言
数据结构,一门数据处理的艺术,精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美,在为信息技术打下坚实地基的同时,也令无数开发者和探索者为之着迷。
也因如此,它作为博主大二上学期最重要的必修课出现了。由于大家对于上学期C++系列博文的支持,我打算将这门课的笔记也写作系列博文,既用于整理、消化,也用于同各位交流、展示数据结构的美。
此系列文章,将会分成两条主线,一条“数据结构基础”,一条“数据结构拓展”。“数据结构基础”主要以记录课上内容为主,“拓展”则是以课上内容为基础的更加高深的数据结构或相关应用知识。
欢迎关注博主,一起交流、学习、进步,往期的文章将会放在文末。
前面总结了线性表的基本使用。这一节我们来总结一下两种常用的也是非常重要且特殊的线性结构——栈和队列。
说它重要,是因为这种结构在日常生产中的大大小小事务中无时无刻不在用到。
例如程序调用函数时产生的存储就是栈结构,所以我们常说“栈空间”,先调用的函数最后结束,后来函数内的局部变量的空间都会在该函数结束时得到释放,且不会影响到前面的函数。而计算机底层指令执行就是一种队列结构,后来的指令最后执行。
不仅如此,在各种算法中,两个数据结构也是频频入镜。以最基本的两种搜索算法为例,深度优先和广度优先就是基于这两种结构对状态进行的枚举。
另外,面对事务并发的场景,一种不错的解决方案是使用队列管理事务,将其队列化后依次处理。常见的输入输出流也是字节数据的队列。面向对象中一个类实例化和释放时他本身及其继承的父类关系就是栈的关系。
所以,这两种基础数据结构的重要性是不言而喻的。俗话说“基础不牢,地动山摇”,要建立坚实可靠算法基础和业务逻辑能力,本节基础内容和下一节的应用扩展内容将会是一个绕不开的重点,那么就让我们开始吧:
本节思维导图:
堆栈
堆栈简称栈,是一种首先得线性表,只允许表的同一段进行插入和删除操作,且这些操作是按后劲消除的原则进行的。
进行插入和删除的一段被称为栈顶,另一端被称为栈底。栈中没有元素的时候被称为空栈
顺序栈
顺序栈的存储方式是顺序的,使用数组实现。
其特点就是栈的最大规模是确定的,因为数组的大小无法动态改变。
顺序栈的封装
下面我们来封装一个顺序栈。最基本操作如下:
- 压入一个元素(push)
- 弹出一个元素(pop)
- 获得栈顶元素(peek)
- 清空栈(clear)
- 判断是否为空(isEmpty)
- 判断是否存满(isFull)
- 获得元素个数(size)
在顺序栈中,我们只需要知道一个数组的地址、当前元素个数以及数组规模便可以完成栈的基本操作。
所以栈的类的原型以及构造函数可如下(以int元素为例):
//C
typedef struct _Stack{
int * elements;
int maxLen;
int size;
}Stack;
Stack * createStack(int maxLen){
Stack * stack = (Stack *)malloc(sizeof(Stack));
stack->maxLen = max(1,maxLen);
stack->size = 0;
stack->stack = (int *)malloc(sizeof(int) * min(1,maxLen));
return stack;
}
//java
public class Stack {
private int[] elements;
private int maxLen;
private int size;
public Stack(int maxLen) {
this.maxLen = Math.max(1,maxLen);
elements= new int[maxLen];
size = 0;
}
}
判断是否空/满
有了这样的原型,判断一个栈是否空或者满就很简单了。
空就是当前规模为0,满就是当前规模和最大规模相同。
//C
int isEmpty(Stack * stack){
return stack->size == 0;
}
int isFull(Stack * stack){
return stack->size == stack->maxLen;
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == maxLen;
}
获得当前栈中元素数量
在确定了栈的原型之后,这个问题就变成了一个送分题,因为栈中元素数量就在里面存着。
//C
int size(Stack * stack){
return stack->size;
}
public int size() {
return size;
}
压入一个元素
在栈还有剩余空间的条件下,插入一个元素只需要将该元素放置在数组末尾 ,同时更新栈顶位置即可。
所以在执行插入操作之前,我们需要先对栈是否已满进行判断
//C
int push(int value,Stack * stack){
if(isFull(stack)){
return 0; // 插入元素失败,栈满
}
Stack->elements[stack->size] = value;
stack->size++;
return 1;
}
//java
public boolean push(int value) throws Exception {
if(isFull()) {
throw new Exception("栈满不能添加元素");
}
elements[size] = value;
size++;
return true;
}
获得栈顶元素
获得栈顶元素,栈顶元素就是当前数组中的最后元素,直接按照下标返回即可。
但在这之前需要判定一下栈是否为空。
//C
int peek(Stack * stack){
if(isEmpty(stack)){
return 0;
}
return stack->elements[stack->size - 1];
}
public int peek() throws Exception {
if(isEmpty()) {
throw new Exception("空栈异常");
}
return elements[size - 1];
}
弹出栈顶元素
当栈顶元素存在时,顺序栈只需要将栈顶指针向前移动一位即可表示删除。对于空栈,可以选择抛出异常,也可以不做任何处理。
//C
int pop(Stack * stack){
stack->size = max(0,stack->size - 1);
return stack->size;
}
public int pop() {
size = Math.max(0, size - 1);
return size;
}
清空栈
清空栈相当于删除所有的元素,只需要将栈顶指针置零即可。
//C
int clear(Stack * stack){
int size = stack->size;
stack->size = 0;
return size;
}
//java
public int clear() {
int size = this.size;
this.size = 0;
return size;
}
完整代码
//C
#include<malloc>
typedef struct _Stack{
int * elements;
int maxLen;
int size;
}Stack;
Stack * createStack(int);
int isEmpty();
int isFull();
int push(Stack*,int);
int pop(Stack*);
int peek(Stack*);
int size(Stack*);
int clear(Stack*)