数据结构之栈

Posted 捕获一只小肚皮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之栈相关的知识,希望对你有一定的参考价值。

前言


经历了算法效率(复杂度)后,我们开始学习了单双链表,然后到达了现在,而此次博主要讲解的便是数据结构中的 ,

是什么样子的呢? 它又具有哪些特性 ? 可以做哪些事情呢 ? 等一系列问题,博主将会在下面意一一进行解答.



栈是什么样子?


既然出现了栈这种数据结构,我们就需要知道它是什么样子. 那么,到底是什么样子呢?

答 : 数据先进入后出来; 请看下图:


通过动图我们清晰的窥见了的结构,那现在我们就需要用一些专业术语进行描述的行为和特性

  • :一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作.
  • 进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则.
  • 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
  • 出栈:栈的删除操作叫做出栈。出数据也在栈顶


清楚怎样实现栈


还记得博主当时讲解顺序表时候,提到的逻辑结构物理结构吗? 上面我们所看见的栈的示意图就是我们的逻辑结构,而逻辑结构只是方便我们理解,我们需要用某种方法去实现它,这,就是我们的物理结构了.

那我们怎么去实现呢? 大家再想想我们在讲解顺序表链表时候,它们各有优势,具有哪些优势呢?

  • 顺序表: 地址连续,尾插尾删即为方便,但是其他地方头插头删不方便.— --- —实现方法:malloc开辟数组
  • 链表: 头插头删即为方便,但是尾插尾删不方便(双链表除外)— --- —实现方法:链接单个动态空间

现在我们回头过来看看,对于栈来说,我们最主要用的是什么操作?? 答案显而易见,我们用的是尾删尾插操作,所以我们怎么实现呢?

答曰: 与顺序表一样,利用malloc开辟数组.



栈结构的代码实现


清楚了怎样实现,我们就可以定义结构了.


typedef int STDataType;  //方便以后修改存储元素的类型

typedef struct Stack
{
    STDataType* data; //接收动态开辟的数组地址
    int top;      //栈顶,代表此时栈空间中的实际元素数量
    int capacity; //动态开辟的容量
}ST; //修改个简短的名字

大家有木有发现,的定义与顺序表几乎是一模一样的???,哈哈哈,没错,的结构和顺序表就是几乎一样,只是它少了一些方法而已,主要突出尾插尾删

栈项目文件搭建

老规矩,博主习惯用vs2019,这里的讲解仍然用这个编译器.

既然是我们自己手动实现,那下面开始建立项目吧~

需要怎么写呢?看过博主前几篇的人可能已经知道了,那就是3板斧:

  • 建立头文件Stack.h: 用来定义结构体,引头文件,函数的声明
  • 建立源文件Stack.c: 用来写函数定义
  • 建立测试文件test.c: 用来测试各个函数是否成功

如图:

栈的各种操作实现

栈的主要操作有哪些?

答曰: 入栈, 出栈, 判空, 获取栈顶元素,获取栈元素数量

而为了实现上面主要操作,我们还少不了一些基本操作: 检查容量, 初始化等

下面博主一次性声明完所有函数方法,至于为什么这样声明,大家不要着急,在定义函数时候会讲解.

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;

typedef struct Stack
{
	STDataType* data;
	int top;
	int capacity;
}ST;

void StackInit(ST* ps);  //初始化
void StackCheckCapacity(ST* ps); //检查容量
void StackPop(ST* ps);   //尾删
void StackPush(ST* ps,STDataType elem);  //尾插
bool StackEmpty(ST* ps); //判空
size_t StackSize(ST* ps);//栈元素数量
STDataType StackTop(ST* ps);//获取栈顶元素
void StackDestroy(ST* ps);//销毁空间

1 栈之初始化

由于结构体ST是把动态数组包含住的,所以我们在外面就只需要定义一个ST 结构体变量就行了,然后传结构体变量的地址.

而我们初始化的内容是 需要把 data指针置为NULL, topcapacity初始化为0.

代码:

