栈
- 栈: 限定仅在表尾进行插入和删除操作的线性表。
- 栈被称为
先进后出
的线性表- 允许进行插入和删除操作的一端称为
栈顶
,另一端称为栈底
- 空栈: 不含任何元素的栈
抽象数据类型
顺序栈
- 结构定义
typedef int SElemType;
typedef struct
{
SElemType data[MAXSIZE];
int top; /* 用于栈顶指针 */
}SqStack;
- 插入元素
Status Push(SqStack * S, SElemType e)
{
if(S->top == MAXSIZE - 1) /* 栈已满 */
return ERROR;
S->data[++S->top] = e;
return OK;
}
- 删除元素
Status Pop(SqStack * S, SElemType * e)
{
if(S->top == -1) /* 栈为空 */
return ERROR;
*e = S->data[top--];
return OK;
}
两栈共享空间
- 基本思路:
- 数组有两个端点,两个栈有两个栈底
- 让一个栈的栈底为数组的始端,即下标为0处,另一个栈底为数组的末端,即下标为数组长度n-1处。
- 两个栈如果增加元素就是两端点向中间延伸
- 当两个栈都是空栈,栈1的top为
-1
,栈2的top为n
- 数组插满的判断条件:
top1+1==top2
- 适用场景:
通常用于两个栈的空间需求具有相反关系:一个栈在增长,连一个栈在缩短
/* 两站共享空间结构 */
typedef struct
{
SElemType data[MAXSIZE];
int top1; /* 栈1的栈顶指针 */
int top2; /* 栈2的栈顶指针 */
}SqDoubleStack;
- 插入元素
Status Push(SqDoubleStack *S, SElemType e, int stackNumber)
{
if(S->top1 + 1 == S->top2) /* 两栈共享数组已被插满 */
return ERROR;
if(stackNumber == 1)
S->data[++S->top1] = e;
else
S->data[--S->top2] = e;
return OK;
}
- 删除元素:
Status Pop(SqDoubleStack *S, SElemType * e, int stackNumber)
{
if(stackNumber == 1)
{
if(S->top1 == -1)
return ERROR;
*e = S->data[top1--];
}
else
{
if(S->top2 == MAXSIZE)
return ERROR;
*e = S->data[top2++];
}
return OK;
}
链栈
- 栈顶放在单链表的头部,在链栈中不需要头结点。
- 空链栈:
top=NULL
时,为空链栈
typedef struct StackNode
{
SElemType data;
struct StackNode * next;
}StackNode, * LinkStackPtr;
typedef struct LinkStack
{
LinkStackPtr top;
int count;
}LinkStack;
- 入栈操作:
Status Push(LinkStack * S, SElemType e)
{
LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
s->data = e;
s->next = S->top;
S->top = s;
count++;
return OK;
}
- 出栈操作:
Status Pop(LinkStack *S, SElemType *e)
{
LinkStackPtr p;
if(StackEmpty(*S))
return ERROR;
*e = S->top->data;
p = S->top;
S->top = S->top->next;
free(p);
S->count--;
return OK;
}
队列
- 定义:
只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
- 队列是一种先进先出(FIFO)线性表
- 允许插入的一端称为队尾,允许删除的一端称为队头。
抽象数据类型
顺序存储结构
简单顺序存储
- 队列顺序存储的不足:
- 入列时时间的复杂度为O(1)
- 出栈时时间的复杂度为O(n)(复杂度较高):
- front指针: 指向队列的队头元素。
- rear指针: 指向队尾元素的下一个位置。
循环对列
- 定义:
对列的头尾相连接的顺序存储结构。
- 存储结构:
typedef int QElemType;
typedef struct
{
QElemType data[MAXSIZE];
int front; /* 头指针 */
int rear; /* 尾指针 */
}SqQueue;
- 初始化:
Status InitQueue(SqQueue * Q)
{
Q->front = 0;
Q->rear = 0;
return OK;
}
- 入队操作:
Status EnQueue(SqQueue * Q, QElemType e)
{
/* 判断队列是否已满 */
if((Q->rear + 1) % MAXSIZE == Q->front)
return ERROR;
Q->data[rear] = e;
/* rear指针向后移一个位置 */
Q->rear = (Q->rear+1) % MAXSIZE; /* 若到最后则转到头部 */
return OK;
}
- 出队操作:
Status DeQueue(SqQueue * Q, QElemType * e)
{
/* 判断队列是否为空 */
if(Q->front == Q->rear)
return ERROR;
/* 将队头元素赋值给e */
*e = Q->data[Q->front];
/* front指针向后移一位置 */
Q->front = (Q->front + 1) % MAXSIZE; /* 若到最后则转到头部 */
return OK;
}
链式存储结构
- 定义
- 只能尾进头出的单链表
- 队头指针指向链队列的头结点,而队尾指针指向终端结点。
- 空队列:
- 存储结构:
typedef int QElemType;
/* 结点结构 */
typedef struct QNode
{
QElemType data;
struct QNode * next;
}QNode, *QueuePtr;
/* 队列的链表结构 */
typedef struct
{
/* 队头、队尾指针 */
QueuePtr front, rear;
}LinkQueue;
-
入栈操作
-
出栈操作:
/* 若队列不空,删除队头元素,用e返回其值,并返回OK,否则返回ERROR */
Status DeQueue(LinkQueue * Q, QElemType *e)
{
QueuePtr p;
if(Q->front == Q->rear)
return ERROR;
/* 将欲删除的队头结点暂存给p*/
p = Q->front->next;
/* 将欲删除的队头结点的值赋值给e */
*e = p->data;
/* 将原队头结点的后继p->next赋值给头结点的后继 */
Q->front->next = p->next;
/*若队头是队尾,则删除后将rear指向头结点*/
if(Q->rear == p)
Q->rear = Q->front;
free(p);
return OK;
}
- 出队操作就是将头结点的后继结点出队,将头结点改为它后面的结点。
- 若出队前链表除头结点外只剩下一个元素时,则需要将rear指向头结点。
循环对列与链队列的区别
- 循环对列是事先申请好空间,使用期间不释放,链队列在使用期间要进行申请和释放空间。
- 循环对列必须要有一个固定的长度,可能会产生空间的浪费,而链队列不存在这个问题。
- 在可以确定队列长度最大值的情况下,使用循环对列,无法预估对列 长度时,使用链队列。