顺序栈和链栈


一、栈

栈的定义与特点:

stack)是限定仅在表尾进行插入或删除操作的线性表。因此,对于栈来说,可以进行插入或删除的表尾端称为栈顶(top),另一端的表头称为 栈底(bottom)

空栈:若栈中无元素,则称为 空栈。

进栈(PUSH):元素插入到栈中的操作,也叫入栈,压栈。

出栈(POP):删除栈顶元素的操作,也称为退栈、弹栈。

栈的操作符合 后进先出* 原则,因此栈又称为 后进先出的线性表(Last In First Out , LIFO)。

栈的抽象数据类型定义:

ADT 栈{

数据对象:D={ai | ai∈ElemSet, i=1,2,…n,n≥0}

数据关系:S={ | ai-1,ai∈D,i=2,…,n}

约定a1为栈底,an为栈顶

基本操作:

init_stack(*stack)

操作结果:初始化栈,构造一个空的栈

get_length(stack)

初始条件:栈stack已存在

操作结果:返回栈的长度,即stack中元素个数

is_empty(stack)

初始条件:栈stack已存在

操作结果:若stack为空栈则返回1,否则返回0

get_top(stack, *e)

初始条件:栈stack已存在且不为空

操作结果:用e返回栈顶元素值,不出栈

push(*stack, e)

初始条件:栈已存在

操作结果:插入元素e为新的栈顶元素

pop(*stack, *e)

初始条件:栈已存在,且不为空

操作结果:删除栈顶元素,并用e返回其值

}ADT 栈

栈为线性表,有两种存储方式,分别为:顺序栈和链栈

二、顺序栈的代码实现

顺序栈的定义及初始化

须知:1、栈遵循后进先出原则,因此栈顶变化较多,我们需要定义一个栈顶标记来始终指向栈顶元素

2、初始化时让 top初始化为 -1 ,标记为空栈。当压栈时,top会随着数组下标开始从0变化。即 :进入一个元素,top++一次

3、获取栈的长度操作,实际为获取栈中有效元素个数,即top所指元素的数组下标 + 1

#include 
//顺序栈
?
#define MAX_SIZE 100//顺序栈的容量
typedef int SElemType;//栈中元素的类型,以int为例
?
//顺序栈定义
typedef struct seq_stack {
?
SElemType data[MAX_SIZE];//使用数组存储顺序栈的元素
int top;//栈顶标记,对用数组下标;
?
}SEQ_STACK;
?
//初始化
int init_stack(SEQ_STACK *S) {
?
S->top = -1;//栈顶标记初始化为 -1 ,此时对应为空栈
return 1;
}
?
//获取栈的长度(栈中有效元素个数)
int get_length(SEQ_STACK S) {
?
return S.top + 1;
}
?
//判断是否为空栈
int is_empty(SEQ_STACK S) {
return S.top == -1;
}
?

入栈与出栈操作:

注意:

1.若进行入栈操作时,先判断栈是否满,若栈满则不可继续入栈。

2.若进行出栈操作时,先判断栈是否为空,若栈空则不可继续出栈操作。

3.取栈顶元素类似于出栈操作,只不过不删除栈顶元素(即栈顶标记无需自减)

实现代码如下:

//入栈和出栈
/*
一、入栈:判断栈是否栈满
二、出栈:判断是否空栈
三、取栈顶:类似出栈,但不是删除栈顶元素
*/
int push(SEQ_STACK *S,SElemType e) {
if (S->top == MAX_SIZE - 1)
{
return 0;//操作失败
}
S->top++;//栈顶标记 +1 ,指向新的栈顶   这里注意,压栈操作先 栈顶指针 S->top++ 再进元素
S->data[S->top] = e;
return 1;
}
?
int get_pop(SEQ_STACK *S, SElemType *e) { //获得栈顶元素
if (S->top == -1)
{
return 0;//空栈,操作失败
}
*e = S->data[S->top];//将栈顶元素赋值给指针e指向的变量,
?
return 1;
}
?
int pop(SEQ_STACK *S,SElemType *e) {
if (S->top == -1)
{
return 0;//空栈,操作失败
}
*e = S->data[S->top];//将栈顶元素赋值给指针e指向的变量,
S->top--;//栈顶标记 -1 ;这里注意,出栈操作先 取出元素,再栈顶指针 S->top-- 操作
return 1;
}
?

三、顺序栈源码:

