大话数据结构学习笔记 - 栈与队列
栈
栈是限定尽在表尾进行插入和删除操作的线性表。 允许插入和删除的一端称为栈顶(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 的元素个数
endADT
栈的顺序存储结构
typedef int SElemType; // 此处 SElemType 类型为int
typedef struct
{
SElemType data[MAXSIZE];
int top; // 用于栈顶指针, 空栈 top = -1
}SqStack;
进栈
/* 插入元素 e 为新的栈顶元素 */
Status Push(SqStack *S, SElemType e)
{
if(S->top == MAXSIZE - 1) // 栈满
return ERROR;
S->top++; // 栈顶指针加一
S->data[S->top] = e; // 将新插入元素赋值给栈顶空间
return OK;
}
出栈
// 若栈不空,则删除 S 的栈顶元素, 用 e 返回其值,并返回 OK;否则返回 ERROR
Status Pop(SqStack *S, SElemType *e)
{
if(S->top == -1) // 空栈
return ERROR;
*e = S->data[S->top]; // 将要删除的栈顶元素赋值给 e
S->top--; // 栈顶指针减一
return OK;
}
进栈、出栈时间复杂度均为 O(1)
两栈共享空间
数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0
处,另一个栈为栈的顶端,下标为数组长度n - 1
处。栈1
为空时,top1 == -1
。 栈2
为空时, top2 == n
。栈满时top1 + 1 = top2
// 两栈共享空间
typedef struct
{
SElemType data[MAXSIZE];
int top1; // 栈1 栈顶指针 top1 == -1 时为空
int top2; // 栈2 栈顶指针 top2 == MAXSIZE 时为空
}SqDoubleStack;
进栈
/* 插入元素 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; // 先更新栈顶指针 top1 + 1, 再赋值
else if(stackNumer == 2) // 元素进栈2
S->data[--S->top2] == e; // 先更新栈顶指针 top2 - 1, 再赋值
return OK;
}
出栈
/* 若栈不空,则删除 S 的栈顶元素, 用 e 返回其值,并返回 OK; 否则返回 ERROR */
Status Pop(SqDoubleStack *S, SElemType *e, int stackNumber)
{
if(stackNumber == 1)
{
if(S->top1 == -1)
return ERROR; /* 栈1 为空栈 */
*e = S->data[S->top1--]; /* 栈1 栈顶元素出栈 */
}
else if(stackNumber == 2)
{
if(S->top2 == MAXSIZE)
return ERROR; /* 栈2 为空栈 */
*e = S->data[S->top2++]; /* 栈2 栈顶元素出栈 */
}
return OK;
}
两栈共享空间仅适用于相同数据类型的栈,否则问题会变得复杂
栈的链式存储结构
将栈顶指针用作单链表的头指针,即无需使用头结点。空栈即头指针指向空 top = NULL
typedef struct StackNode
{
SElemType data;
struct StackNode *next;
}StackNode, *LinkStackPtr;
typedef strcut 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;
}
出栈
// 若栈不空,则删除 S 的栈顶元素,用 e 返回其值,并返回 OK。否则返回 ERROR
Status Pop(LinkStack *S, SElemType *e)
{
LinkStackPtr q;
if(StackEmpty(*S))
return ERROR;
q = S->top; // 将栈顶指针赋值给 q
*e = q->data;
S->top = q->next; // 使得栈顶指针后移以为,指向下一结点
free(q); // 释放结点 q
S->count--;
return OK;
}
链栈push
和pop
操作时间复杂度哦均为O(1)
队列
队列(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): 若队列 Q 存在且非空,用 e 返回队列 Q 的队头元素
EnQueue(*Q, e): 若队列 Q 存在,插入新元素 e 到队列 Q 中并成为队尾元素
DeQueue(*Q, *e): 删除队列 Q 中队头元素, 并用 e 返回其值
QueueLength(Q): 返回队列 Q 的元素个数
endADT
循环队列
假溢出:即对列中,数组开头位置为空,而末尾位置被占用,若再入队,则数组越界,即假溢出
解决办法:使用循环队列,即头尾相接的顺序存储结构
循环队列的顺序存储结构
- 判断为空:
front == rear
- 判断队满:
(rear + 1) % MAXSIZE == front
- 队列长度:
(rear - front + MAXSIZE) % MAXSIZE
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;
}
入队
/* 若队列未满,则插入元素 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->rear == Q->front) // 判断队空
return ERROR;
*e = Q->data[Q->front]; // 将队头元素赋值给 e
Q->front = (Q->front + 1) % MAXSIZE; // front 指针向后移一位,若到最后则转到数组头部
}
队列的链式存储结构
队列的链式存储结构,其实就是线性表的单链表,只不过只能尾进头出而已,简称为链队列。 队头指针指向链队列的头结点,队尾指针指向终端节点
空队列时,front
和rear
都指向头结点
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) /* 存储分配失败 */
return ERROR;
s->data = e;
s->next = NULL;
Q->rear->next = s; /* 把拥有元素 e 新节点 s 赋值给原队尾结点的后继 如上图中 圈1*/
Q->rear = s; /* 把当前的 s 设置为队尾节点, rear 指向 s 如上图中 圈2 */
return OK;
}
出队
注意:若除头结点外,只剩下一个元素,则需将rear
指针指向头结点
/* 若队列不空,删除 Q 的队头元素,用 e 返回其值,并返回 OK, 否则返回 ERROR */
Status DeQueue(LinkQueue *Q, QElemType *e)
{
if(Q->front == Q->rear)
return ERROR;
QueuePtr p = Q->front->next; // 将欲删除的队头结点暂存给 p, 见上图中圈 1
*e = p->data; // 将欲删除的队头结点的值赋值给 e
Q->front->next = p->next; // 将原队头结点后继 p->next 赋值给头结点后继, 见上图中圈 2
if(Q->rear == p) // 若对头是队尾,则删除后将 rear 执行头结点 见上图中圈 3
Q->rear = Q->front;
free(p);
return OK;
}