栈的定义

栈(stack)是一个后进先出的线性表(一种特殊的线性表),它要求只在表尾进行删除和插入操作。

所谓的栈其实也就是一个特殊的线性表(顺序表,链表),但是它在操作上有一些特殊的要求和限制。

-栈的元素必须”后进先出“。

-栈 的操作只能在这个线性表的表尾进行。

-对于栈来说没这个表尾成为栈的栈顶(top),相应的表头称为栈底(bottom)。

栈的插入操作(push),叫做进栈,也成为压栈,入栈。类似子弹放入弹夹的动作。

栈的删除操作(pop),叫做出栈,也成为弹栈。如同弹夹中的子弹出夹。

栈和队列_头结点

栈的顺序存储结构

#define STACK_INIT_SIZE  100
#define STACKINCREMENT 10

typedef int ElemType ;

typedef struct {
ElemType * base;
ElemType * top;
int stackSize;

}sqStack;

这里定义了一个顺序存储的栈,它包含了三个元素;base,top,stackSize。其中base是指向栈底的指针变量,top是指向栈定的指针变量,stackSize指示栈的当前可使用的最大容量。

初始化一个栈

void initStack(sqStack * s){
s->base = malloc(sizeof(ElemType));
if (!s->base) {
return;
}

s->top = s->base;//开始栈顶就是栈底
s->stackSize = STACK_INIT_SIZE;
}

入栈操作

入栈操作又叫做压栈操作,就是向栈中存放数据。

入栈操作要在栈定进行,每次向栈中压入一个数据,top指针就要+1,直到栈满为止。

void pushStack(sqStack * s,ElemType value){
if (s->top - s->base >= s->stackSize) {
s->base = (int *)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) = value;
s->top++;
}

出栈操作

出栈操作就是在栈顶取出数据,栈顶指针随之下移的操作。

每当从栈内弹出一个数据,栈的当前容量就-1。

void popStack(sqStack * s,ElemType * value)
{
if (s->top == s->base) {
return;
}
value = --(s->top);
}


逆波兰计算器

正常的表达式 --->逆波兰表达式

-a+b ---> a b +

-a+(b-c) --->a b c - +


队列

队列(queue)是指允许在一段进行插入操作,而在另一端进行删除操作的线性表。

与栈相反,队列是一种先进先出的线性表。与栈相同的是,队列也是一种重要的线性结构,实现一个队列同样需要顺序表或链表作为基础。

队列既可以用链表实现,也可以用顺序表实现。跟栈相反的是,栈一般我们用顺序表来实现,队列我们常用链表来实现,简称为链队列。

栈和队列_线性表_02

//队列每个节点结构体
typedef struct QNode{
ElemType data;
struct QNode *next;
} QNode, *QueuePrt;

//队列结构体
typedef struct {
QueuePrt front,rear;//队头 尾指针
}LinkQueue;

我们将队头指针指向链表队列的头结点,而队尾指针指向终端结点。(注:头结点不是必要的,但为了方便操作,我们加上了)。

栈和队列_链表_03

空队列时,front和rear都指向头结点。

栈和队列_头结点_04

创建一个队列要完成两个任务,一是在内存中创建一个头结点,二是将队列的头指针和尾指针都指向这个生成的头结点,因此此时是空队列。

初始化队列

void initQueue(LinkQueue * q)
{
QueuePrt node = (QueuePrt)malloc(sizeof( QNode));
q->front = node;
q->rear = node;
if (!q->front) {
exit(0);
}
q->front->next= NULL;
}

入队列操作

void insertQueue(LinkQueue * q, ElemType e) {
QueuePrt p = (QueuePrt)malloc(sizeof(QNode));
if (p==NULL) {
exit(0);
}
p->data = e;
p->next = NULL;
q->rear->next = p;
q->rear = p;
}

出队列操作

出队列操作是将队列中的第一个元素移除,队头指针不发生改变,改变头结点的next指针即可。

栈和队列_链表_05

void deleteQueue (LinkQueue * q,ElemType *e) {
QueuePrt p;
if (q->front == q->rear) {
return;
}
p = q->front->next;
*e = p->data;
q->front->next = p->next;
if (q->rear == q->front) {
q->rear = q->front;
}
free(p);
}

销毁一个队列

由于链队列建立在内存的动态区,因此当一个队列不再有用时应当把它及时销毁掉,以免过多地占用内存空间。

void DestroyQueue(LinkQueue *q)
{
while(q->front){
q->rear = q->front-next;
free(q->front);
q->front = q->rear;
}
}

队列的顺序存储结构

我们来理解一下上面所说的更愿意用链式存储结构来存储?

我们先按照应有的思路来考虑下如何构造队列的顺序存储结构,然后发掘都遇到了什么麻烦?

我们假设一个队列有N个元素,则顺序存储的队列需建立一个大于N的存储单元,并把队列的所有元素存储在数组的前N个单元,数组下表为0的一端则是队头。

栈和队列_线性表_06

入队列操作其实就是在队尾追加一个元素,不需要任何移动,时间复杂度为O(1)。出队列则不同,因为我们已经架设下标为0的位置是队列的队头,因此每次出队列操作所有元素都要向前移动。

栈和队列_线性表_07

在现实中也是如此,一群人在排队买火车票,前边的人买好了离开,后面的人就要全部向前一步补上空位。

可是我们研究数据结构和算法的一个根本目的就是要想方设法提高我们的程序的效率,按刚才的方式,出队列的时间复杂度是Q(N),效率大打折扣。