数据结构之栈

1.1前言

栈和队列是两种重要的线性结构,从数据结构的角度看,栈和队列也是线性表,其特 殊性在于栈和队列的基本操作是线性表的子集。
他们是操作受限的线性表,因此,可称为限定性的数据结构。但从数据类型角度看,他们是和线性表大不相同的两类重要的的抽象数据类型。

1.2栈的定义

栈(Stack)是一种特殊的线性数据结构,它遵循后进先出(LIFO,Last In First Out)的原则。

栈中的所有元素都按照它们进入栈的顺序排列,后进入栈的元素位于栈顶,而先进入栈的元素位于栈底。

栈的基本操作包括:

  1. push(压栈/入栈):在栈顶添加一个元素。
  2. pop(弹栈/出栈):移除栈顶的元素,并返回该元素的值。如果栈为空,则此操作可能无效或导致错误。
  3. peek(查看栈顶):返回栈顶元素的值,但不移除它。如果栈为空,则此操作可能无效或返回特殊值(如nullundefined)。
  4. isEmpty(判断栈空):检查栈是否为空。
  5. size(获取栈大小):返回栈中元素的数量。

栈的实现方式有多种,例如可以使用数组或链表来实现。

1.3栈的举例

在生活中,栈的一个常见例子是一叠盘子

想象一下你在一个餐馆里,每次有新的一盘菜上来时,你都会将其放在最上面,这样顾客就能最先吃到这盘菜。这就是栈的“后进先出”(LIFO)原则的一个直观体现。

具体来说:

  1. 压栈(Push):当新的一盘菜上来时,你将其放在这叠盘子的最上面。这就是压栈操作,新的盘子(数据)被添加到了栈顶。
  2. 弹栈(Pop):当顾客吃完一盘菜后,你会从最上面拿走这盘菜。这就是弹栈操作,栈顶的盘子(数据)被移除。
  3. 查看栈顶(Peek):在拿走盘子之前,你可能会先看一下最上面的这盘菜是什么。这就是查看栈顶操作,你可以看到栈顶的元素(盘子),但不会移除它。

这种场景下的“盘子堆”就构成了一个栈结构

1.4栈的顺序存储结构

栈和线性表类似,也有两种存储表示方法顺序栈和链栈,链栈的操作是线性表操作的特例,操作比较容易实现。
顺序栈即栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,用数组肯定比较好。
同时附设:
指针 top 指示栈顶元素在顺序栈中的位置
若存储栈的长度为 StackSize ,则栈顶位置 top 必须小于 StackSize
即如果栈为空的时候若top 附设为 0 的话,到时候会数组下标越界,
所以 top 初始值要为 -1, 同时可以看出栈满的时候,top = MAXSIZE - 1
当栈存在一个元素时,top 等于 0 ,因此通常把空栈的判断条件定位 top 等于 -1
栈的顺序存储结构可描述为
#define MAX 5
typedef int datatype_t;
typedef struct {
    //数组存储数据元素
    datatype_t buf[MAX];
    //记录当前栈顶位置的栈顶指针(下标)
    int top;
}seqstack_t;

1.5顺序栈的操作

1.创建空栈

//创建空的顺序栈---为结构体在堆区申请空间
seqstack_t *create_empty_stack(){

    //为结构体在堆区申请空间
    seqstack_t *stack = (seqstack_t *)malloc(sizeof(seqstack_t));
    
    if(NULL == stack){
        printf("malloc is fail.\n");
        return NULL;
    }
    //初始化值
    memset(stack,0,sizeof(seqstack_t));
    stack->top = -1;
    return stack;
}

2.入栈

//入栈操作
int push_data_stack(seqstack_t *stack,datatype_t data){

    //栈满则失败
    if(stack->top == MAX - 1){
        printf("栈满,已无法入栈\n");
        return -1;
    }

    //栈顶指针先向上移动在加入数据
    stack->buf[++stack->top] = data;
    return 0;
}

3.出栈

//出栈
int pop_data_stack(seqstack_t *stack){
    //栈空的情况,无法出栈
    if(stack->top == -1){
        printf("栈已空,以无法出栈\n");
        return -1;
    }
    //先取出数据,栈顶指针再移动
    return stack->buf[stack->top--];
}

4.判空

//判空
int is_empty_stack(seqstack_t *stack){
    //为空则返回1,否则返回0
    return stack->top == -1 ? 1 : 0;
}

5.判满

//判满
int is_full_stack(seqstack_t *stack){
    //满则返回1,否则返回0
    return stack->top == MAX - 1 ? 1 : 0;
}

6.取栈顶元素

