Table of Contents
C语言具体实现见:数据结构C语言实现-3—栈与队列
栈
栈(stack):是限定仅在表尾进行插入和删除操作的线性表。(注意,表尾是栈顶,不是栈底)
允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),栈又称为后进先出(Last In First Out),简称LIFO结构
栈的抽象数据类型
ADT 栈(stack)
Data
同线性表,元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
InitStack(*S):初始化操作,建立一个空栈 S;
DestroyStack(*S):若栈存在,则销毁它;
ClearStack(*S):将栈清空;
StackEmpty(S):若栈为空返回True,否则返回False;
GetTop(S, *e):若栈存在且非空,用 e 返回 S 的栈顶元素;
Push(*S, e):若栈 S 存在,插入新元素 e 到栈 S 中;
Pop(*S, *e):删除栈 S 中栈顶元素,并用 e 返回其值;
StackLength(S):返回栈 S 的元素个数。
栈的顺序存储结构
栈的结构定义:
typedef int SElemType
typedef struct
{
SElemType data[MAXSIZE];
int top; /* 用于栈顶指针 */
}SqStack;
进栈操作(push):
/* 插入元素 e 为新的栈顶元素 */
Status Push(SqStack *S, SElemType e)
{
if(S->top == MAXSIZE-1) /* 栈满 */
return ERROR;
S->top++;
S->data[S->top] = e;
return OK;
}
出栈操作(pop):
Status Pop(SqStack *S, SElemType *e)
{
if(S->top == -1)
return ERROR;
*e = S->data[S->top];
S->top--;
return OK;
}
两栈共享空间
当有两个相同类型的栈时(这个前提条件很重要),为节省空间,完全可以用一个数组来存储两个栈。
/* 两栈共享空间结构 */
typedef struct
{
SElemType data[MAXSIZE];
int top1; /* 栈 1 栈顶指针 */
int top2; /* 栈 2 栈顶指针 */
}SqDoubleStack;
两栈共享空间的 Push 方法
/* 插入元素 e 称为新的栈顶 */
Status Push(SqDoubleStack *S, SElemType e, int stackNumber)
{
if(S->top1+1 == S->top2) /*判断栈已满*/
return ERROR;
if(stackNumber == 1) /* 栈 1 有元素进栈 */
S->data[++S->top1] = e;
else if(stackNumber == 2) /* 栈 2 有元素进栈 */
S->data[--S->top2] = e;
return OK;
}
两栈共享空间的 Pop 方法
Status Pop(SqDoubleStack *S, SElemType *e, int stackNumber)
{
if(stackNumber == 1)
{
if(S->top1 == -1) /* 说明栈 1 是空栈 */
return ERROR;
*e = S->data[S->top1--];
}
else if(stackNumber == 2)
{
if(S->top2 == MAXSIZE) /* 说明栈 2 是空栈 */
return ERROR;
*e = S->data[S->top2++];
}
return OK;
}
栈的链式存储结构
栈的链式存储结构,简称为链栈
链栈的结构代码如下:
typedef struct StackNode
{
SElemType data;
struct StackNode *next;
}StackNode, *LinkStackPtr;
typedef struct LinkStack
{
LinkStackPtr top;
int count;
}LinkStack;
进栈操作
/* 插入元素 e 为新的栈顶元素 */
Status Push(LinkStack *S, SElemType e)
{
LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode));
s->data = e;
s->next = S->top; /* 把当前的栈顶元素赋值给新结点的直接后继 */
S->top = s; /* 将新的结点s赋值给栈顶指针 */
S->count++;
return OK;
}
出栈操作
Status Pop(LinkStack *S,SElemType *e)
{
LinkStackPtr p;
if(StackEmpty(*S))
return ERROR;
*e = S->top->data;
p = S->top; /* 将栈顶结点赋值给p */
S->top = S->top->next; /* 使得栈顶指针下移一位,指向后一结点 */
free(p); /* 释放结点p */
S->count--;
return OK;
}
队列
队列(queue):是只允许再一端进行插入操作,而在另一端进行删除操作的线性表
队列是一种先进先出(First In First Out)的线性表,简称为FIFO,允许插入的一端称为队尾,允许删除的一端称为队头
队列的抽象数据类型
ADT 队列(Queue)
Data
同线性表,元素具有相同的数据类型,相邻元素具有前驱和后继关系
Operation
InitQueue(*Q):初始化操作,建立一个空队列 Q;
DestroyQueue(*Q):若队列 Q 存在,则销毁它;
ClearQueue(*Q):清空队列 Q;
QueueEmpty(Q):若队列 Q 为空,返回true,否则返回false;
GetHead(Q, *e):用 e 返回队列 Q 的队头元素;
EnQueue(*Q, e):插入新元素 e 到队列 Q 中并成为队尾元素;
DeQueue(*Q, *e):删除队列 Q 中队头元素,并用 e 返回其值;
QueueLength(Q):返回队列 Q 的元素个数。
队列的顺序存储结构
队列的顺序存储结构有一个明显的不足:
具体的实现和线性表的顺序存储结构完全相同,详见,数据结构理论基础-1—线性表。
为此引入两个指针,front 指针指向队头元素,rear 指针指向队尾元素的下一个位置
但此时还有问题:
循环队列:队列的头尾相接的顺序存储结构
typedef int QElemType;
/* 循环队列的顺序存储结构 */
typedef struct
{
QElemType data[MAXSIZE];
int front; /* 头指针 */
int rear; /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SqQueue;
/* 初始化一个空队列Q */
Status InitQueue(SqQueue *Q)
{
Q->front=0;
Q->rear=0;
return OK;
}
/* 返回Q的元素个数,也就是队列的当前长度 */
int QueueLength(SqQueue Q)
{
return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}
循环队列的入队操作
/* 若队列未满,则插入元素e为Q新的队尾元素 */
Status EnQueue(SqQueue *Q,QElemType e)
{
if ((Q->rear+1)%MAXSIZE == Q->front) /* 队列满的判断 */
return ERROR;
Q->data[Q->rear] = e; /* 将元素e赋值给队尾 */
Q->rear = (Q->rear+1) % MAXSIZE;/* rear指针向后移一位置, */
/* 若到最后则转到数组头部 */
return OK;
}
循环队列的出队操作
/* 若队列不空,则删除Q中队头元素,用e返回其值 */
Status DeQueue(SqQueue *Q,QElemType *e)
{
if (Q->front == Q->rear) /* 队列空的判断 */
return ERROR;
*e = Q->data[Q->front]; /* 将队头元素赋值给e */
Q->front = (Q->front+1) % MAXSIZE; /* front指针向后移一位置, */
/* 若到最后则转到数组头部 */
return OK;
}
队列的链式存储结构
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出,简称为链队列
typedef int QElemType;
typedef struct QNode /* 结点结构 */
{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct /* 队列的链表结构 */
{
QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;
入队操作
/* 插入元素e为Q的新的队尾元素 */
Status EnQueue(LinkQueue *Q, QElemType e)
{
QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
if(!s) /* 存储分配失败 */
exit(OVERFLOW);
s->data = e;
s->next = NULL;
Q->rear->next = s; /* 把拥有元素e的新结点s赋值给原队尾结点的后继 */
Q->rear = s; /* 把当前的s设置为队尾结点,rear指向s */
return OK;
}
出队操作
/* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */
Status DeQueue(LinkQueue *Q, QElemType *e)
{
QueuePtr p;
if(Q->front == Q->rear)
return ERROR;
p = Q->front->next; /* 将欲删除的队头结点暂存给p */
*e = p->data; /* 将欲删除的队头结点的值赋值给e */
Q->front->next = p->next; /* 将原队头结点的后继p->next赋值给头结点后继 */
if(Q->rear == p) /* 若队头就是队尾,则删除后将rear指向头结点 */
Q->rear = Q->front;
free(p);
return OK;
}
栈和队列都是特殊的线性表,只不过对插入和删除操作做了限制。
它们的顺序存储结构,都有着线性表顺序存储的一些弊端。对于栈来说,如果是两个相同数据类型的栈,则可以用数组的两端做栈底的方法来让两个栈共享数据,这样可以最大化地利用数组地空间。对于队列来说,为了避免数组插入和删除时需要移动数据,就引入了循环队列,使得队头和队尾可以在数组中循环变化,解决了移动数据地时间损耗,使得本来插入和删除的复杂度由O(n)变成了O(1).
它们的链式存储结构的实现和线性表基本相同。