void StackInit(ST* ps)   
{
    assert(ps); //传进来的地址一定不能为空
    
    ps->data = NULL;
    ps->top = ps->capacity = 0;
}

测试是否成功:

成功!!!


2 栈之检查容量

为什么要检查容量呢? 因为我们不知道栈空间是否满了,如果满了就无法再入栈,所以需要检查.

那我们在这里面要做的事情就是检查容量吗? 肯定不是哇~~,如果没有满容就不需要管,如果满了就需要增加空间了

那怎么判断容量是否满了呢?? 没错! 答案就是:ps->top == ps->capacity.

注意点当满足上述条件时候,也不能一定说是满容哦,大家想想为什么~~~ 对,那就是top==capacity==0

void StackCheckCapacity(ST* ps)
{
    assert(ps);  //ps不可以为空指针
    
    if(ps->top == ps->capacity)
    {
        //如果此时容量为0,就给4个,否则就翻倍
        int newcapacity = (ps->capacity == 0 ? 4 : 2 * ps->capacity ); 
        
        //注意这里要新给一个指针接收realloc调整的空间,至于为啥博主之前讲解过哦~下面有链接
        STDataType* newdata = (STDataType*)realloc(ps->data,newcapacity*sizeof(STDataType));
        
        if(newdata == NULL)
        {
            perror("错误原因:");
            exit(-1);
        }
        ps->data = newdata; //重新交接给ps->data
        
        ps->capacity = newcapacity;
    }
}

realloc的接收问题

解释: 有人会问上面代码为什么要用一个newcapacity变量接收新的空间数量再去下面调整,大家想想,如果空间本就是0,你去下面调整管用吗?不管用,因为0乘以任何数还是0,永远也开辟不了空间

测试:

成功~~


3 栈之判空

栈什么时候为空呢? 这个实在简单,博主就不太详细解释了,直接上代码

void StackPush(ST* ps,STDataType elem)
{
    assert(ps); //ps不能为空
    
    return ps->top == 0; //如果等于0就返回真,如果不等于就返回假
}

4 栈之尾插

这个时候有人问了,肚皮~,为何你的函数声明名字写成StackPush而不写成StackPushBack呢? 因为栈只有一个插入操作啊,虽然也是尾插,但是忘记了栈有一个专业术语叫入栈吗? 既然是入,写成push是不是更符合语义呢?

既然是插入数据,所以函数的形参设置就多了个需要插入的形参elem

void StackPush(ST* ps,STDataType elem)
{
    assert(ps); //ps一定不可以为空
    
    StackCheckCapacity(ps); //检查容量是否满了
    
    ps->data[ps->top++] = elem;
}

测试:

成功!!


5 栈之尾删

尾删很简单,只需要ps->top减去1就行,不需要真的删除,因为我们能访问的权限最高就是ps->top

但是有个注意点就是,如果栈已经空了,就不可以删除了

另外,对于名字比较疑惑的,参照上面的尾删

void StackPop(ST* ps)
{
    assert(ps);
    
    assert(!StackEmpty(ps));
    
    ps->top--;
}

6 栈之获取元素数量

这个也是过于简单,直接把ps->top的值返回就行

size_t StackSize(ST* ps)
{
    assert(ps);
    
    return ps->top;
}

7 栈之读取栈顶

这个函数的定义比较简单,直接通过ps->top-1的索引,把值返回,但是注意栈为空的时候哦

STDataType StackTop(ST* ps)
{    
	assert(ps);        
	assert(!StackEmpty(ps));        
	return ps->data[ps->top - 1];
}

8 栈之销毁空间

直接free掉data所指向的空间即可

void StackDestroy(ST* ps)
{    
	assert(ps);    
	free(ps->data);
	//当ps->data是NULL时候,free什么都不会做    
	ps->data = NULL;    
	ps->top = ps->capacity = 0;
}

测试:

成功!!!

以上是关于数据结构之栈的主要内容,如果未能解决你的问题,请参考以下文章

数据结构之栈

数据结构之栈

数据结构之栈

数据结构之栈

python数据结构之栈

数据结构之栈