【C数据结构】认识栈并用数组和链表实现栈

一、认识栈

 栈是一种先入后出(或是后入先出)的结构,通常插入和删除操作的一端称为栈顶,另一端称为栈底。

 栈的结构也是线性表的一种,结构实现一般可以用数组或者链表实现,相对而言数组的结构实现更优一点。因为数组在尾上插入数据的代价比较小。

 结构选择以及思路:
  如果用数组栈的话:可以通过malloc一个动态空间,创建一个指针指向这个空间,为了判断空间的容量情况,可以通过创建一个top记录有效数据数量,一个capacity记录空间最大容量。
  如果用链式栈的话:如果用尾做栈顶,尾插尾删,如果不设计成双向链表,每次尾删都需要遍历找尾,很麻烦。如果用头做栈顶的话,头插头删,这样就可以设计较方便的单链表。

二、栈的应用场景

其实在系统中的内存有一部分被划分的内存区域也被称为 ,函数的栈帧就是在这一块区域内开辟,所以一些临时变量也储存在这个区域。栈中,空间使用自上而下(由高地址到低地址),并且栈很小,一般8M。

而本章的数据结构中的栈,它是我们通过在系统内存中的堆区进行模拟实现出来的,它随着程序结束而销毁。堆存储着我们用动态内存开辟的变量,空间总体由下而上(但并不是一定上一个地址低于下一个,可能使用后又销毁),堆区很大,一般以G为单位。

正因为堆区很大,栈区很小,所以当一些使用递归的算法递归太深出现栈溢出,并且不好使用迭代进行实现的时候,可以通过在堆区模拟栈,从而实现算法。

在这里插入图片描述

三、数组栈的实现

 1.准备测试页和头文件

头文件:Stack.h

//Stack.h
#include<stdio.h>;
#include<stdlib.h>;
#include<assert.h>; //断言所需要调的库
#include<stdbool.h>;//布尔型调用的库

typedef int STDataType;

typedef struct stack {
	STDataType* a;//动态空间指针
	int top;
	int capacity;
}ST;

//所需要实现的函数

//初始化栈
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);

测试页:Test.c

//Test.c
#include"Stack.h"
void StackTest1() {
	ST st;//定义结构体变量
	StackInit(&st);
	StackPush(&st, 1);
	//以下可以自己调用测试
	...
}

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

数组栈用动态顺序表这种更优的方式实现。

 2.初始化和销毁栈操作的实现

接下来的函数操作在源文件Stack.c中实现。

//Stack.c
//栈的初始化
#include"Stack.h"
void StackInit(ST* ps) {
	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;//或者-1
}

先将结构体ST中的值先初始化,为了接下来的操作观察更方便。

//Stack.c
//栈的销毁
void StackDestroy(ST* ps) {
	free(ps->a); //释放a指针指向的空间
	ps->a = NULL;
	ps->capacity = ps->top = 0;//一切回归初始
}

 3.入栈和出栈操作的实现

//Stack.c
//入栈操作
void StackPush(ST* ps, STDataType x) {

	if (ps->capacity == ps->top) {  //先判断 有效数据数是否等于最大容量
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//建立新的容量变量
		STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);//用realloc 扩建空间大小
		if (tmp == NULL) {
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp; //赋值给结构体中的
		ps->capacity = newcapacity;
	}
	ps->a[ps->top++] = x; //赋值入栈 top++
}
//Stack.c
//出栈操作
void StackPop(ST* ps) {
	/*assert(ps->top > 0);*/
	assert(!StackEmpty(ps));//如果为不为空程序继续,否则报错。
	ps->top--;//先进先出 直接--
}

 4.判空、取值、查看有效数量操作的实现

//Stack.c
//判空操作
bool StackEmpty(ST* ps) {
	return ps->top==0; //说明栈里的有效数据数为0,栈为空
}
//Stack.c
//取栈顶数据操作
STDataType StackTop(ST* ps) {
	assert(!StackEmpty(ps)); //为空报错
	return ps->a[ps->top-1];//因为每次在给top位置赋值后,top会++
}
//Stack.c
//取栈有效数据数
int StackSize(ST* ps) {
	return ps->top; //就是top的数
}

四、链式栈的实现

如果用头做栈顶,头插头删,这样就可以设计较方便的单链表。

 1.准备测试页和头文件

头文件:SLStack.h

//STStack.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

