前言:
栈与队列是限定性线性表,与常规线性表的区别在于限制了插入和删除等操作的位置。
在栈中,只能在指定的一段进行插入和删除,俗称 “后进先出”。
在队列中,只能在一段插入,另一端删除,俗称 “先进先出”。
1 栈
1.1 栈的定义
插入和删除运算限制为仅在表的一端进行的线性表。
通常将表中允许进行插入、删除操作的一端称为栈顶,另一端称为栈底。栈底不能进行任何操作。当栈中没有元素时称为空栈。栈的插入操作被称为入栈,删除操作称为出栈。
栈的特点是:“后进先出”(后进栈的先出栈)
1.2 栈的表示和实现
1.2.1 顺序栈
顺序存储结构的栈。利用一组连续的存储单元依次存放自栈底到栈顶的数据元素。
栈顶指针top指示栈顶元素位置,top=-1表示为空栈
1.2.1.1 顺序栈的存储结构
#define StackSize 255
typedef struct{
StackElemType elem[StackSize]; //存放数据元素
int top; //存放栈顶元素下标
}SeqStack;
1.2.1.2 顺序栈的初始化
void InitStack(SeqStack *S){
S->top = -1;
}
1.2.1.3 顺序栈的进栈
int Push(SeqStack * S,StackElementType x){
if(S->top == StackSize-1) //栈满
return false;
S->top++;
S->elem[S->top] = x;
return true;
}
1.2.1.4 顺序栈的出栈
int GetTop(SeqStack * S, StackElementType *x){
if(S->top == -1) //空栈
return false;
else
{
*x = S->elem[S->top];
S->top--;
return true;
}
}
1.2.2 链栈
链栈是采用链表作为存储结构实现的栈。采用带头结点的单链表实现栈。表头指针就作为栈顶指针。
1.2.2.1 链栈的存储结构
typedef struct node
{
StackElementType data;
struct node * next;
}LinkStackNode,*LinkStack;
1.2.2.2 链栈的入栈
int Push(LinkStack top, StackElementType x)
{
LinkStackNode *temp;
temp = (LinkStackNode * )malloc(sizeof(LinkStackNode));
if(temp==NULL) //没有存储空间
return false;
temp->data = x;
temp->next = top->next;
top->next = temp;
return true;
}
1.2.2.3 链栈的出栈
int Pop(LinkStack top,StackElementTyoe *x)
{
LinkStackNode *temp;
temp = top->next;
if(temp==NULL) //空栈
return false;
top->next = temp->next;
*x = temp->data;
free(temp);
return true;
}
1.3 栈与递归的实现
递归算法的特点:
- 分而治之
- 效率低下
1.3.1 递归算法的前提
- 原问题可以层层分解为类似的子问题,且子问题规模更小
- 规模最小的子问题具有直接解(递归出口)
1.3.2 递归工作栈
递归函数运行期间的数据存储区。每一层递归所需的信息构成一个“工作记录”,其中包括所有实参、局部变量以及上一层返回地址
递归工作栈的特点:
- 调用即入栈
- 退出则出栈
- 当前执行层的工作记录必须是递归工作栈栈顶的工作记录,栈顶指针为当前环境指针
2 队列
2.1 队列的定义
只允许在表的一端插入元素,在另一端删除元素的线性表。“先进先出”
2.2 队列的表示和实现
2.2.1 顺序队列
利用一个一维数组来存储,并设立两个指针rear(指向队尾)和 front(指向队首)。
非空顺序队列队头指针(front)指向队头元素,队尾指针(rear)指向最后一个元素的下一位。
2.2.1.1 顺序队列的存储结构
#define MaxQueueSize 255
typedef struct
{
DataType queue[MaxQueueSize];
int rear;
int front;
}SeqCQueue;
2.2.1.2 顺序队列的假溢出
顺序队列的假溢出是指顺序队列因多次入队出队导致有存储空间但无法插入元素的现象。
解决办法:
- 尽可能设置最大元素个数
- 修改出队算法,使每次出队后都把队列中剩余元素向对头方向移动一个位置
- 修改入队算法,增加判断条件,当出现“假溢出”现象,把元素向队头移动
- 采用循环队列
2.2.2 循环队列
把顺序队列所使用的存储空间构造成逻辑上首尾相连的循环队列。当rear和front到达MaxQueueSize-1,再前进一个位置就自动到0。当rear+1 = MaxQueueSize时,rear = 0
2.2.2.1 循环队列的存储结构
#define MaxQueueSize 255 //定义队列最大长度
type struct
{
DataType queue[MaxQueueSize];
int rear;
int front;
int count;
}SeqQueue;
2.2.2.2 循环队列的初始化
//初始化操作
void InitQueue(SeqQueue *Q)
{
Q->front = Q->rear = 0;
}
2.2.2.3 循环队列的入队
int EnterQueue(SeqQueue *Q,QueueElementType x)
{
/* 将元素x入队 */
if((Q->rear+1)%MaxQueueSize==Q->front) //尾指针+1指向了头指针,说明队列已满
return false;
Q->element[Q->rear] = x;
Q->rear = (Q->rear+1)%MaxQueueSize;
return true;
}
2.2.2.3 循环队列的出队
int DeleteQueue(SeqQueue *Q, QueueElementType * x)
{
//删除队列的对头元素,用x返回其值
if(Q->front==Q->rear) //队列为空
return false;
*x = Q->element[Q->front];
Q->front = (Q->front+1)%MaxQueueSize;
return true;
}
2.2.2.3 循环队列的队空队满判断
- 计数器方法
- 队满:count>0 && rear==front
- 对空:count = 0
- 标志位Tag
- 队满:tag == 1 && rear==front
- 对空:tag == 0 && rear==front
- 牺牲一个空间
- 队满:(rear+1)% Max == front
- 对空: rear == front
2.2.3 链队列
链式队列的队头指针指在队列的当前队头结点位置,队尾指针指在队列的当前队尾结点位置。
2.2.2.1 链队列的存储结构
typedef struct Node
{
QueueElementType data;
struct Node * next;
}LinkQueueNode;
typedef struct
{
LinkQueueNode *front;
LinkQueueNode *rear;
}LinkQueue;
3 实验题
3.1 顺序队列的逆置
借助顺序栈完成顺序队列的逆置
代码:
#include <stdio.h>
#include <stdlib.h>
//顺序队列、顺序栈的逆置
//顺序队列结构化描述:
typedef struct Node {
int data;
struct Node *next;
}QueueNode;
typedef struct {
int sum;
QueueNode *front;
QueueNode *rear;
}Queue;
//顺序栈结构化描述
typedef struct SeqStack
{
int elem[20]; //用来存放栈中元素的一维数组
int top; //用于存放栈顶元素下标
}SeqStack;
//顺序队列的初始化
void InitQueue(Queue *Q){
Q->front = (QueueNode *)malloc(sizeof(QueueNode));
Q->rear = Q->front;
Q->front->next = NULL;
Q->sum = 0;
return;
}
//顺序栈初始化
void InitSeqStack(SeqStack *S){
S->top = -1;
return;
}
//顺序队列入队
void EnterQueue(Queue *Q, int x){
QueueNode *e;
e = (QueueNode *)malloc(sizeof(QueueNode));
e->data = x;
e->next = NULL;
Q->rear->next = e;
Q->rear = e;
Q->sum++;
return;
}
//顺序队列出队
int PopQueue(Queue *Q) {
QueueNode *x; //记录出队元素的值
if(Q->rear == Q->front){
return -1; //空队列返回-1
}
x = Q->front->next;
Q->front->next = x->next;
//若x为队列中唯一的元素,则尾指针应指向头指针
if(Q->rear == x){
Q->rear = Q->front;
}
Q->sum--;
return x->data;
}
//顺序栈入栈:将队列中元素依次入栈
void EnterSeqStack(SeqStack *S, Queue *Q){
int x;
int num = Q->sum;
for(int i = 0;i<num;i++){
x = PopQueue(Q);
S->top++;
S->elem[S->top] = x;
}
S->top++;
return;
}
//打印顺序队列
void PrintQueue(Queue *Q){
QueueNode *t;
t = Q->front->next; //t指向Q第一个元素
while(t!=NULL){
printf("%d ",t->data);
t = t->next;
}
return;
}
//打印顺序栈
void PrintSeqStack(SeqStack *S){
for(int i = S->top-1;i>=0;i--){
printf("%d ",S->elem[i]);
}
return;
}
int main(){
Queue Q;
SeqStack S;
InitQueue(&Q);
InitSeqStack(&S);
printf("请依次输入队列的各元素(输入0表示结束):");
while(1){
int x;
scanf("%d", &x);
if(x == 0)
break;
EnterQueue(&Q, x);
}
printf("--------------------------------------------------\n");
printf("输出逆置前为:");
PrintQueue(&Q);
printf("\n");
EnterSeqStack(&S,&Q);
printf("输出逆置后为:");
PrintSeqStack(&S);
return 0;
}
结果如下:
3.2 划分子集
问题描述:
代码:
#include <stdio.h>
#include <stdlib.h>
//划分子集
//R:2,8 9,4 2,9 2,1 2,5 6,2 5,9 5,6 5,4 7,5 7,6 3,7 6,3 (0,0)
//设置全局变量
int n = 0; //记录集合A元素个数
int m = 0; //记录集合B元素个数
//矩阵结构化描述
typedef struct {
int row; //行值
int col; //列值
}Triple;
Triple t[100];
//顺序队列结构化描述
typedef struct Node {
int data;
struct Node *next;
}QueueNode;
typedef struct {
int sum;
QueueNode *front;
QueueNode *rear;
}Queue;
//顺序队列初始化
void InitQueue(Queue *Q){
Q->front = (QueueNode *)malloc(sizeof(QueueNode));
Q->rear = Q->front;
Q->front->next = NULL;
Q->sum = 0;
return;
}
//入队
void EnterQueue(Queue *Q, int x){
QueueNode *e;
e = (QueueNode *)malloc(sizeof(QueueNode));
e->data = x;
e->next = NULL;
Q->rear->next = e;
Q->rear = e;
Q->sum++;
return;
}
//出队
int PopQueue(Queue *Q) {
QueueNode *x;
if(Q->rear == Q->front){
return -1; //空队返回-1
}
x = Q->front->next;
Q->front->next = x->next;
//若x为顺序队列的唯一元素,尾指针指向到头指针
if(Q->rear == x){
Q->rear = Q->front;
}
Q->sum--;
return x->data;
}
//打印队列
void PrintQueue(Queue *Q){
QueueNode *t;
t = Q->front->next;
while(t!=NULL){
printf("%d ",t->data);
t = t->next;
}
return;
}
//判断元素x与队列Result的元素是否冲突
int Conflict(Queue *Result, int x){
QueueNode *temp;
temp = Result->front->next;
while(temp!=NULL){
for(int i=0;i<m;i++){
if((x == t[i].row && temp->data==t[i].col)||(x==t[i].col && temp->data==t[i].row)){
return 1;
}
}
temp = temp->next;
}
return 0;
}
//划分子集
void CollectionDivide(Queue *Result,Queue *Q){
int num = Q->sum;
for(int i=0;i<num;i++){
//遍历Q中每个元素,判断该元素与Result中元素是否冲突
//将Q中首元素取出并将其在队列中删除
int q_data = PopQueue(Q);
if(Conflict(Result,q_data)){
EnterQueue(Q, q_data);
}
else
{
EnterQueue(Result, q_data);
}
}
return;
}
int main(){
Queue Q;
InitQueue(&Q);
int i = 0; //记录矩阵下标值
//集合A、R的初始化
printf("请依次输入集合A中各元素的值(输入0视为结束):");
while(1){
int x;
scanf("%d", &x);
if(x == 0)
break;
EnterQueue(&Q, x);
n++;
}
printf("------------------------------------------------------------------\n");
printf("请依次输入集合R中各元素的值(输入0,0视为结束):");
while(1){
scanf("%d,%d", &t[i].row,&t[i].col);
if(t[i].row == 0 && t[i].col == 0)
break;
i++;
m++;
}
printf("------------------------------------------------------------------\n");
Queue result[100];
int re = 0; //记录result数组下标
while(Q.sum>0){
InitQueue(&result[re]);
EnterQueue(&result[re],PopQueue(&Q));
if(Q.sum>0){
CollectionDivide(&result[re],&Q);
}
re++;
}
for(int i = 0;i<re;i++) {
printf("第%d个子集:",i+1);
PrintQueue(&result[i]);
printf("\n");
}
return 0;
}
结果如下: