【C语言数据结构与算法】五、线性表之栈

观看这里的uu建议先看顺序表和链表相关内容
线性表之顺序表
线性表之单链表
线性表之双链表

一、前言

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列等。
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
栈是一种特殊的线性表,既可以以数组形式存储也可以以链式结构的形式存储。以数组形式存储称为数组栈(特殊的线性表),以链式结构的形式存储称为链式栈(特殊的链表)。

一、栈

1·栈的概念及其结构

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

2·栈的分类

栈分为数组栈和链式栈。
数组栈:以数组形式存储的栈。
链式栈:以链式结构的形式存储的栈。
数组栈和链式栈优缺点对比:
数组栈:

  • 优点:第一、栈是栈顶进行操作,对数组栈而言,栈顶是顺序表的尾部,顺序表的尾部操作效率很高。第二、顺序表的顺序是正逻辑,即栈顶对应数组末端,栈底对应数组首端,操作方便。第三、顺序表的cache搬运效率比较高。
  • 缺点:顺序表进行扩容后的容量随扩容的次数指数增长,难免会有空间的浪费。

链式栈:

  • 优点:链表没有空间浪费,空间的大小始终是所需要的空间。
  • 缺点:链表进行栈的操作时,如果尾部作为栈顶,单链表的效率不高,只有双向链表进行尾部操作时,效率会高一些,但是效率仍然比顺序表低一些。第二、双向链表操作时结构比较复杂。第三、如果是单链表形式的栈,只有头部作为栈顶,对头部进行操作,效率会高,但是这样链表的顺序是反逻辑,操作不方便。

综合考虑,数组栈是最优选择。

3·栈的接口函数

栈头文件List.h的声明如下:

#pragma once

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

// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;	//数组起点地址指针
	int top; // 栈顶
	int capacity; // 容量
}ST;

// 初始化栈
void StackInit(ST* ps);

// 扩容
void CheckStackcapaticy(ST* ps);

// 入栈
void StackPush(ST* ps, STDataType x);

// 出栈
void StackPop(ST* ps);

// 获取栈顶元素
STDataType StackTop(ST* ps);

// 获取栈中有效元素个数
int StackSize(ST* ps);

// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(ST* ps);

// 销毁栈
void StackDestroy(ST* ps);

// 打印栈
void StackPrint(ST* ps);

三、栈的实现

栈的实现大部分是顺序表中的一些操作,这里不做详细解释,只给源码和某些函数调试结果。
函数的实现思路在代码注释中已详细解释。

1·初始化、扩容与销毁

初始化函数在Stack.c中如下:

// 初始化栈
void StackInit(ST* ps)
{
	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

扩容函数在Stack.c中如下:

// 扩容
void CheckStackcapaticy(ST* ps)
{
	//如果数据存满
	if (ps->top == ps->capacity)
	{
		//如果还未开辟空间,则设置为4,如果开辟空间,则新容量为旧容量的两倍
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//开辟空间
		STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);
		//扩容失败
		if (tmp == NULL)
		{
			printf("realloc fail");
			exit(-1);
		}
		//扩容成功
		else
		{
			ps->a = tmp;
			//当前容量设置为新容量值
			ps->capacity = newcapacity;
		}
	}
}

销毁函数在Stack.c中如下:

// 销毁栈
void StackDestroy(ST* ps)
{
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

以上函数与顺序表中的毫无差别。

2·入栈、出栈、判断栈是否为空

入栈函数在Stack.c中如下:

// 入栈
void StackPush(ST* ps, STDataType x)
{
	//检查并扩容
	CheckStackcapaticy(ps);
	//入栈
	ps->a[ps->top] = x;
	//栈顶加一
	ps->top++;
}

出栈函数在Stack.c中如下:

// 出栈
// 出栈
void StackPop(ST* ps)
{
	//不为空出栈
	assert(!StackEmpty(ps));
	//不能访问相对于删除
	ps->top--;
}

判断栈是否为空在Stack.c中如下:

// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(ST* ps)
{
	return ps->top == 0;
}

具体使用方式如下:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "stack.h"
//入栈出栈
void StackTest1()
{
    ST p ;
    //初始化
    StackInit(&p);
    //入栈1234
    StackPush(&p, 1);
    StackPush(&p, 2);
    StackPush(&p, 3);
    StackPush(&p, 4);

    //出栈
    StackPop(&p);
    StackPop(&p);
    //再次出栈
    StackPop(&p);
    StackPop(&p);
    StackPop(&p);
}

int main()
{
    StackTest1();
    return 0;
}

执行调试结果如下:
1·从栈顶存入1234,并对相应变量进行监测:
在这里插入图片描述
2·以上面情况为基础,出栈两次,并对相应变量进行监测:
在这里插入图片描述
p.top变为2,意味着当前栈中能被访问到的数据只有a[0]和a[1]。

3·以上面情况为基础,再出栈三次,并对相应变量进行监测:
在这里插入图片描述
当前只有两个数据却出栈三次,因此程序中断并报错。

3·获取栈顶元素

获取栈顶元素在Stack.c中如下:

// 获取栈顶元素
STDataType StackTop(ST* ps)
{
	//不为空可获取
	assert(!StackEmpty(ps));
	return ps->a[ps->top-1];
}

具体使用方式如下:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "stack.h"
//获取栈顶元素
void StackTest2()
{
    ST p ;
    //初始化
    StackInit(&p);
    //入栈1234
    StackPush(&p, 1);
    StackPush(&p, 2);
    StackPush(&p, 3);
    StackPush(&p, 4);

    //获取栈顶元素
    STDataType top1 = StackTop(&p);
    //出栈一数据后继续获取栈顶元素
    StackPop(&p);
    STDataType top2 = StackTop(&p);
}

int main()
{
    StackTest2();
    return 0;
}

执行调试结果如下:
从栈顶存入1234,获取栈顶元素并对相应变量进行监测:
在这里插入图片描述

4·从栈顶开始打印数据

打印函数在Stack.c中如下:

// 遍历打印栈
void StackPrint(ST* ps)
{
	//不为空执行
	while (!StackEmpty(ps))
	{
		//从栈顶访问,因此从栈顶遍历打印
		printf("%d ", StackTop(ps));
		//每打印栈顶出栈一次
		StackPop(ps);
	}
}

具体使用方式如下:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "stack.h"

//从栈顶开始打印数据
void StackTest3()
{
    ST p;
    //初始化
    StackInit(&p);
    //入栈1234
    StackPush(&p, 1);
    StackPush(&p, 2);
    StackPush(&p, 3);
    StackPush(&p, 4);

    //从栈顶打印元素
    StackPrint(&p);
}

int main()
{
    StackTest3();
    return 0;
}

执行调试结果如下:
从栈顶存入1234,从栈顶开始打印数据:
在这里插入图片描述

posted @ 2023-04-30 14:10  码上芯路人  阅读(7)  评论(0)    收藏  举报