typedef int SDataType;
typedef struct stack {
	struct stack* next;
	SDataType data;
}ST;

void StackPush(ST** top,SDataType x);

void StackPop(ST** top);

SDataType StackTop(ST** top);

int StackSize(ST** top);

bool StackEmpty(ST** top);

void StackDestroy(ST** top);

测试页:SLTest.c

#include"SLStack.h"
void Test1() {
	ST* ps=NULL;
	StackPush(&ps, 1);
	printf("%d", StackTop(&ps));
	StackPush(&ps, 2);
	printf("%d", StackTop(&ps));
	StackPush(&ps, 3);
	printf("%d", StackTop(&ps));
	StackPush(&ps, 4);
	printf("%d\n", StackTop(&ps));
	printf("%d\n", StackSize(&ps));
	while (!StackEmpty(&ps)) {
		printf("%d", StackTop(&ps));
		StackPop(&ps);
	}

	StackDestroy(&ps);
}

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

 2.入栈和出栈操作的实现

先创建单个结点

//SLStack.c
#include"SLStack.h"
//创建单个结点
ST* StackBuy(SDataType x) {
	ST* newnode = (ST*)malloc(sizeof(ST));
	if (newnode == NULL) {
		printf("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//SLStack.c
//入栈
void StackPush(ST** top, SDataType x) {
	assert(top);//如果传的是指针变量的值 报错
	ST* newnode = StackBuy(x);
	if (*top == NULL) { //为空直接指向新结点
		*top = newnode;
	}
	else {
		newnode->next = *top; //链表头插
		*top = newnode;
	}
}
//SLStack.c
//出栈
void StackPop(ST** top) {
	assert(!StackEmpty(top));
	if ((*top)->next == NULL) {
		free(*top);
		*top = NULL;
	}
	else {
		ST* prev = (*top)->next; //链表头删
		free(*top);
		*top =prev;
	}

}

 3.判空和销毁栈操作的实现

//SLStack.c
//判空
bool StackEmpty(ST** top) {
	return (*top)==NULL;
}
//SLStack.c
//销毁栈
void StackDestroy(ST** top) {
	ST* cur = *top;
	while (cur) {
		ST* next = cur->next;
		free(cur);
		cur = next;
	}
}

 4.取值、查看有效数据量操作的实现

//SLStack.c
//查看栈顶值
SDataType StackTop(ST** top) {
	assert(!StackEmpty(top));
	return (*top)->data;
}
//SLStack.c
//查看有效数据量
int StackSize(ST** top) {
	assert(!StackEmpty(top));
	int i = 1;
	ST* cur = *top;
	while (cur->next) {
		cur = cur->next;
		i++;
	}
	return i;
}

五、一道经典题

有效的括号
在这里插入图片描述

在这里插入图片描述
思路:

  1. 通过观察,第一次判断是第一次出现右括号时候,和左边的左括号进行匹对,并且在第一次碰到右括号前,左括号都是后到的先匹对,所以通过这一特点,似乎可以用栈试试。
  2. 如果将第一次出现右括号的之前的左括号全部入栈,在遇到右括号后,出一个与其匹对,这样思路就感觉对了。
  3. 在一般情况后还要考虑极端情况,比如空、左括号比较多与右括号多这种数量不匹配的情况。

C语言代码实现

//...
//由于C没有栈,在这之前先得把C中实现的栈搬过来。
//头文件和源文件
//...

bool isValid(char * s){
    ST st;
    StackInit(&st);
    while(*s)
    {
    	//左括号全部入栈
        if(*s == '{' || *s=='[' || *s=='(')
        {
            StackPush(&st, *s);
        }
        //遇到右括号出栈匹对
        else
        {
        	//针对空与右括号多的情况
            if(StackEmpty(&st))
            {
                return false;
            }
            STDataType top = StackTop(&st);
            if((*s == '}' && top != '{')
            || (*s == ']' && top != '[')
            || (*s == ')' && top != '('))
            {
                return false;
            }
            StackPop(&st);
        }
        s++;
    }
    
    //针对左括号多的情况
    bool flag = StackEmpty(&st);
    return flag;
}

在这里插入图片描述

六、总结

栈的实现总体来说,用数组实现是非常容易的,链式栈的实现相对有点复杂,链式栈如果用尾插尾删的方式实现入栈进栈,需要每次遍历尾部结点,所以通过头插头删实现会更加便利。

以下是完整代码:
完整代码

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值