栈是在表尾进行插入和删除操作的线性表,允许数据操作的一头称为栈顶,另一端称为栈底,不含数据元素称为空栈。栈即后进先出(LIFO)的线性表。栈和线性表一样,同样有两种存储结构,顺序存储结构和链式存储结构。如果所需的栈大小在可控范围内使用顺序栈,否则使用链栈。
队列是只允许在一端进行插入,另外一端进行删除操作的线性表。队列即先进先出(FIFO)的线性表。队列和线性表一样,同样有两种存储结构,顺序存储结构和链式存储结构(链队列)。确定队列的最大长度情况下可使用循环队列,否则使用链队列。
头尾相接的顺序存储结构队列称为循环队列,这也是这篇文章所介绍的,如果不做循环队列,那么每次队列出队后都要把后面的队伍全部向前移动,无疑提高了时间复杂度。循环队列的思想就是,数据入队就在队尾+1,数据出队也让队头+1,可以理解为一个动态的队头,当超过队列长度后,队尾跑回队头的“前面”,但它依然是队尾。结合后面的例子来理解会好点。
实例一:顺序栈
#include "stdio.h"
#define SIZE 5 // 栈的大小
typedef struct
{
int top;
int data[SIZE];
}stack;
/*
* 功能:数据人栈
* 输入:堆栈指针,数据
* 输出:无
*/
void push(stack *ps,int data)
{
ps->data[++ps->top] = data; // 数组下标先加1,然后把值传入数组
if(ps->top == SIZE-1) // 当数组下标到达栈顶时,提示栈满
{
printf("full stack\n");
}
}
/*
* 功能:数据出栈
* 输出:堆栈指针,数据
* 输出:无
*/
void pop(stack *ps,int *data)
{
if(ps->top>=0)
*data = ps->data[ps->top--]; // 先将数据出栈,再将栈的游标向下移
else
printf("no data\n");
}
void main()
{
stack sta = {-1}; // 堆栈游标一开始为-1是为了方便出栈入栈的一致操作
stack *ps = &sta; // 堆栈指针指向结构体
int data;
pop(ps,&data); // 一开始没有元素可出栈
push(ps,100); // 100入栈
push(ps,101); // ..
push(ps,102); // ..
push(ps,103); // ..
push(ps,104); // 104入栈,此时栈满
pop(ps,&data); // 此时应该是104出栈,赋给data
printf("%d\n",data); // 打印data
printf("%d\n",ps->data[ps->top]); // 此时栈顶数据为103
}
打印结果:
实例二:链栈
#include "malloc.h"
#include "stdio.h"
// 堆栈结点
typedef struct stackNode
{
int data;
struct stackNode *next;
}stackNode;
/*
* 功能:数据入栈
* 输入:堆栈指针(头指针),数据
* 输出:无
*/
void push(stackNode *h,int data)
{
stackNode *s = (stackNode *)malloc(sizeof(stackNode)); // 分配结点内存
s->data = data; // 将数据赋给当前结点
s->next = h->next; // 头结点的指针域赋给当前结点的指针域
h->next = s; // 当前结点指针赋给头结点的指针域
}
/*
* 功能:数据出栈
* 输入:堆栈指针(头指针),数据的指针
* 输出:无
*/
void pop(stackNode *h,int *data)
{
if(h->next != NULL) // 如果头结点指针域存放数据,即栈是有数据的
{
stackNode *s = h->next; // 出栈的结点
*data = s->data; // 数据取出
h->next = s->next; // 把头结点的指针指向出栈结点的指针域,即下一个结点
free(s); // 释放出栈的结点
}else
{
printf("no element\n");
}
}
void main(int argc, char* argv[])
{
stackNode *h = (stackNode *)malloc(sizeof(stackNode));
h->next = NULL;
push(h,100); // 100入栈
push(h,101); // ..
push(h,102); // ..
push(h,103); // ..
push(h,104); // 104入栈
int a;
pop(h,&a); // 104出栈,赋给变量a,并打印
printf("%d\n",a);
pop(h,&a);
printf("%d\n",a);
pop(h,&a);
printf("%d\n",a);
pop(h,&a);
printf("%d\n",a);
pop(h,&a);
printf("%d\n",a);
pop(h,&a); // 没有元素出栈,会提示错误
}
打印结果:
实例三:循环队列
// 循环队列.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include "stdio.h"
#include "malloc.h"
#define SIZE 5
// 循环队列的结构体
typedef struct
{
int data[SIZE];
int front;
int rear;
}queue;
/*
* 功能:数据入队
* 输入:队列结构体指针,数据
* 输出:无
*/
void enQueue(queue *q,int data)
{
if((q->rear+1)%SIZE == q->front) // 由于FIFO,队头位置永远不会比队尾大,
printf("can not insert, the quequ is full\n"); // 所以当队尾循环一圈还差一个数据赶上队头时,表明队列已满,如果不留一// 个数据位置,那么队满和空队列都是front == rear这个条件,两者会有点难以区分。
else
{
q->data[q->rear] = data;
q->rear = (q->rear+1)%SIZE; // 循环队列最重要的一句其实就是这句(rear+1)%SIZE
}
}
/*
* 功能:数据入队
* 输入:队列结构体指针,数据指针
* 输出:无
*/
void deQueue(queue *q,int *data)
{
if(q->front == q->rear)
printf("the queue is empty\n");
else
{
*data = q->data[q->front];
q->front = (q->front+1)%SIZE;
}
}
void main(int argc, char* argv[])
{
int data;
queue *que = (queue *)malloc(sizeof(queue)); // 队列初始化
que->front = 0;
que->rear = 0;
deQueue(que,&data); // 一开始没有数据出队,打印错误
enQueue(que,100); // 101-103入队,
enQueue(que,101);
enQueue(que,102);
enQueue(que,103); // 队列长度为5,其实是4个,此时队列满
enQueue(que,104); // 数据再入队会打印错误
for(int i = 0; i < 4; i++) // 101-103出队,打印出来,此时队列空
{
deQueue(que,&data);
printf("%d\n",data);
}
deQueue(que,&data); // 再出队,打印错误
}
// 当队尾在队头后面时,此时队列长度为: rear - front
// 当队尾循环到前面来了后,队列长度为 rear + (size - front)
// 所以得出的队列长度算法是: (rear-front+size)%size
打印结果:
实例四:链队列
// 链队列.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "stdio.h"
#include "malloc.h"
/*
* 队列结点
*/
typedef struct qNode
{
int data;
struct qNode *next;
}qNode;
/*
* 队头队尾结构体
*/
typedef struct
{
qNode *front,*rear;
}queue;
/*
* 功能:数据入队
* 输入:队头队尾结构体指针,入队数据
* 输出:无
*/
void enQueue(queue *q, int data)
{
qNode *s = (qNode *)malloc(sizeof(qNode)); // 插入的队列结点
s->data = data; // 结点数据
s->next = NULL; // 结点的指针域为NULL
q->rear->next = s; // 当前队尾的指针域为当前结点地址
q->rear = s; // 现在队尾是当前结点
}
void deQueue(queue *q, int *data)
{
if(q->front == q->rear) // 当队头尾一样的时候,说明没有队列没有元素
{
printf("no data in the queue\n");
}
else
{
qNode *s; // 结点指针
s = q->front->next; // 出队时,总是出头结点的指针域,即第一个结点
*data = s->data;
q->front->next = s->next; // 把头结点的指针域设为当前结点的指针域
if(q->rear == s) // 当队尾为第一个结点时(注意第一个结点不是头结点),将队尾指向头结点
{
q->rear = q->front;
}
free(s); // 将第一个结点释放
}
}
void main(int argc, char* argv[])
{
int data;
queue *q = (queue *)malloc(sizeof(queue)); // 队头队尾结构体指针
qNode *h = (qNode *)malloc(sizeof(qNode)); // 头结点结构体指针
q->front = h; // 队头和队尾都指向头结点
q->rear = h;
deQueue(q,&data); // 没有元素出队,打印错误
enQueue(q,100); // 100、101、102入队
enQueue(q,101);
enQueue(q,102);
deQueue(q,&data); // 100、101、102出队,打印出来
printf("%d\n",data);
deQueue(q,&data);
printf("%d\n",data);
deQueue(q,&data); // 执行此次出队时,队尾为第一个结点,队头队尾都指向头结点了,可返回去看函数
printf("%d\n",data);
deQueue(q,&data); // 再出队没有元素,打印错误
enQueue(q,103); // 103入队出队,打印出来
deQueue(q,&data);
printf("%d\n",data);
}
打印结果:
小结:栈是一种后进先出的线性表数据结构,可分为顺序栈和链栈;队列是一种先进先出的线性表数据结构,可分为顺序队列,其中又以循环队列为重,和链队列。