栈:栈是只允许在一端进行插入或删除操作的线性表。栈是一种线性表,但限定了这种线性表只能在某一端进行插入和删除操作。
栈的特性:后进先出或先进后出。
栈的应用:进制转换、表达式求值、括号匹配等。
采用顺序存储结构的栈称为顺序栈,利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置。
栈的顺序存储结构类型描述如下:
#define MaxSize 50 //定义栈中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //存放栈中元素
int top; //栈顶指针
}SqStack;
栈顶指针:S.top,初始时设置:S.top = -1,栈顶元素:S.data[S.top]
进栈操作:栈不满时,栈顶指针先加1,再送值到栈顶元素。
出栈操作:栈非空时,先去栈顶元素值,再将栈顶指针减1。
栈空条件:S.top == -1,栈满条件:S.top == MaxSize-1,栈长:S.top + 1
注意:顺序栈的入栈操作受数组上界的约束,当对栈的最大使用空间估计不足时,有可能会出现栈上溢出。栈和队列的判空、判断条件,根据实际给的条件不同而变化。
栈顶指针和栈中元素之间的关系如下图所示:
顺序栈常用的基本运算的代码实现如下所示:
(1)初始化
void InitStack(SqStack &S){
S.top = -1; //初始化栈顶指针
}
(2)判栈空
bool StackEmpty(SqStack S){
if(S.top == -1) //栈空
return true;
else //栈非空
return false;
}
(3)进栈
bool Push(SqStack &S,ElemType x){
if(S.top == MaxSize-1) //栈满,报错
return false;
S.data[++S.top] = x; //指针先加1,再入栈
return true;
}
(4)出栈
bool Pop(SqStack &S,ElemType &x){
if(S.top == -1) //栈空,报错
return false;
x = S.data[S.top--]; //先出栈,指针再减1
return true;
}
(5)读栈顶元素
bool GetTop(SqStack S,ElemType x){
if(S.top == -1) //栈空,报错
return false;
x = S.data[S.top]; // x 记录栈顶元素
return true;
}
仅为读取栈顶元素,并没有出栈操作,原栈顶元素依然保留在栈中。
注意:栈顶指针初始化为 S.top = -1,top 指向的是栈顶元素,进栈操作为 S.data[++S.top] = x,出栈操作为 x = S.data[S.top-- ]。若栈顶指针初始化为 S.top = 0,即 top 指向栈顶元素的下一个位置,则入栈操作变为 S.data[S.top++] = x,出栈操作变为 x = S.data[- -S.top ]。相应的栈空、栈满条件也会发生变化。
利用栈底位置相对不变的特性,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸。
两个栈的栈顶指针都指向栈顶元素,top0 = -1 时 0 号栈为空,top1 = MaxSize 时 1 号栈为空;仅当两个栈顶指针相邻(top1 - top0 = 1)时,判断栈满。当 0 号栈进栈时 top0 先加 1 再赋值,1 号栈进栈时 top1 先减 1 再赋值;出栈时则刚好相反。
共享栈特点:为了更有效地利用存储空间,两个栈互相调节,只有再整个存储空间被占满时才会发生上溢。共享栈存取数据地时间复杂度均为 O(1)。
1、顺序栈的基本操作:顺序栈的初始化及其栈的入栈、出栈、查看栈中所有元素的示例代码如下所示:
#include
#include
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef int Status;
typedef int SElemType;
typedef struct{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
Status InitStack(SqStack &S)
{
S.base = (SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
if(!S.base)
exit(OVERFLOW);
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
Status DestroyStack(SqStack &S)
{
if(!S.base)
{
printf("不存在该栈\n");
return OK;
}
free(S.base);
return OK;
}
Status GetTop(SqStack S,SElemType &e)
{
if(S.top == S.base) return ERROR;
e = *(S.top - 1);
return OK;
}
Status Push(SqStack &S,SElemType e)
{
if(S.top - S.base >= S.stacksize)
{
S.base = (SElemType*)realloc(S.base,(S.stacksize + STACKINCREMENT)*sizeof(SElemType));
if(!S.base)
exit(OVERFLOW);
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top++ = e;
return OK;
}
Status Pop(SqStack &S,SElemType &e)
{
if(S.top == S.base) return ERROR;
e = * --S.top;
return OK;
}
Status displayStack(SqStack S)
{
if(S.top == S.base)
{
printf("该栈为空\n");
return OK;
}
for(;S.top != S.base;)
{
printf("%d\t",*(--S.top));
}
printf("\n");
return OK;
}
int main()
{
SqStack S;
int e;
InitStack(S);
printf("数据入栈:\n");
Push(S,10);
Push(S,20);
Push(S,30);
displayStack(S);
printf("显示栈中的数据:\n");
displayStack(S);
GetTop(S,e);
printf("栈顶的元素是:\n%d\n",e);
Pop(S,e);
printf("栈顶元素出栈:\n%d\n",e);
printf("显示栈中的数据:\n");
displayStack(S);
return 0;
}
运行结果如下图所示:
2、顺序栈的应用:实现两个多项式的相加运算。
#include
#include
typedef int ElemType;
typedef struct PolynNode{
int coef;
int expn;
struct PolynNode *next;
}PolynNode,*PolynList;
void CreatePolyn(PolynList &L,int n)
{
int i;
PolynList p,q;
L=(PolynList)malloc(sizeof(PolynNode));
L->next=NULL;
q=L;
printf("成对输入%d个数据\n",n);
for(i=1;i<=n;i++)
{
p=(PolynList)malloc(sizeof(PolynNode));
scanf("%d%d",&p->coef,&p->expn);
q->next=p;
q=q->next;
}
p->next=NULL;
}
void PolynTraverse(PolynList L,void(*vi)(ElemType, ElemType))
{
PolynList p=L->next;
while(p)
{
vi(p->coef, p->expn);
if(p->next)
{
printf(" + ");
}
p=p->next;
}
printf("\n");
}
void visit(ElemType c, ElemType e)
{
if(c != 0)
{
printf("%dX^%d",c,e);
}
}
PolynList MergeList(PolynList La, PolynList Lb)
{
PolynList pa, pb, pc, Lc;
pa = La->next;
pb = Lb->next;
Lc = pc = La;
while(pa&&pb)
{
if(pa->expn < pb->expn)
{
pc->next = pa;
pc = pa;
pa = pa->next;
}
else if (pa ->expn > pb->expn )
{
pc->next = pb;
pc = pb;
pb = pb->next;
}
else
{
pa->coef = pa->coef + pb->coef;
pc->next = pa;
pc = pa;
pa = pa->next;
pb = pb->next;
}
}
pc->next = pa ? pa:pb;
return Lc;
}
void main()
{
PolynList ha,hb,hc;
printf("非递减输入多项式A, ");
CreatePolyn(ha,5);
printf("非递减输入多项式B, ");
CreatePolyn(hb,5);
printf("多项式A :");
PolynTraverse(ha, visit);
printf("\n");
printf("多项式B :");
PolynTraverse(hb, visit);
printf("\n");
hc = MergeList(ha,hb);
PolynTraverse(hc, visit);
}
采用链式存储结构的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都在单链表的表头进行。规定链栈没有头结点,LHead 指向栈顶元素,栈的链式存储结构如下图所示:
栈的链式存储结构类型描述如下:
typedef struct Linknode{
ElemType data; //数据域
struct Linknode *next; //指针域
}*LiStack; //栈定义类型
采用链式存储,便于结点的插入和删除。链栈的操作和链表类似,入栈和出栈的操作都在链表的表头进行。对于带头结点和不带头结点的连载,具体的实现会有所不同。
示例代码:链栈的初始化、入链栈、获取链栈顶元素以及输出链栈所有元素的代码如下:
#include
#include
#define OK 1;
#define ERROR 0;
#define TRUE 1;
#define FALSE 0;
typedef int Status;
typedef int ElemType;
typedef struct LinkNode
{
ElemType data;
struct LinkNode *next;
}*LiStack;
Status InitStack(LiStack &L)
{
L=(struct LinkNode *)malloc(sizeof(LinkNode));
L->next=NULL;
return OK;
}
Status InputStack(LiStack &L,ElemType e)
{
LiStack p;
p=(struct LinkNode *)malloc(sizeof(LinkNode));
p->data=e;
p->next=L->next;
L->next=p;
return OK;
}
Status PrintStack(LiStack &L)
{
LiStack p=L->next;
while(p!=NULL)
{
printf("%d\t",p->data);
p=p->next;
}
printf("\n");
return OK;
}
Status DestoryStack(LiStack &L)
{
LiStack p;
p=L->next;
while(!p)
{
p=p->next;
free(p);
}
return OK;
}
Status GetTop(LiStack &L,ElemType &e)
{
LiStack p=L->next;
if(p==NULL)
return ERROR;
e=p->data;
return OK;
}
int main()
{
LiStack L;
int e;
int e1,e2,e3;
InitStack(L);
printf("输入三个数据进入链栈");
InputStack(L,10);
InputStack(L,20);
InputStack(L,30);
printf("\n链栈中的数据为:\n");
PrintStack(L);
printf("获取栈顶数据:\n");
GetTop(L,e);
printf("栈顶数据为e=%d\n",e);
printf("\n输出链栈中全部数据:\n");
PrintStack(L);
printf("\n初始化链栈:\n");
DestoryStack(L);
printf("\n");
return OK;
}
队列:队列简称队,是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。向队列种插入元素称为入队或进队;删除元素称为出队或离队。队列的示意图如下所示:
队列的特性:先进先出。
队列的应用:速度不匹配问题、多用户资源竞争问题。
注意:栈和队列都是操作受限的线性表,不是任何对线性表的操作都适合栈和队列的操作,不可以随便读取栈或队列中间的某个元素。
队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:对头指针 front 指向对头元素,队尾指针 rear 指向队尾元素的下一个位置。
队列的顺序存储类型描述如下:
#define MaxSize 50 //定义队列中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //存放队列元素
int front,rear; //队头指针和队尾指针
} SqQueue;
初始状态(队空条件):Q.front == Q.rear == 0
进队操作:队不满时,先送值到队尾元素,再将队尾指针加1
出队操作:队不空时,先取队头元素值,再将队头指针加1
队列的操作示意图如下图所示:
队列操作示意图中 d 图所示,队列中仅有一个元素,再进行入队操作时,会出现“上溢出”,但这种溢出不是真正的溢出,在队列数组中仍然存在可以存放元素的空位置,这是一种“假溢出”。
循环队列:将顺序队列臆造成一个环状的空间,即把存储队列元素的表从逻辑上视为一个环,称为循环队列。
当队首指针 Q.front = MaxSize-1 后,再前进一个位置就会自动到 0,可利用除法取余(%)来实现。
注意:不能用动态分配的一维数组来实现循环队列,初始化时必须设定一个最大队列长度。
为了区分循环队列队空还是队满情况,有三种处理方式,其中第一种为常用的区分方式,重点掌握:
(1)牺牲一个单元来区分队空还是队满,入队时少用一个队列单元,约定以“队头指针在队尾的下一个指针作为堆满标志”。如下图 (d2) 所示。
(2)类型中增设表示元素个数的数据成员。对空的条件为 Q.size = 0,队满的条件为 Q.size == MaxSize,有Q.front == Q.rear。
(3)类型中增设 tag 数据成员,以区分是队空还是队满。tag = 0 时,若因删除导致 Q.front == Q.rear,则为队空;tag = 1 时,若因插入导致 Q.front == Q.rear,则为队满。
(1)初始化
void InitQueue(SqQueue &Q){
Q.front == Q.rear=0; //初始化队首、队尾指针
}
(2)判队空
bool isEmpty(SqQueue Q){
if(Q.front == Q.rear) //队空条件
return true;
else
return false;
}
(3)入队
bool EnQueue(SqQueue &Q,ElemType x){
if( (Q.rear + 1)%MaxSize == Q.front) //队满则报错
return false;
Q.data[Q.rear]=x;
Q.rear= (Q.rear + 1)%MaxSize; //队尾指针加1模
return true;
}
(4)出队
bool DeQueue(SqQueue &Q,ElemType &x){
if(Q.front == Q.rear) //队空则报错
return false;
x=Q.data[Q.front];
Q.front = (Q.front + 1)%MaxSize; //队头指针加1取模
return true;
}
顺序队列基本操作:顺序队列的初始化、入队列、出队列以及显示队列中的数据元素的示例代码如下所示:
#include
#include
#define QINITSIZE 100
#define QINCRECEMENT 10
#define OK 1
#define ERROR 0
#define null 0
typedef int Status;
typedef int QElemType;
typedef struct QueueType
{
QElemType *front;
QElemType *rear;
int qsize;
}queue;
Status q_init(queue *q)
{
q->front = (QElemType *)malloc(QINITSIZE*sizeof(QElemType));
if(q->front == null)
return ERROR;
q->rear = q->front;
q->qsize = QINITSIZE;
return OK;
}
Status Enqueue(queue *q,int e)
{
if(q->rear - q->front >= QINITSIZE)
{
q->front = (QElemType *)realloc(q,(q->qsize + QINCRECEMENT)*sizeof(QElemType));
q->rear = q->front;
q->qsize += QINCRECEMENT;
}
*q->rear++ = e;
return OK;
}
Status Dequeue(queue *q,int *e)
{
if(q->rear == q->front)
return ERROR;
*e = *q->front++;
q->qsize--;
return OK;
}
int main()
{
queue q;
QElemType e,*p;
if(q_init(&q))
{
printf("顺序队列创建成功!\n");
}
Enqueue(&q,1);
Enqueue(&q,2);
Enqueue(&q,3);
Enqueue(&q,4);
Enqueue(&q,5);
p = q.front;
printf("顺序队列里面的数据为:\n");
while(p < q.rear)
printf("%d ",*p++);
Dequeue(&q,&e);
printf("\n被删除的元素:\n");
printf("%d\n",e);
p=q.front;
printf("删除队头后的数据为:\n");
while(p<q.rear)
printf("%d ",*p++);
printf("\n");
return 0;
}
队列的链式表示称为链队列,实际上是一个同时带有队头指针和队尾指针的单链表。头指针指向队头结点,尾指针指向队尾结点,即单链表的最后一个结点。
队列的不带头结点的链式存储示意图如下图所示:
队列的链式存储类型代码描述如下所示:
typedef struct{ //链式队列结点
ElemType data;
struct LinkNode *next;
}LinkNode;
typedef struct{ //链式队列
LinkNode *front,*rear; //队列的队头和队尾指针
}LinkQueue;
当 Q.front == NULL 且 Q.rear == NULL 时,链式队列为空。
出队时,首先判断队是否为空,若不空,则取出队头元素,将其从链表中移除,并让 Q.front 指向下一个结点(若该结点为最后一个结点,则令 Q.front 和 Q.rear 都为 NULL)。入队时,建立一个新结点,将新结点插入到链表的尾部,并改让 Q.rear 指向这个新插入的结点(若原队列为空队,则另 Q.front 也指向该结点)。
由于不带头结点的链式队列在操作上比较麻烦,因此通常将链式队列设计成一个带头结点的单链表,实现插入和删除相统一。带头结点和不带头结点的链式队列如下图所示:
优点:用单链表表示的链式队列特别适合于数据元素变动比较大的情形,而且不存在队列满且产生溢出的问题。
(1)初始化
void InitQueue(LinkQueue &Q){
Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));//建立头结点
Q.front->next =NULL; //初始为空
(2)判队空
bool IsEmpty(LinkQueue Q){
if(Q.front == Q.rear)
return true;
else
return false;
}
(3)入队
void EnQueue(LinkQueue &Q,ElemTyepe x){
LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));
s->data=x; //创建新结点,插入到链尾
s->next=null;
Q.rear->next=s;
Q.rear=s;
}
(4)出队
bool DeQueue(LinkQueue &Q,ElemType &x){
if(Q.front==Q.rear) //空队
return false;
LinkNode *p =Q.front->next;
x=p->data;
Q.front->next=p->next;
if(Q.rear==p)
Q.rear=Q.front;//若原队列中只有一个结点,删除后变空
free(p);
return true;
}
链队列的操作:链队列的初始化、入链队列、链队列队头元素出链队列以及显示链队列所有数据元素的示例代码如下所示:
#include
#include
#define OK 1;
#define ERROR 0;
typedef int QElemType;
typedef int Status;
typedef struct QNode
{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct
{
QueuePtr rear;
QueuePtr front;
}LinkQueue;
Status InitQueue(LinkQueue &Q)
{
Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q.front)
exit(0);
Q.front->next=NULL;
return OK;
}
Status EnQueue(LinkQueue &Q,QElemType e)
{
QueuePtr p;
p=(QueuePtr)malloc(sizeof(QNode));
if(!p)
exit(0);
p->data=e;
p->next=NULL;
Q.rear->next=p;
Q.rear=p;
return OK;
}
Status displayQueue(LinkQueue &Q)
{
QueuePtr rear,front;
front=Q.front->next;
rear=Q.rear;
if(front==rear)
{
printf("链队列为空!");
return OK;
}
while(front!=NULL)
{
printf("%d\t",front->data);
front=front->next;
}
printf("\n");
return OK;
}
Status distoryQueue(LinkQueue &Q)
{
while(Q.front!=NULL)
{
Q.rear=Q.front->next;
free(Q.front);
Q.front=Q.rear;
}
return OK;
}
Status DeQueue(LinkQueue &Q,QElemType &e)
{
QueuePtr p;
if(Q.front==Q.rear)
return ERROR;
p=Q.front->next;
e=p->data;
Q.front->next=p->next;
if(Q.rear==p)
Q.rear=Q.front;
free(p);
return OK;
}
int main()
{
LinkQueue Q;
InitQueue(Q);
printf("链队列中输入三个数据:\n");
printf("10\t20\t30");
EnQueue(Q,10);
EnQueue(Q,20);
EnQueue(Q,30);
printf("\n输出链队列中的三个数据:\n");
displayQueue(Q);
int e;
printf("链队列对头元素出队:");
DeQueue(Q,e);
printf("出队的元素为:e=%d\n\n",e);
printf("初始化链队列:\n\n");
distoryQueue(Q);
return 0;
}