//取栈顶元素(最后入栈的元素)
int get_top_stack(seqstack_t *stack){
    if(stack->top == -1){
        return -1;
    }
    return stack->buf[stack->top];
}

7.完整代码

seqstack.c
#include"head.h"

//创建空的顺序栈---为结构体在堆区申请空间
seqstack_t *create_empty_stack(){
	//为结构体在堆区申请空间
	seqstack_t *stack = (seqstack_t *)malloc(sizeof(seqstack_t));
	if(NULL == stack){
		printf("malloc is fail.\n");
		return NULL;
	}
	//初始化值
	memset(stack,0,sizeof(seqstack_t));
	stack->top = -1;

	return stack;

}

//入栈操作
int push_data_stack(seqstack_t *stack,datatype_t data){

	//栈满则失败
	if(stack->top == MAX - 1){
		printf("栈满,已无法入栈\n");
		return -1;
	}
	//栈顶指针先向上移动在加入数据
	stack->buf[++stack->top] = data;	

	return 0;
}

//出栈
int pop_data_stack(seqstack_t *stack){
	
	//栈空的情况,无法出栈
	if(stack->top == -1){
		printf("栈已空,以无法出栈\n");
		return -1;
	}
	//先取出数据,栈顶指针再移动
	return stack->buf[stack->top--];
}

//判空
int is_empty_stack(seqstack_t *stack){
	//为空则返回1,否则返回0
	return stack->top == -1 ? 1 : 0;
}

//判满
int is_full_stack(seqstack_t *stack){
	//满则返回1,否则返回0
	return stack->top == MAX - 1 ? 1 : 0;
}

//取栈顶元素(最后入栈的元素)
int get_top_stack(seqstack_t *stack){
	if(stack->top == -1){
		return -1;
	}
	return stack->buf[stack->top];
}

head.h

#ifndef __HEAD_H__
#define __HEAD_H__

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define MAX 5
typedef int datatype_t;

typedef struct {
	//数组存储数据元素
	datatype_t buf[MAX];
	//记录当前栈顶位置的栈顶指针(下标)
	int top;
}seqstack_t;

extern seqstack_t *create_empty_stack();
extern int push_data_stack(seqstack_t *stack,datatype_t data);
extern int pop_data_stack(seqstack_t *stack);
extern int is_empty_stack(seqstack_t *stack);
extern int is_full_stack(seqstack_t *stack);
extern int get_top_stack(seqstack_t *stack);

#endif

main.c

#include"head.h"

int main(){
	int i = 0,ret = 0;
	seqstack_t *stack = create_empty_stack();
	//当栈未满,插入数据
	while(!is_full_stack(stack)){
		push_data_stack(stack,i++);
	}

	printf("栈顶元素为:%d\n",get_top_stack(stack));
	printf("出栈元素为:%d\n",pop_data_stack(stack));

	//栈不为空的时候,出栈数据
	while(!is_empty_stack(stack)){
		ret = pop_data_stack(stack);
		printf("出栈元素为:%d",ret);
	}
	printf("\n");

	return 0;
}

8.运行演示

1.6栈的链式存储结构

链式栈:插入操作和删除操作均在链表头部进行,链表尾部就是栈底,栈顶指针就是 头指针。
链式栈的本质:栈头 + 不带头结点的链表。
typedef char data_t;

typedef struct node{
	data_t data;
	struct node *next;
}linknode_t;

typedef struct {
	int len;//记录当前栈中元素的个数
	linknode_t *top;//栈顶指针
}linkstack_t;

1.7链栈的操作

1.创建空栈

//1.创建空的链栈---为栈头在堆区分配空间
linkstack_t *create_empty_linkstack(){
    linkstack_t *s = NULL;
    s = (linkstack_t*)malloc(sizeof(linkstack_t));
    if(NULL == s){
        printf("malloc is fail");
        return NULL;
    }
    memset(s,0,sizeof(linkstack_t));
    return s;
}

2.判空

类似于链表的头插法

图解

//3.入栈
void push(linkstack_t *s,data_t data){

    //注意这里使用的是linknode_t类型的,而不是linkstack_t类型的,因为插入的是节点而不是栈头
    linknode_t *temp = NULL;
    temp = (linknode_t *)malloc(sizeof(linknode_t));

    if(NULL == temp){
        printf("malloc if fail");
        return ;
    }

    temp->data = data;
    //插入数据类似于链表的头插法
    temp->next = s->top;
    s->top = temp;
    //长度要加1别忘了
    s->len++;
    return ;
}

3.出栈

图解

