1 栈
1.1 栈的定义
栈是一种重要的线性结构,是线性表的一种具体形式。
栈(Stack)是一个后进先出(Last in first out,LIFO)的线性表,它要求只在表尾进行删除和插入操作。
表尾称为栈的栈顶(top),表头称为栈底(bottom)。
栈的插入操作(Push),叫做进栈,也称为压栈,入栈。栈的删除操作(Pop),叫做出栈,也称为弹栈。
因为栈的本质是一个线性表,线性表有两种存储形式,那么栈也有分为栈的顺序存储结构和栈的链式存储结构。
1.2 栈的顺序存储结构
最开始栈中不含有任何数据,叫做空栈,此时栈顶就是栈底。然后数据从栈顶进入,栈顶栈底分离,整个栈的当前容量变大。数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。
定义
typedef struct
{
ElemType *base;
ElemType *top;
int stackSize;
}sqStack;
//定义了一个顺序存储的栈,它包含了三个元素:base,top,stackSize。
//其中base是指向栈底的指针变量,top是指向栈顶的指针变量,stackSize指示栈的当前可使用的最大容量。
- 创建
#define STACK_INIT_SIZE 100
initStack(sqStack *s)
{
s->base = (ElemType *)malloc( STACK_INIT_SIZE * sizeof(ElemType) );
if( !s->base )
exit(0);
s->top = s->base; // 最开始,栈顶就是栈底
s->stackSize = STACK_INIT_SIZE;
}
- 入栈:要在栈顶进行,每次向栈中压入一个数据,top指针就要+1,到栈满为止。
#define SATCKINCREMENT 10
Push(sqStack *s, ElemType e)
{
// 如果栈满,追加空间
if( s->top – s->base >= s->stackSize )
{
s->base = (ElemType *)realloc(s->base, (s->stackSize + STACKINCREMENT) * sizeof(ElemType));
if( !s->base )
exit(0);
s->top = s->base + s->stackSize; // 设置栈顶
s->stackSize = s->stackSize + STACKINCREMENT; // 设置栈的最大容量
}
*(s->top) = e;
s->top++;
}
- 出栈:就是在栈顶取出数据,栈顶指针随之下移的操作。每当从栈内弹出一个数据,栈的当前容量就-1。
Pop(sqStack *s, ElemType *e)
{
if( s->top == s->base ) // 栈已空空是也
return;
*e = *--(s->top);
}
- 清空栈:将栈中的元素全部作废,但栈本身物理空间并不发生改变(不是销毁)。单纯地清空没有覆盖。
ClearStack(sqStack *s){
s->top = s->base;
}
- 销毁栈:销毁一个栈是要释放掉该栈所占据的物理内存空间。
DestroyStack(sqStack *s){
int i, len;
len = s->stackSize;
for( i=0; i < len; i++ ){
free( s->base );
s->base++;
}
s->base = s->top = NULL;
s->stackSize = 0;
}
计算栈中元素个数
计算栈的当前容量也就是计算栈中元素的个数,因此只要返回s.top-s.base即可。
PS:栈的最大容量是指该栈占据内存空间的大小,其值是s.stackSize,它与栈的当前容量不是一个概念。
int StackLen(sqStack s)
{
return(s.top – s.base); // 初学者需要重点讲解
}
1.3 栈的链式存储结构
定义
栈的链式存储结构,简称栈链。(通常我们用的都是栈的顺序存储结构存储)
略
2 队列
1 定义
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out, FIFO)的线性表。
实现一个队列同样需要顺序表或链表作为基础。
跟栈相反的是,栈一般我们用顺序表来实现,而队列我们常用链表来实现,简称为链队列。
代码
typedef struct QNode {
ElemType data;
struct QNode *next;
} QNode, *QueuePrt;
typedef struct {
QueuePrt front, rear; // 队头、尾指针
} LinkQueue;
2 队列的链式存储结构
我们将队头指针指向链队列的头结点,而队尾指针指向终端结点。(注:头结点不是必要的,但为了方便操作,我们加上了。)
空队列:front和rear都指向头结点。
创建一个队列
创建一个队列要完成两个任务:一是在内存中创建一个头结点,二是将队列的头指针和尾指针都指向这个生成的头结点,因为此时是空队列。
initQueue(LinkQueue *q)
{
q->front=q->rear=(QueuePtr)malloc(sizeof(QNode));
if( !q->front )
exit(0);
q->front->next = NULL;
}
入队列
InsertQueue(LinkQueue *q, ElemType e)
{
QueuePtr p;
p = (QueuePtr)malloc(sizeof(QNode));
if( p == NULL )
exit(0);
p->data = e;
p->next = NULL;
q->rear->next = p;
q->rear = p;
}
出队列
出队列操作是将队列中的第一个元素移出,队头指针不发生改变,改变头结点的next指针即可。
如果原队列只有一个元素,那么我们就应该处理一下队尾指针。
DeleteQueue(LinkQueue *q, ELemType *e)
{
QueuePtr p;
if( q->front == q->rear )
return;
p = q->front->next;
*e = p->data;
q->front->next = p->next;
if( q->rear == p )
q->rear = q->front;
free(p);
}
销毁一个队列
由于链队列建立在内存的动态区,因此当一个队列不再有用时应当把它及时销毁掉,以免过多地占用内存空间。
DestroyQueue(LinkQueue *q)
{
while( q->front ) {
q->rear = q->front->next;
free( q->front );
q->front = q->rear;
}
}
- 例:编写一个链队列,任意输入一串字符,以#作为结束标志,然后将队列中的元素显示到屏幕上。
#include <stdio.h>
#include <stdlib.h>
typedef char ElemType;
typedef struct QNode
{
ElemType data;
struct QNode *next;
} QNode, *QueuePtr;
typedef struct
{
QueuePtr front, rear;
} LinkQueue;
initQueue(LinkQueue *q)
{
q->front = q->rear = (QueuePtr)malloc(sizeof(QNode));
if( !q->front )
exit(0);
q->front->next = NULL;
}
InsertQueue(LinkQueue *q, ElemType e)
{
QueuePtr p;
p = (QueuePtr)malloc(sizeof(QNode));
if( !q->front )
exit(0);
p->data = e;
p->next = NULL;
q->rear->next = p;
q->rear = p;
}
DeleteQueue(LinkQueue *q, ElemType *e)
{
QueuePtr p;
if( q->front == q->rear )
return;
p = q->front->next;
*e = p->data;
q->front->next = p->next;
if( q->rear == p )
{
q->rear = q->front;
}
free(p);
}
int main()
{
ElemType e;
LinkQueue q;
initQueue(&q);
printf("请输入一个字符串,以井号键结束输入:");
scanf("%c", &e);
while( e != '#' )
{
InsertQueue( &q, e );
scanf("%c", &e);
}
printf("打印队列中的元素:");
while( q.front != q.rear )
{
DeleteQueue( &q, &e );
printf("%c", e);
}
return 0;
}
2 队列的顺序存储结构与循环队列
我们更愿意用链式存储结构来存储队列
由于队列的顺序存储会出现很多问题,采用循环队列更好
循环队列它的容量固定,并且队头和队尾指针都可以随着元素入出队列而发生改变,逻辑上就好像是一个环形存储空间。(在实际的内存当中,不可能有真正的环形存储区,我们只是用顺序表模拟出来的逻辑上的循环。)
也就是让front或rear指针不断加1,即时超出了地址范围,也会自动从头开始。我们可以采取取模运算处理:
(rear+1) % QueueSize
(front+1) % QueueSize定义一个循环队列
#define MAXSIZE 100
typedef struct
{
ElemType *base; // 用于存放内存分配基地址
// 这里你也可以用数组存放
int front;
int rear;
}
- 初始化一个循环队列
initQueue(cycleQueue *q)
{
q->base = (ElemType *) malloc (MAXSIZE * sizeof(ElemType));
if( !q->base )
exit(0);
q->front = q->rear = 0;
}
- 入队列操作
InsertQueue(cycleQueue *q, ElemType e)
{
if( (q->rear+1)%MAXSIZE == q->front )
return; // 队列已满
q->base[q->rear] = e;
q->rear = (q->rear+1) % MAXSIZE;
}
- 出队列操作
DeleteQueue(cycleQueue *q, ElemType *e)
{
if( q->front == q->rear )
return ; // 队列为空
*e = q->base[q->front];
q->front = (q->front+1) % MAXSIZE;
}