#include 
//顺序栈
#define MAX_SIZE 100//顺序栈的容量
typedef int SElemType;//栈中元素的类型,以int为例
?
//顺序栈定义
typedef struct seq_stack {
?
SElemType data[MAX_SIZE];//使用数组存储顺序栈的元素
int top;//栈顶标记,对用数组下标;
?
}SEQ_STACK;
?
//初始化
int init_stack(SEQ_STACK *S) {
?
S->top = -1;//栈顶标记初始化为 -1 ,此时对应为空栈
return 1;
}
?
//获取栈的长度
int get_length(SEQ_STACK S) {
?
return S.top + 1;
}
?
//判断是否为空栈
int is_empty(SEQ_STACK S) {
return S.top == -1;
}
?
//入栈和出栈
/*
一、入栈:判断栈是否栈满
二、出栈:判断是否空栈
三、取栈顶:类似出栈,但不是删除栈顶元素
*/
int push(SEQ_STACK *S,SElemType e) {
if (S->top == MAX_SIZE - 1)
{
return 0;//操作失败
}
S->top++;//栈顶标记 +1 ,指向新的栈顶   这里注意,压栈操作先 栈顶指针 S->top++ 再进元素
S->data[S->top] = e;
return 1;
}
?
int get_pop(SEQ_STACK *S, SElemType *e) { //获得栈顶元素
if (S->top == -1)
{
return 0;//空栈,操作失败
}
*e = S->data[S->top];//将栈顶元素赋值给指针e指向的变量,
?
return 1;
}
?
int pop(SEQ_STACK *S,SElemType *e) {
if (S->top == -1)
{
return 0;//空栈,操作失败
}
*e = S->data[S->top];//将栈顶元素赋值给指针e指向的变量,
S->top--;//栈顶标记 -1 ;这里注意,出栈操作先 取出元素,再栈顶指针 S->top-- 操作
return 1;
}
?
int main() {
SEQ_STACK stack;
init_stack(&stack);
//入栈
push(&stack,1);
push(&stack, 3);
push(&stack, 5);
?
SElemType e;
while (stack.top != -1)
{
int rlt1 = pop(&stack, &e);
if (rlt1 == 1)
{
printf("出栈:%d\n", e);
}
else {
printf("出栈失败!");
}
}
//出栈顺序 5、3、1 先入后出
?
return 0;
}
?

调试结果:

 

四、链栈的代码实现

链栈的定义:

链栈通常用单链表表示,其结点的结构与单链表的结构基本相同。区别在与:单链表中为了结点操作方便而设置了一个“头结点”,而链栈中不需要设置头结点。

P.S:头结点的作用:

1、防止单链表是空的而设的.当链表为空的时候,带头结点的头指针就指向头结点.如果当链表为空的时候,头结点的指针域的数值为NULL.

2、是为了方便单链表的特殊操作,插入在表头或者删除第一个结点.这样就保持了单链表操作的统一性!

3、单链表加上头结点之后,无论单链表是否为空,头指针始终指向头结点,因此空表和非空表的处理也统一了,方便了单链表的操作,也减少了程序的复杂性和出现bug的机会 [1] 。

4、对单链表的多数操作应明确对哪个结点以及该结点的前驱。不带头结点的链表对首元结点、中间结点分别处理等;而带头结点的链表因为有头结点,首元结点、中间结点的操作相同,从而减少分支,使算法变得简单,流程清晰。对单链表进行插入、删除操作时,如果在首元结点之前插入或删除的是首元结点,不带头结点的单链表需改变头指针的值,在TurboC算法的函数形参表中头指针一般使用指针的指针(在C++中使用引用&);而带头结点的单链表不需改变头指针的值,函数参数表中头结点使用指针变量即可,对初学者更易接受。

我们知道,栈的特点是后进先出,而无论是入栈、出栈还是其他操作都只是在操作栈顶元素,用头指针指向栈顶即能直接操作栈顶元素,因此不需要头结点;不需用头结点;不需用头结点;

//链栈
/*
头指针直接指向首元结点
*/
typedef int SElemType;//定义栈元素的类型
typedef struct stack_node {
SElemType data;//数据域
struct stack_node *next;
c
}STACK_NODE,*LINK_STACK;//STACK_NODE定义新结点,*LINK_STACK定义链栈

链栈初始化:

保证链栈初始为空栈

int init_stack(LINK_STACK *S) {
//单链表初始化需要给头结点分配空间,这里不需要头结点,直接置空
*S = NULL;
return 1;
}

判断是否为空栈:

//判断是否为空
int is_empty(LINK_STACK S) {
return S == NULL;
}

获取链栈的长度:

即链栈中有效结点个数

1、定义结点指针指向栈顶元素

2、判断该结点是否有效,即栈顶元素是否为空

3、循环遍历,统计长度,注意栈顶指针后移

//获取链栈长度:获取栈中有效结点个数
int get_length(LINK_STACK S) {
int length = 0;
STACK_NODE *tmp = S;
while (tmp!=NULL)
{
length++;
tmp = tmp->next;
}
return length;
}
?