//4.出栈
data_t pop(linkstack_t *s){

    linknode_t *temp = (linknode_t *)malloc(sizeof(linknode_t));
    data_t data;//保存要删除的数据
    //保留要删除节点的地址
    temp = s->top;
    //取出数据
    data = temp->data;
    //更新指针信息
    s->top = temp->next;
    //释放temp节点
    free(temp);
    temp = NULL;
    //长度减1
    s->len--;
    return data;
}

4.取栈顶元素

//5.取栈顶元素
data_t gettop(linkstack_t *s){
    return s->top->data;
}

5.完整代码

linkstack.c

#include"head.h"

//1.创建空的链栈---为栈头在堆区分配空间
linkstack_t *create_empty_linkstack(){
	linkstack_t *s = NULL;
	s = (linkstack_t*)malloc(sizeof(linkstack_t));
	if(NULL == s){
		printf("malloc is fail");
		return NULL;
	}
	memset(s,0,sizeof(linkstack_t));

	return s;
}

//2.判空
int is_empty(linkstack_t *s){
	return s->top == NULL ? 1 : 0;
}

//3.入栈
void push(linkstack_t *s,data_t data){
	linknode_t *temp = NULL;
	temp = (linknode_t *)malloc(sizeof(linknode_t));
	if(NULL == temp){
		printf("malloc if fail");
		return ;
	}
	temp->data = data;

	//插入数据类似于链表的头插法
	temp->next = s->top;
	s->top = temp;
	//长度要加1
	s->len++;

	return ;
}

//出栈
data_t pop(linkstack_t *s){
	linknode_t *temp = (linknode_t *)malloc(sizeof(linknode_t));
	data_t data;//保存要删除的数据
	
	//保留要删除节点的地址
	temp = s->top;
	
	//取出数据
	data = temp->data;

	//更新指针信息
	s->top = temp->next;

	//释放temp节点
	free(temp);
	temp = NULL;

	//长度减1
	s->len--;

	return data;
}

//取栈顶元素
data_t gettop(linkstack_t *s){
	return s->top->data;
}

head.h

#ifndef __HEAD_H__
#define __HEAD_H__

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

typedef char data_t;

typedef struct node{
	data_t data;
	struct node *next;
}linknode_t;
typedef struct {
	int len;//记录当前栈中元素的个数
	linknode_t *top;//栈顶指针
}linkstack_t;


extern linkstack_t *create_empty_linkstack();
extern int is_empty(linkstack_t *s);
extern void push(linkstack_t *s,data_t data);
extern void push(linkstack_t *s,data_t data);
extern data_t pop(linkstack_t *s);
extern data_t gettop(linkstack_t *s);

#endif

main.c

#include"head.h"

int main(){

	linkstack_t *s = NULL;
	s = create_empty_linkstack();

	data_t array[] = {'o','l','l','e','h'};
	int i = 0;

	for(i = 0;i < sizeof(array)/sizeof(array[0]);i++){
		push(s,array[i]);
	}
	
	printf("Top data is:%c\n",gettop(s));

	while(!is_empty(s)){
		printf("%c",pop(s));
	}
	printf("\n");

	return 0;
}

6.运行演示

1.8优缺点

优点

  1. 操作简便:栈的主要操作(如push、pop、peek等)相对简单,且时间复杂度通常为O(1),即常数时间复杂度。这使得栈在处理某些问题时非常高效。
  2. 空间利用高效:栈是动态分配内存的,它根据元素的入栈和出栈来自动调整其大小。这避免了不必要的空间浪费。
  3. 实现简单:栈的实现通常基于数组或链表,这使得栈的实现相对简单。
  4. 适用性强:栈在多种场景中都有广泛的应用,如函数调用、表达式求值、括号匹配、浏览器历史记录等。这些场景都遵循后进先出的原则,因此栈是这些场景下的理想数据结构。

缺点

  1. 功能受限:栈的主要操作是push(入栈)和pop(出栈),这限制了栈的使用范围。对于需要更复杂操作(如插入、删除、查找等)的场景,栈可能不是最佳选择。
  2. 数据访问不便:栈只允许在栈顶进行元素的插入和删除操作,这使得访问栈中的其他元素变得困难。如果需要频繁访问栈中的元素,可能需要考虑使用其他数据结构(如数组、链表或树)。
  3. 空间限制:虽然栈可以动态调整大小,但在某些情况下(如使用固定大小的数组实现栈),栈的大小可能会受到限制。当栈满时,新的元素无法入栈,这可能导致程序出错或性能下降。
  4. 依赖顺序:栈的操作严格遵循后进先出的原则,这意味着元素的出栈顺序完全取决于它们的入栈顺序。在某些情况下,这种顺序依赖可能会导致问题或限制。
  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值