定义:栈(stack)是限定仅在表尾进行插入(push)和输出(pop)操作的线性表。
我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据的称为空栈。栈又称为后进先出的线性表,简称为LIFO。
这里介绍两种栈的结构:顺序栈,链栈。
顺序栈采用的是数组的形式存储栈元素(就是我们通常理解的栈),而链栈采用的是链表形式对栈元素进行存储,对于两者的特性可参考数组和链表的属性比较。
#define MAXSIZE 20 /* 存储空间初始分配量 */
/* 首先定义一个顺序栈结构,可以看出该顺序栈结构主要是数组和指针 */
typedef struct {
int data[MAXSIZE]; //定义该堆栈的大小
int top; //用于指向栈顶
}SqStack;
/* 构造一个空栈S */
int InitStack(SqStack *S)
{
S->top = -1; //对栈进行初始化其实就是使其栈顶指针指向栈底
return 1;
}
/* 把S置为空栈 */
int ClearStack(SqStack *S)
{
S->top = -1; //设置栈为空栈也就是使其栈顶指针指向栈底
return 1;
}
/* 判断栈是否为空栈 */
int StackEmpty(SqStack *S)
{
if (S->top == -1) //也就是判断栈顶指针是否指向栈低
return 1;
else
return 0;
}
/* 返回S的元素个数,即栈的长度 */
int StackLength(SqStack *S)
{
return S->top + 1; //判断当前栈的元素个数,也就是栈顶指针位置加1(因为从-1开始)
}
/* 获取栈顶节的data */
int GetTop(SqStack *S, int *e)
{
if (S->top == -1) //栈顶指向栈低,表示栈为空
return 0;
else
*e = S->data[S->top]; //取出栈顶元素
return 1;
}
/* push */
int Push(SqStack *S, int e)
{
if (S->top == MAXSIZE - 1) //栈满了,返回错误
{
return 0;
}
S->top++; //栈顶指针增加一
S->data[S->top] = e; //将新插入元素赋值给栈顶空间
return 1;
}
/* pop*/
int Pop(SqStack *S, int *e)
{
if (S->top == -1) //栈为空
return 0;
*e = S->data[S->top]; //将要删除的栈顶元素取出赋值给e
S->top--; //栈顶指针减一
return 1;
}
/* 从栈底到栈顶依次对栈中每个元素显示 */
int StackTraverse(SqStack *S)
{
int i;
i = 0;
while (i <= S->top)
{
printf("%d ", S->data[i++]);
}
printf("\n");
return 1;
}
/*在主函数中进行测试*/
#include "stdio.h"
#include "ctype.h"
#include "stdlib.h"
int main()
{
int j;
int e; //定义e作为获取值
SqStack s; //定义一个栈空间,注意此处为什么要定义成s而不是*s,因为存在一个初始化问题
InitStack(&s); //初始化该栈,也就是令s->top=-1
for (j = 1; j <= 15; j++)
Push(&s, j); //1.对该栈进行赋初值,也就是push入栈
printf("栈中元素依次为:");
StackTraverse(&s); //显示出该栈的各元素
Pop(&s, &e); //2.将栈顶元素弹出并放在e中,也就是pop出栈
printf("弹出的栈顶元素为:%d\n",e);
printf("栈中元素依次为:");
StackTraverse(&s); //显示出该栈的各元素
GetTop(&s, &e); //获取栈顶元素并放在e中,注意只是获取并未弹出
printf("获取的栈顶元素为:%d\n", e);
printf("栈的长度为%d\n",StackLength(&s)); //获取栈的长度
ClearStack(&s); //将栈清空,也就是s->top=-1,使栈指针指向栈底
printf("栈的长度为%d\n", StackLength(&s)); //获取栈的长度
system("pause");
return 0;
}
/*测试结果如下*/
栈中元素依次为:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
弹出的栈顶元素为:15
栈中元素依次为:1 2 3 4 5 6 7 8 9 10 11 12 13 14
获取的栈顶元素为:14
栈的长度为14
栈的长度为0
请按任意键继续. . .
/* 定义一个Node,作为链栈的基本节点 */
typedef struct StackNode
{
int data; //定义该节点的数据域
struct StackNode *next; //定义该节点的指针域,指向下一个节点
}*LinkStackPtr; //类型重定义
/* 定义一个链栈结构,实际上就包含了两部分:头节点top,栈长度count */
typedef struct
{
LinkStackPtr top; //该链栈的头节点
int count; //计数值
}LinkStack; //类型重定义
/* 构造一个空栈S,当前该栈只有一个节点 */
int InitStack(LinkStack *S)
{
S->top = (LinkStackPtr)malloc(sizeof(StackNode)); //首先分配一个节点空间,做为该栈的首节点
if (!S->top) //分配失败,返回错误
return 0;
S->top = NULL; //收个栈节点指向NULL,后续再添加节点
S->count = 0; //栈元素个数为0
return 1;
}
/* 把S置为空栈 */
int ClearStack(LinkStack *S)
{
LinkStackPtr p, q;
p = S->top; //把栈顶节点地址赋给p
while (p) //栈顶地址不为NULL
{
q = p; //把当前地址备份给q
p = p->next; //p指向该栈的下一个节点(从栈顶往栈低方向移动)
free(q); //释放当前栈节点的空间
}
S->count = 0; //释放完成后栈元素个数为0
return 1;
}
/* 判断该栈是否为空栈,也就是判断栈元素个数是否为0 */
int StackEmpty(LinkStack S)
{
if (S.count == 0)
return 1;
else
return 0;
}
/* 返回S的元素个数,即栈的长度,也就是返回栈元素个数 */
int StackLength(LinkStack S)
{
return S.count;
}
/* 获取栈顶节点的data */
int GetTop(LinkStack S, int *e)
{
if (S.top == NULL)
return 0; //如果为空栈,返回错误
else
*e = S.top->data; //获取栈顶节点中的数据,并保存在e中
return 1;
}
/* push */
int Push(LinkStack *S, int e)
{
LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode)); //首选要给加入的节点分配空间
s->data = e; //给该节点的数据部分赋值
s->next = S->top; //把当前的栈顶元素赋值给新结点的直接后继
S->top = s; //将新的结点s作为栈顶
S->count++; //栈元素计数加1
return 1;
}
/* pop */
int Pop(LinkStack *S,int *e)
{
LinkStackPtr p = S->top; //将栈顶节点备份给p
if (StackEmpty(*S)) //如果栈为空的话返回错误
return 0;
*e = S->top->data; //获取该栈顶节点的data并放在e中
S->top = S->top->next; //将S->top指向下一个节点
free(p); //释放刚刚的顶节点S->top
S->count--; //栈元素计数减1
return 1;
}
/* 显示栈各节点数据 */
int StackTraverse(LinkStack S)
{
LinkStackPtr p;
p = S.top;
while (p)
{
printf("%d ",p->data); //打印当前节点的data
p = p->next; //指向下一个节点
}
printf("\n");
return 1;
}
/*在主函数中进行测试*/
#include "stdio.h"
#include "ctype.h"
#include "stdlib.h"
int main()
{
int j;
LinkStack s; //定义一个栈s(实际只有一个顶节点)
int e; //作为读取值存储
if (InitStack(&s) == 1)
for (j = 1; j <= 66; j++)
Push(&s, j); //对栈进行初始化(由于采用链表结构,初始化无上限)
printf("栈中元素依次为:");
StackTraverse(s); //可以看出栈底的节点指向NULL,也就是从顶到底的访问结构
Pop(&s, &e); //执行pop操作
printf("弹出的栈顶元素 e=%d\n", e);
GetTop(s, &e); //获取顶节点数据
printf("栈顶元素 e=%d 栈的长度为%d\n", e, StackLength(s));
ClearStack(&s); //清空栈(挨个节点释放内存并使count=0)
printf("清空栈后,栈空否:%d(1:空 0:否)\n", StackEmpty(s));
system("pause");
return 0;
}
/*测试结果如下*/
栈中元素依次为:66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 4
5 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19
18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
弹出的栈顶元素 e=66
栈顶元素 e=65 栈的长度为65
清空栈后,栈空否:1(1:空 0:否)
请按任意键继续. . .
定义:队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。
这里介绍两种队列的结构:顺序队列,链队列。
顺序栈采用的是数组的形式存储队列元素,而链队列采用的是链表形式对队列元素进行存储,对于两者的特性可参考数组和链表的属性比较。
#define MAXSIZE 20 /* 存储空间初始分配量 */
/* 循环队列的顺序存储结构 */
typedef struct
{
int data[MAXSIZE];
int front; //头指针
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue; //类型重定义
/* 初始化一个空队列Q */
int InitQueue(SqQueue *Q)
{
Q->front = 0; //头指针为0
Q->rear = 0; //尾指针为0
return 1;
}
/* 将Q清为空队列 */
int ClearQueue(SqQueue *Q)
{
Q->front = Q->rear = 0; //头指针尾指针都为0
return 1;
}
/* 判断队列是否为空 */
int QueueEmpty(SqQueue *Q)
{
if (Q->front == Q->rear) //判断队列头指针和尾指针是否相等
return 1;
else
return 0;
}
/* 返回队列长度 */
int QueueLength(SqQueue Q)
{
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;//队列长度就是头指针减去尾指针,其中MAXSIZE是为了使用循环结构
}
/* 获取队首元素 */
int GetHead(SqQueue *Q, int *e)
{
if (Q->front == Q->rear) //队列空
return 0; //如果队列空,则返回错误
*e = Q->data[Q->front]; //获得队列顶数据
return 1;
}
/* 在队尾添加元素 */
int EnQueue(SqQueue *Q, int e)
{
if ((Q->rear + 1) % MAXSIZE == Q->front) /* 队列满的判断 */
return 0;
Q->data[Q->rear] = e; /* 将元素e赋值给队尾 */
Q->rear = (Q->rear + 1) % MAXSIZE; /* rear指针向后移一位置, */
/* 若到最后则转到数组头部 */
return 1;
}
/* 删除队首元素 */
int DeQueue(SqQueue *Q, int *e)
{
if (Q->front == Q->rear) /* 队列空的判断 */
return 0;
*e = Q->data[Q->front]; /* 将队头元素赋值给e */
Q->front = (Q->front + 1) % MAXSIZE; /* front指针向后移一位置, */
/* 若到最后则转到数组头部 */
return 1;
}
/* 从队头到队尾依次对队列Q中每个元素输出 */
int QueueTraverse(SqQueue *Q)
{
int i=0;
while ((Q->front + i) != Q->rear) //
{
printf("%d ", Q->data[i + Q->front]);
i = (i + 1) % MAXSIZE;
}
printf("\n");
return 1;
}
/*主函数*/
#include "stdio.h"
#include "ctype.h"
#include "stdlib.h"
int main()
{
int d=0,i=0;
SqQueue Q; //定义一个队列Q
InitQueue(&Q); //对Q进行初始化
printf("初始化队列后,队列空否?%u(1:空 0:否)\n", QueueEmpty(&Q)); //判断初始化后的队列是否为空
do
{
i++;
EnQueue(&Q, i); //队尾插入元素
} while (i < MAXSIZE - 1);
printf("\n队列长度为: %d\n", QueueLength(Q)); //获取队列长度
printf("现在队列空否?%u(1:空 0:否)\n", QueueEmpty(&Q)); //判断队列是否为空
QueueTraverse(&Q);
DeQueue(&Q, &d); //删除队首元素
printf("\n被删除的队首元素为:%d\n", d);
printf("队列长度为: %d\n", QueueLength(Q)); //获取队列长度
QueueTraverse(&Q); //打印队列元素
GetHead(&Q, &d); //获取队首元素
printf("获得的队首元素为:%d\n", d);
ClearQueue(&Q); //清空队列
printf("\n队列长度为: %d\n\n", QueueLength(Q)); //获取队列长度
system("pause");
return 0;
}
/*测试结果如下*/
初始化队列后,队列空否?1(1:空 0:否)
队列长度为: 19
现在队列空否?0(1:空 0:否)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
被删除的队首元素为:1
队列长度为: 18
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
获得的队首元素为:2
队列长度为: 0
请按任意键继续. . .
//注意:该链对列的结构是定义了一个头节点放在队头,只为了作为链表的起始,不作为数据存储单元,只要链对列创建,该节点就存在
/* 定义一个结点结构 */
typedef struct QNode
{
int data; //数据域
struct QNode *next; //指针域
}*QueuePtr; //类型重定义
/* 队列的链表结构 */
typedef struct
{
QueuePtr front,rear; //队头、队尾指针(节点结构体类型)
}LinkQueue;
/* 构造一个空队列Q */
int InitQueue(LinkQueue *Q)
{
Q->front = Q->rear = (QueuePtr)malloc(sizeof(QNode)); //分配一个节点空间,使队头,队尾指针均指向它
if (!Q->front) //若空间分配失败,则返回错误
return 0;
Q->front->next = NULL; //使队头节点指向NULL
return 1;
}
/* 销毁队列Q */
int DestroyQueue(LinkQueue *Q)
{
QueuePtr p; //定义节点p多为缓存中间变量
while (Q->front) //如果队头节点存在
{
p = Q->front->next; //将Q->front->next备份到p当中
free(Q->front); //释放队头节点
Q->front = p; //将队头指向下一个节点
}
return 1;
}
/* 将Q清为空队列,清空与销毁的区别就是清空保留一个头节点指向NULL,而销毁则释放一切节点内存 */
int ClearQueue(LinkQueue *Q)
{
QueuePtr p, q;
Q->rear = Q->front;
p = Q->front->next;
Q->front->next = NULL;
while (p)
{
q = p;
p = p->next;
free(q);
}
return 1;
}
/* 判断队列是否为空队列 */
int QueueEmpty(LinkQueue Q)
{
if (Q.front == Q.rear) //也就是判断队列的队头和队尾节点的地址是否一致
return 1;
else
return 0;
}
/* 求队列的长度 */
int QueueLength(LinkQueue Q)
{
int i = 0;
QueuePtr p;
p = Q.front;
while (Q.rear != p) //从队头一直找到队尾,判断有多少个节点
{
i++;
p = p->next; //指向下一个节点
}
return i;
}
/* 获取队头节点数据 */
int GetHead(LinkQueue Q, int *e)
{
QueuePtr p;
if (Q.front == Q.rear) //判断是否为空队列
return 0;
p = Q.front->next; //注意:队头节点不是front节点,而是其下一个节点,因为front节点不做数据存储用
*e = p->data;
return 1;
}
/* 在队尾插入节点 */
int EnQueue(LinkQueue *Q, int e)
{
QueuePtr s = (QueuePtr)malloc(sizeof(QNode)); //给要插入的节点分配内存空间
if (!s) /* 存储分配失败 */
return 0;
s->data = e; //给该节点赋值
s->next = NULL; //该节点指向NULL
Q->rear->next = s; //rear指向该节点
Q->rear = s; //把新加入节点s作为rear节点
return 1;
}
/* 在队头删除一个节点 */
int DeQueue(LinkQueue *Q, int *e)
{
QueuePtr p;
if (Q->front == Q->rear) //如果对列为空,返回错误
return 0;
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 1;
}
/* 从队头到队尾依次对队列Q中每个元素输出 */
int QueueTraverse(LinkQueue Q)
{
QueuePtr p;
p = Q.front->next; //注意开始存放数据的节点是front的下一个节点
while (p)
{
printf("%d ", p->data); //获取该节点数据
p = p->next; //指向下一个节点
}
printf("\n");
return 1;
}
/*主函数*/
#include "stdio.h"
#include "ctype.h"
#include "stdlib.h"
int main()
{
int i;
int d;
LinkQueue q; //定义一个链队列
InitQueue(&q); //初始化该链队列
printf("是否空队列?%d(1:空 0:否) ", QueueEmpty(q)); //判断是否为空链队列
printf("队列的长度为%d\n", QueueLength(q)); //获取链队列的长度
EnQueue(&q, -5); //队尾插入一个节点
EnQueue(&q, 5); //队尾插入一个节点
EnQueue(&q, 10); //队尾插入一个节点
printf("\n插入3个元素(-5,5,10)后,队列的长度为%d\n", QueueLength(q));
printf("是否空队列?%d(1:空 0:否) ", QueueEmpty(q));
printf("队列的元素依次为:");
QueueTraverse(q);
GetHead(q, &d); //获取队头节点数据
printf("\n队头元素是:%d\n", d);
DeQueue(&q, &d); //删除队头节点数据
printf("删除了队头元素%d\n", d);
GetHead(q, &d); //获取队头节点数据
printf("新的队头元素是:%d\n", d);
ClearQueue(&q); //清空链队列,还剩一个front节点
printf("\n清空队列后,q.front=%u q.rear=%u q.front->next=%u\n", q.front, q.rear, q.front->next);
DestroyQueue(&q); //销毁链队列,一个节点不剩
printf("\n销毁队列后,q.front=%u q.rear=%u\n", q.front, q.rear);
system("pause");
return 0;
}
/*测试结果如下*/
是否空队列?1(1:空 0:否) 队列的长度为0
插入3个元素(-5,5,10)后,队列的长度为3
是否空队列?0(1:空 0:否) 队列的元素依次为:-5 5 10
队头元素是:-5
删除了队头元素-5
新的队头元素是:5
清空队列后,q.front=7189872 q.rear=7189872 q.front->next=0
销毁队列后,q.front=0 q.rear=7189872
请按任意键继续. . .
对于循环队列与链队列的比较,可以从两方面来考虑:
<1>.从时间上,他们的基本操作都是常数时间,即时间复杂度均为O(1),不过循环队列需要实现申请好空间,使用期间不释放,而对链队列,每次申请和释放节点也会存在一些时间开销,如果入队出队频繁,则两者还是有细微差异。
<2>.对于空间上来说,循环队列必须要有一个固定的长度,所以就有了存储元素个数和空间浪费的问题。而链队列不存在这个问题,尽管它有指针域,会产生一些空间上的开销,但也可以接受。所以在空间上,链队列更加灵活。
总的来说,在可以确定对垒长度情况下,建议用循环队列,如果无法预估队列的长度,则用链队列。