入栈:

1、定义新结点,并给新结点分配空间

2、判断新结点是否为空

3、给新结点的数据域赋值

4、将栈顶针指地址赋值给新结点的下一地址

5、将新结点的地址给栈顶指针

int push(LINK_STACK *S,SElemType e) {
STACK_NODE *p_top = (stack_node *)malloc(sizeof(STACK_NODE));//定义并给新结点分配空间
if (p_top ==NULL)
{
return 0;
}
p_top->data = e;
p_top->next = *S;//新结点指针域指向当前的链栈栈顶
*S = p_top;//修改栈顶指针为新的结点
return 1;
}

出栈:

与入栈操作类似

注意要将出栈的结点空间释放掉

1、判断栈顶指针是否为空

2、通过指针变量返回栈顶元素的数据域

3、定义指针p_top指向当前栈顶

4、修改栈顶元素,将栈顶指针指向下一结点next

5、释放原栈顶元素空间

//出栈
int pop(LINK_STACK *S,SElemType *e) {
if (*S==NULL)//判断是否空栈
{
return 0;
}
*e = (*S)->data;//将栈顶元素赋值给指针e所指向的变量
STACK_NODE *p_top = *S;
*S = p_top->next;//将栈顶指针的下一结点地址赋值给栈顶指针*S  
free(p_top);//空间释放
?
return 1;
}

取栈顶:

类似出栈,但不需要删除栈顶元素

//取栈顶:取栈顶元素不会删除栈顶结点
int get_pop(LINK_STACK *S, SElemType *e) {
if (*S == NULL)//判断是否空栈
{
return 0;
}
*e = (*S)->data;//将栈顶元素赋值给指针e所指向的变量
return 1;
}

五、链栈源码:

#include 
#include
//链栈
/*
头指针直接指向首元结点
*/
typedef int SElemType;//定义栈元素的类型
typedef struct stack_node {
SElemType data;//数据域
struct stack_node *next;
?
}STACK_NODE,*LINK_STACK;
//链栈初始化:保证栈为空
int init_stack(LINK_STACK *S) {
//单链表初始化需要给头结点分配空间,这里不需要头结点,直接置空
*S = NULL;
return 1;
}
//判断是否为空
int is_empty(LINK_STACK S) {
return S == NULL;
}
?
//获取链栈长度:获取栈中有效结点个数
int get_length(LINK_STACK S) {
int length = 0;
STACK_NODE *tmp = S;
while (tmp!=NULL)
{
length++;
tmp = tmp->next;
}
return length;
}
//入栈、出栈、取栈顶
int push(LINK_STACK *S,SElemType e) {
STACK_NODE *p_top = (stack_node *)malloc(sizeof(STACK_NODE));//定义并给新结点分配空间
if (p_top ==NULL)
{
return 0;
}
p_top->data = e;
p_top->next = *S;//新结点指针域指向当前的链栈栈顶
*S = p_top;//修改栈顶指针为新的结点
return 1;
}
?
//出栈
int pop(LINK_STACK *S,SElemType *e) {
if (*S==NULL)//判断是否空栈
{
return 0;
}
*e = (*S)->data;//将栈顶元素赋值给指针e所指向的变量
STACK_NODE *p_top = *S;
*S = p_top->next;//将栈顶指针的下一结点地址赋值给栈顶指针*S  
free(p_top);//空间释放
?
return 1;
}
//取栈顶:取栈顶元素不会删除栈顶结点
int get_pop(LINK_STACK *S, SElemType *e) {
if (*S == NULL)//判断是否空栈
{
return 0;
}
*e = (*S)->data;//将栈顶元素赋值给指针e所指向的变量
return 1;
}
?
?
?
int main() {
LINK_STACK stack = NULL;//定义链栈并初始化为NULL,或者 STACK_NODE *stack;,stack指向栈顶元素,表示单链表的头指针
//调用初始化函数(防止定义时忘记初始化)
init_stack(&stack);
?
//入栈
push(&stack,1);
push(&stack, 3);
push(&stack, 5);
push(&stack, 7);
?
//出栈
SElemType e;
int rlt1 = pop(&stack,&e);
printf("rlt = %d\t,e = %d\n",rlt1,e);
rlt1 = pop(&stack, &e);
printf("rlt = %d\t,e = %d\n", rlt1, e);
rlt1 = pop(&stack, &e);
printf("rlt = %d\t,e = %d\n", rlt1, e);
rlt1 = pop(&stack, &e);
printf("rlt = %d\t,e = %d\n", rlt1, e);
rlt1 = pop(&stack, &e);
printf("rlt = %d\t,e = %d\n", rlt1, e);
?
return 0;
}

调试结果: