【数据结构】栈结构全解析

☀️ Hi,大家好,我是每天都在变强的白晨。
【数据结构】栈结构全解析_第1张图片

栈结构全解析

  • 前言
    • 栈的定义及结构
    • 顺序栈
      • 顺序栈结构
      • 初始化和销毁
      • 判断栈是否为空、获取栈中数据个数 及 获取栈顶元素
      • 入栈 以及 出栈
      • 顺序栈全局代码
    • 链栈
      • 链栈结构
      • 链栈入栈
      • 链栈出栈
      • 全局代码
  • 后记


前言

在顺序表和链表中我们讲述了数据结构最基本的两种结构,今天我们就要在这两种数据结构的基础上来继续认识另外一种数据结构——


栈的定义及结构


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

简单来说,栈是一种只能从一端进行插入和删除的遵循后进先出的线性表
【数据结构】栈结构全解析_第2张图片

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
【数据结构】栈结构全解析_第3张图片

出栈:栈的删除操作叫做出栈。出数据也在栈顶。
【数据结构】栈结构全解析_第4张图片

【数据结构】栈结构全解析_第5张图片

如果你曾经了解过函数栈帧,那么你应该对这种结构不陌生。但是还是要提醒一下,函数栈帧中的栈是一种存储空间,而本篇文章中的栈是一种数据结构,两者在结构上有相似之处。


我们现在已经了解了栈的结构了,那么栈到底怎么实现呢?
前文说到,栈是一种线性表,所以我们可以在顺序表和链表的基础上实现链表。

我们来对比一下,顺序表和链表实现栈的优劣情况
【数据结构】栈结构全解析_第6张图片

我们先来实现顺序栈

顺序栈


【数据结构】栈结构全解析_第7张图片

顺序栈结构


首先,我们先来写出栈的顺序表结构

typedef int STDataType;
typedef struct Stack
{
  STDataType* a;
  int top;//栈中数据个数
  int capacity;//栈总容量
}ST;

首先,我们为了方便储存各种不同类型的数据,我们定义一个SLDataType,如果以后想改变储存数据的类型,直接修改 SLDataType 的定义即可。

接着,我们发现 Stack 中有一个指针,这个指针是用来指向动态内存开辟的空间的,也就是指向数据的。

最后,top 指的是顺序表中元素的个数,capacity 是指顺序表中最多能容纳元素的个数。


接下来,我们完成栈中非常重要的一步——实现接口函数
所有接口函数如下:

// 栈初始化
void StackInit(ST* ps);
// 栈销毁
void StackDestroy(ST* ps);
// 插入数据(入栈)
void StackPush(ST* ps, STDataType x);
// 删除数据 (出栈)
void StackPop(ST* ps); 
// 取栈顶数据 
STDataType StackTop(ST* ps);
// 栈中数据个数 
int StackSize(ST* ps);
 // 判断栈是否为空 
 bool StackEmpty(ST* ps);

初始化和销毁


  • 初始化其实非常好理解,就传一个顺序表的指针,我们将其中的数据先置空。

  • 销毁就是,先将size和capacity置为0,再释放掉储存数据的空间。

void StackInit(ST* ps)
{
   assert(ps);
   ps->a = NULL;
   ps->capacity = 0;
   ps->top = 0;
}
void StackDestroy(ST* ps)
{
   assert(ps);
   free(ps->a);
   ps->a = NULL;
   ps->capacity = 0;
   ps->top = 0;
}

判断栈是否为空、获取栈中数据个数 及 获取栈顶元素


  • 由于经常需要判断栈中是否为空以及获取栈的大小,所以我们单独将其功能封装成一个函数
  • 实现相对简单,判断为空只需要判断top是否为0,如果为0,返回真,非0,返回假
  • 获取栈的大小只需要将top值传出
  • 要获取栈顶元素就先得判断栈是否为空,然后将栈顶元素传出
bool StackEmpty(ST* ps) 
{ 	
   assert(ps); 	
   return ps->top == 0; 
}
int StackSize(ST* ps)
 { 	
    assert(ps);
    return ps->top;
 } 
 STDataType StackTop(ST* ps)
{ 	
    assert(ps); 	
    assert(!StackEmpty(ps));
	return ps->a[ps->top - 1]; 
}


入栈 以及 出栈


由于栈只能从尾部(栈顶)插入/删除元素,所以只用实现尾插/尾删功能(尾插与尾删详细见顺序表)。

入栈:
【数据结构】栈结构全解析_第8张图片

出栈:
【数据结构】栈结构全解析_第9张图片

void StackPush(ST* ps, STDataType x)
{
	assert(ps);

	if (ps->top == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		STDataType* tmp = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			printf("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}

	ps->a[ps->top] = x;
	ps->top++;
}
void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	ps->top--;
}

顺序栈全局代码


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

void StackInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

void StackDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

void StackPush(ST* ps, STDataType x)
{
	assert(ps);

	if (ps->top == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		STDataType* tmp = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			printf("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}

	ps->a[ps->top] = x;
	ps->top++;
}

void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	ps->top--;
}

STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	return ps->a[ps->top - 1];
}

int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}


