【数据结构】3.1栈

在前面的学习中,我们学习了最基本经典的线性表,和一些相关基本操作,而今天要学习的是一种比较特殊的性线表:  

栈是什么?

栈的性质

栈是一种特殊的线性表,它不仅具有普通线性表的性质(即元素类型相同,一对一,一头一尾),还具有另外一个重要性质:

Last In First Out

这是什么意思呢?

我们可以把栈想象成一个容器,里面的元素就是一个一个的盘子

这个容器的特点就是 第一个放进去的盘子最后才能拿出来,最后放进去的盘子,最先拿出来

这也就是栈的特点 第一个进来 最后一个出去


栈的基本操作 

栈的最基本操作只有两种: 入栈(Push)和出栈(Pop) 

相当于线性表只能在末尾插入,删除只能从头删除


当入栈顺序为 A B C时,出栈顺序可以有哪几种呢?

1.ABC (A进A出,B进B出,C进C出)

2.ACB(A进A出,B进C进,C出B出)

3.BAC(A进B进,B出A出,C出)

4.CBA(A进B进C进C出B出A出)

5.BCA(A进B进B出,C进C出A出) //这个难想一点

怎么能写的不重不漏呢?可以借助树状图


接下来我们依旧从存储结构入手,分别讲栈的顺序存储和链式存储

栈的顺序表示: 顺序栈

顺序栈,即用一个数组来表示。在之前的线性表学习中,我们知道使用顺序存储时,需要用一个结构体去储存表的基本信息(首元素地址,表长,表的容量)。

在栈中,这些都没有变化。首元素地址是指栈底元素,表长有了一个新的表达方式 :top

top = 0 时,代表这是个空栈,没有任何元素;每有一个元素被压入栈中,top++;每有一个元素被弹出栈时,top--。

top代表了栈中的元素个数 (有些版本中top从-1开始,top代表栈顶元素的下标)


基本操作

栈的定义

#define INITSIZE 100
typedef struct {
	int* base;//栈底元素的地址
	int top; //栈顶指针,实际上是一个数
	int maxSize; //栈的容量
}sqStack;

初始化空栈

void initStack(sqStack* S) {
	S->base = (int*)malloc(INITSIZE * sizeof(int));
	if (!S->base) {
		printf("Overflow!"); exit(1);
	}
	S->top = 0;
	S->maxSize = INITSIZE;
}

获取长度

int getLen(sqStack* S) {
	return S->top;
}

获取栈顶元素

int getTop(sqStack* S, int* x) {
	if (S->top == 0) return 0; //注意检查栈是否为空
	*x = S->base[S->top -1];//注意栈中有top个元素时,栈顶元素下标为top-1
	return 1;
}

 元素进栈(push)

int push(sqStack* S, int x) {
	//先检查栈是否已满,如果满了则重新分配空间
	if (S->top == S->maxSize) {
		S->base = (int*)realloc(S->base, (S->maxSize + 1) * sizeof(int));
	if (!S->base) {
		printf("Overflow!"); exit(1);}
	S->maxSize++;
	}
	S->base[S->top++] = x;//先放元素,top后++
	return 1;
}

元素出栈

int pop(sqStack* S, int* x) {
	//先检查栈是否为空
	if (S->top == 0) {
		return 0;
	}
	*x = S->base[--S->top]; 
	return 1;
}

解释一下*x = S->base[--S->top]: 

比如说当栈里有五个元素时,top  = 5; 那么栈顶元素下标为4 ,--top正好可以带回被弹出的元素的值,并且修改弹出元素后的top值

判断栈是否为空

int emptyStack(sqStack* S) {
	return (S->top == 0 ? 1 : 0);
}

输出栈的元素

void outputStack(sqStack* S) {
	int i = 0;
	while (i < S->top) {
		printf("%d ", S->base[i]);
		i++;
	}
}

两栈共享空间

当内存受限,可以通过共享内存来节约空间。将两个栈的栈底设置到数组的两端,让它们的栈顶向数组中间延长。当两个栈的栈顶相遇时,才会发生“上溢”。

在定义栈的时候,使用两个指针top1 top2来分别代表两个栈的栈顶就行

基本操作会有一点点变化

(1)向以数组末端的为栈底的栈压数据时top--,出栈时++

(2)判断栈是否会溢出是看两个栈的top是否相同

typedef struct {
    int capacity;
    int *data;
    int top1;
    int top2;
} TwoStacks;

TwoStacks* createStacks() {
    int capacility = 50; //假设初始大小为50
    TwoStacks* stacks = (TwoStacks*)malloc(sizeof(TwoStacks));
    stacks->capacity = capacity;
    stacks->data = (int*)malloc(capacity * sizeof(int));
    stacks->top1 = 0;      // 栈1的栈顶指针
    stacks->top2 = capacity - 1; // 栈2的栈顶指针
    return stacks;
}

void push1(TwoStacks* stacks, int value) {
    if (stacks->top1 + 1 == stacks->top2) {
        printf("Stack 1 overflow\n");
        return;
    }
    stacks->data[stacks->top1 ++] = value;
}

void push2(TwoStacks* stacks, int value) {
    if (stacks->top2 - 1 == stacks->top1) {
        printf("Stack 2 overflow\n");
        return;
    }
    stacks->data[stacks->top2--] = value;
}

int pop1(TwoStacks* stacks) {
    if (stacks->top1 == 0) {
        printf("Stack 1 underflow\n");
        exit(1);
    }
    return stacks->data[--stacks->top1];
}

int pop2(TwoStacks* stacks) {
    if (stacks->top2 == stacks->capacity - 1) {
        printf("Stack 2 underflow\n");
        exit(1);
    }
    return stacks->data[++stacks->top2];
}

 

栈的链式表示: 链栈

和顺序栈的不同之处在于:

1.链栈没有最大容量的限制

2.只在表头操作: 即 头插法压入元素,从头弹出元素;

3.top指向链表的首结点代表栈顶;bottom指向链表的尾结点代表栈底。

基本操作

基本操作也很简单 我就不详细写了,直接上代码

typedef struct node {
	int data;
	struct node* next;
}linkStack;

linkStack* initStack() {
	linkStack* S = (linkStack*)malloc(sizeof(linkStack));
	if (!S) exit(1);
	S->next = NULL;
	return S;
}
int push(linkStack* S,int x) {
	//为新元素申请结点
	linkStack* m = (linkStack*)malloc(sizeof(linkStack));
	if (!m) return 0;
	m->data = x;
	//头插
	m->next = S->next;
	S->next = m;
	return 1;
}
int pop(linkStack* S, int* x) {
	//栈为空,弹栈失败
	if (!S->next) return 0;
	linkStack* m;
	m = S->next; //栈顶地址
	*x = m->data; //带回栈顶元素的值
	//删除栈顶元素的结点
	S->next = m->next; 
	free(m);
	return 1;
	
}

可以补两句关于如何摧毁一个链栈 如果是顺序栈的话 直接将首元素地址free就行了

但是 摧毁链栈必须要摧毁所有的结点,因为每一个结点后面都储存了下一个结点的位置,如果只释放头结点,后续结点不会被释放,会导致内存泄漏

int destroy(linkStack* S) {
	linkStack* m, * n; //两个指针一前一后遍历链表
	m = S; n = S->next;
	while (m) {
		free(m);
		m = n;
		n = n->next;
	}
	free(n);
}

 在这节中,我们学习了栈的基本性质,从顺序存储和链式存储两个角度分别写了一些基本操作的代码。如果有什么问题,欢迎在评论区批评指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值