栈和队列
栈和队列是数据结构中比较常用的两种类型,栈(FILO)先进后出,即先存放的数据最后才能出栈、队列(FIFO)先进先出。
栈顶top:允许插入和删除的一端;
栈底bottom:另外一端;
空栈:不含任何数据元素的栈,top = -1;
栈中有一个元素时,top = 0;
空栈:不含任何数据元素的栈,top = -1;
栈中有一个元素时,top = 0;
一、顺序存储(一般采用循环队列)
顺序栈:即栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。
顺序存储中,我们通常用数组下标表示结点的位置,因此当top为0时,并不表示该栈为空,而表示含有一个结点;
因此,在初始化的时候,将top = -1,;
一般顺序栈的定义如下:ElemType 为数据类型;
typedef int ElemType;
struct stack
{
ElemType data[MAXISZE];
int top;
};
typedef struct stack Stack;
例如有一个容量为5个元素的栈:
栈里最重要的两个操作时进栈和出栈:
1.进栈:
int Push(Stack *s, ElemType e)
{
if(s->top == MAXSIZE - 1)
{
return FAILURE;
}
s->top++;
s->data[s->top] = e;
return SUCCESS;
}
说明:对于顺序栈的进栈,就当做数组一样操作就好了,首先进行判断,判断此栈是否已满;不满的情况下,先将top进行+1操作,再赋值操作, 这样就完成了入栈的操作;
2.出栈:
定义了一个e,来接删除的数据,可以根据实际情况选择是否需要e;
出栈的过程与进栈相反;
int Pop(Stack *s, ElemType *e)
{
if(s->top == -1)
{
return FAILURE;
}
*e = s->data[s->top];
s->top--;
return SUCCESS;
}
二、链式存储(链栈)
链式存储的顺序表并没有容量可言,一般来说只要内存没有用完,那么链栈就可以一直延续下去;顺序存储的内存在声明顺序表时就已经分配好了,而链栈的结点是在使用时,利用malloc分配就可以,这一点与链表相类似。
typedef int ElemType;
struct node //结点
{
ElemType data;
struct node *next;
};
typedef struct node Node;
typedef Node *LinkNode;
struct stack //栈
{
LinkNode top;
int count; //count成员记录链栈的长度
};
typedef struct stack LinkStack;
对于栈这种先进后出的数据结构,只能在栈顶进行操作。因此在进栈之前,我们要将新增的结点与原来的top建立联系,就是上图中的(1),建立联系之后,将top由原来指向aj变为指向e,这样就将top又指向了最上面的结点,如上图(2)。
int Push(LinkStack *s, ElemType e)
{
LinkNode p = (LinkNode)malloc(sizeof(Node)
if(NULL == p)
{
return FAILURE;
}
p->next = s->top;
p->data = e;
s->top = p;
s->count++;
}
2.出栈
出栈分为三步:第一步,定义变量p来存储要删除的栈顶指针;第二步,将栈顶指针指向下一结点;第三步,释放p;
int Pop(LinkStack *s, ElemType *e)
{
LinkNode *p;
if(StackEmpty(s) == TRUE) //判断是否为空栈,空栈则返回TRUE,那么无法删除栈顶结点
return FAILURE;
*e = s->top->data; //通过e来保存栈顶的数据
p = s->top; //p指向栈顶
s->top = s->top->next; //将top指向下一结点
free(p); //释放p
s->count--;
return SUCCESS;
}
队列
队列(queue)是只允许在一端进行插入操作,在另一端进行删除操作的线性表。同样,队列也分为顺序队列和链式队列。
1.顺序队列
一般规定front指针指向队头元素,rear指针指向队尾元素的下一个位置,这样当front等于rear的时候,就表示该队列为空队列。
例如下图,我规定这个队列的容量为6,开始时,front和rear都指向0位置,当插入一个元素时,front依旧指向队头元素,但rear会指向下一位置,依次反复,当插入元素e时,rear已经指向5的位置,那么再插入一个元素rear会去哪?
这时候会出现假溢出的现象,所以对于一个容量为MAXSIZE的队列,最多只能存放MAXSIZE-1个元素,为了解决假溢出的问题,我们通常采用循环队列。
循环队列,说白了就是头尾相接的队列,那么这种情况下,rear也会出现在front的前面。接着上面这个例子来说,当我将元素a出队后,再插入一个结点f,这时,front和 rear指向哪了呢?
此时,front指向的不再是我们所认为的“队头”,front在删除a元素后,指向了元素b,插入f后,rear指向了0的位置,也就是说,front会随着元素的删除而移动,而rear依旧指向下一地址,只是这时这个队列是循环的。
那么我们如何求取该队列的长度和判断是否满的?
下面两个公式,需要我们牢记:
1.判断队列是否满:
(rear + 1) % MAXSIZE = = front;
2.求取队列的长度:
(rear - front + MAXSIZE) % MAXSIZE;
注意:这里的rear和front都是数组的下标;
下面讲解一下,循环队列的结构体,初始化:
结构体:
typedef int ElemType;
struct queue
{
ElemType data[MAXSIZE];
int front;
int rear;
};
typedef struct queue Queue;
初始化:
int QueueInit(Queue *q)
{
q->front = 0;
q->rear = 0;
return SUCCESS;
}
入队和出队:
入队:
int EnQueue(Queue *q, ElemType e)
{
if(( q->rear + 1 ) % MAXSIZE == q->front)
return FAILURE;
q->data[q->rear] = e;
q->rear = (q->rear + 1 ) % MAXSIZE;
return SUCCESS;
}
出队:
int DeQueue(Queue *q, ElemType *e)
{
if(q->rear == q->front)
return FAILURE;
*e = q->data[q->front];
q->front = (q->front + 1) % MAXSIZE;
return SUCCESS;
}
2.链式队列
队列的链式存储结构,其实就是线性表的单链表,只是它只能够从尾进队、从头出队。
一般,我们将队头指针指向链队列的头结点,而将队尾指针指向尾结点。
结构体:
typedef int ElemType;
struct node
{
ElemType data;
struct node *next;
};
typedef struct node *Node;
struct queue
{
Node front;
Node rear;
};
typedef struct queue LinkQueue;
进队:在栈尾插入一个结点,将rear指向这个结点
int EnQueue(Queue *q, ElemType e)
{
Node s = (Node)malloc(sizeof(node));
if(NULL == s)
{
return FAILURE;
}
s->data = e;
s->next = NULL ;
q->rear->next = s;
q->rear = s;
return SUCCESS;
}
出队:
将头结点的后继结点出队,将头结点的后继改为它后面的结点。如果链队列只剩一个结点,那么还需要将rear指向front;
int DeQueue(Queue *q, ElemType *e)
{
Node p;
if(q->front == q->rear)
return FAILURE;
p = q->front->next;
*e = p->data;
q->front->next = p->next;
if(q->rear == p)
{
q->rear = q->front;
}
free(p);
return SUCCESS;
}