链栈


上文提到,链栈结构选择头作为栈顶实现入栈(头插)、出栈(头删)比较简单,所以这个结构大家可以动手写一写,与链表实现极为相似,这里不过多赘述。
【数据结构】栈结构全解析_第10张图片
这次,我们要实现用链尾当栈顶的非双向链表写法。由于这种写法实际用途不大,所以我们只讲解基本实现思路。

链栈结构


  1. 创建栈结构
typedef int STDataType;

typedef struct StackNode
{
   struct StackNode* next;
   STDataType data;
}STNode;

typedef struct Stack
{
   STNode* top;
   STNode* bottom;
}Stack;

这里由于单向链表的节点只能指向下一个节点,所以我们只能在栈结构中记录一下栈顶和栈底的位置。
【数据结构】栈结构全解析_第11张图片


链栈入栈


  1. 再来说下一个难题——如何入栈?
  • 先创建一个新节点,将所给数据存入
  • 判断bottom是否为NULL
    • 如果是,说明栈中没有数据,则让topbottom都指向新节点
    • 如果不是,则实施尾插,尾插结束后,使top指向尾节点
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);

	STNode* newnode = (STNode*)malloc(sizeof(STNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = data;
	newnode->next = NULL;

	if (ps->bottom == NULL)
	{
		ps->bottom = ps->top = newnode;
	}
	else
	{
		ps->top->next = newnode;
		ps->top = newnode;
	}
	
}

链栈出栈


  1. 如何出栈?
  • 首先,判断是否栈中为空
  • 其次,遍历找到尾节点的前一个节点,删除数据,top指向原尾节点前一个元素
  • 这里有个特殊情况,如果栈中只有一个元素,那么删除完后,还得把bottom还原为NULL
void StackPop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	STNode* Del = ps->bottom;

	if (ps->bottom == ps->top)
	{
		free(ps->bottom);
		ps->bottom = ps->top = NULL;
		return;
	}

	while (Del->next != ps->top)
	{
		Del = Del->next;
	}

	free(ps->top);
	Del->next = NULL;
	ps->top = Del;
}

剩下的函数都不难实现,大家可以参考下文代码实现。

全局代码


typedef int STDataType;

typedef struct StackNode
{
	struct StackNode* next;
	STDataType data;
}STNode;

typedef struct Stack
{
	STNode* top;
	STNode* bottom;
}Stack;

// 初始化栈 
void StackInit(Stack* ps);
// 入栈 
void StackPush(Stack* ps, STDataType data);
// 出栈 
void StackPop(Stack* ps);
// 获取栈顶元素 
STDataType StackTop(Stack* ps);
// 获取栈底元素
STDataType StackBottom(Stack* ps);
// 获取栈中有效元素个数 
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
bool StackEmpty(Stack* ps);
// 销毁栈 
void StackDestroy(Stack* ps);
 
void StackInit(Stack* ps)
{
	assert(ps);
	ps->top = NULL;
	ps->bottom = NULL;
}

void StackDestroy(Stack* ps)
{
	assert(ps);

	STNode* cur = ps->bottom;

	while (cur)
	{
		STNode* next = cur->next;
		free(cur);
		cur = next;
	}

	ps->bottom = ps->top = NULL;
}

bool StackEmpty(Stack* ps)
{
	assert(ps);

	return ps->bottom == NULL;
}

void StackPush(Stack* ps, STDataType data)
{
	assert(ps);

	STNode* newnode = (STNode*)malloc(sizeof(STNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = data;
	newnode->next = NULL;

	if (ps->bottom == NULL)
	{
		ps->bottom = ps->top = newnode;
	}
	else
	{
		ps->top->next = newnode;
		ps->top = newnode;
	}
	
}

void StackPop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	STNode* Del = ps->bottom;

	if (ps->bottom == ps->top)
	{
		free(ps->bottom);
		ps->bottom = ps->top = NULL;
		return;
	}

	while (Del->next != ps->top)
	{
		Del = Del->next;
	}

	free(ps->top);
	Del->next = NULL;
	ps->top = Del;
}

STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	return ps->top->data;
}

STDataType StackBottom(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	return ps->bottom->data;
}

int StackSize(Stack* ps)
{
	assert(ps);

	int sz = 0;
	STNode* cur = ps->bottom;

	while (cur)
	{
		sz++;
		cur = cur->next;
	}

	return sz;
}


后记


如果说,结构的特点是后进先出,那么有没有一种结构可以做到先进先出呢?
如果有,这个结构该如何实现呢?
了解了这种结构以后,下一篇文章,我们就要了解的姊妹结构——队列


这次是白晨第一次使用markdown编辑器编辑,对一些语法尚且不熟悉。如有错误,还请指正,在这里多谢各位的包容。

以上就是本次的分享内容了,喜欢我的分享的话,别忘了点赞加关注哟!
如果你对我的文章有任何看法,欢迎在下方评论留言或者私信我鸭!
我是白晨,我们下次分享见!!!
☀️☀️☀️

你可能感兴趣的:(数据结构,数据结构,链表,c语言,算法,后端)