第4章 栈和队列

第4章 栈和队列

栈(stack)是限定仅在表尾进行插入和删除操作的线性表。
我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。

栈的插入操作,叫作进栈,也称压栈、入栈。类似子弹入弹夹。栈的删除操作,叫作出栈,也有的叫作弹栈。如同弹夹中的子弹出夹。
在这里插入图片描述

4.1栈的抽象数据类型

对于栈来讲,理论上线性表的操作特性它都具备,可由于它的特殊性,所以针对它在操作上会有些变化。特别是插入和删除操作,我们改名为push和pop,英文直译的话是压和弹,更容易理解。你就把它当成是弹夹的子弹压入和弹出就好记忆了,我们一般叫进栈和出栈。

ADT栈(stack)
Data
	同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
    InitStack (*S):初始化操作,建立一个空栈S。
    Destroystack (*s):若栈存在,则销毁它。Clearstack(*s):将栈清空。
    stackEmpty(s):若栈为空,返回true,否则返回false。
    GetTop (s,*e):若栈存在且非空,用e返回S的栈顶元素。
    Push(*s,e):若栈S存在,插入新元素e到栈S中并成为栈顶元素。
    Pop (*s,*e):删除栈s中栈顶元素,并用e返回其值。
    StackLength(S):返回栈S的元素个数。
endADT

4.2栈的顺序存储结构及实现

4.2.1栈的顺序存储结构

既然栈是线性表的特例,那么栈的顺序存储其实也是线性表顺序存储的简化,我们简称为顺序栈。线性表是用数组来实现的,想想看,对于栈这种只能一头插入删除的线性表来说,用数组哪一端来作为栈顶和栈底比较好?
对,没错,下标为0的一端作为栈底比较好,因为首元素都存在栈底,变化最小,所以让它作栈底。

来看栈的结构定义

typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */
typedef struct
{
        SElemType data[MAXSIZE];
        int top; /* 用于栈顶指针 */
}SqStack;

在这里插入图片描述

4.2.2 进栈操作

/* 插入元素e为新的栈顶元素 */
Status Push(SqStack *S,SElemType e)
{
        if(S->top == MAXSIZE -1) /* 栈满 */
        {
                return ERROR;
        }
        S->top++;				/* 栈顶指针增加一 */
        S->data[S->top]=e;  /* 将新插入元素赋值给栈顶空间 */
        return OK;
}

在这里插入图片描述

4.2.2 出栈操作

/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqStack *S,SElemType *e)
{ 
        if(S->top==-1)
                return ERROR;
        *e=S->data[S->top];	/* 将要删除的栈顶元素赋值给e */
        S->top--;				/* 栈顶指针减一 */
        return OK;
}

4.3 两栈共享空间

在这里插入图片描述

#include "stdio.h"    
#include "stdlib.h"   

#include "math.h"  
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */

typedef int Status; 

typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */


/* 两栈共享空间结构 */
typedef struct 
{
        SElemType data[MAXSIZE];
        int top1;	/* 栈1栈顶指针 */
        int top2;	/* 栈2栈顶指针 */
}SqDoubleStack;


Status visit(SElemType c)
{
        printf("%d ",c);
        return OK;
}

/*  构造一个空栈S */
Status InitStack(SqDoubleStack *S)
{ 
        S->top1=-1;
        S->top2=MAXSIZE;
        return OK;
}

/* 把S置为空栈 */
Status ClearStack(SqDoubleStack *S)
{ 
        S->top1=-1;
        S->top2=MAXSIZE;
        return OK;
}

/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
Status StackEmpty(SqDoubleStack S)
{ 
        if (S.top1==-1 && S.top2==MAXSIZE)
                return TRUE;
        else
                return FALSE;
}

/* 返回S的元素个数,即栈的长度 */
int StackLength(SqDoubleStack S)
{ 
        return (S.top1+1)+(MAXSIZE-S.top2);
}

/* 插入元素e为新的栈顶元素 */
Status Push(SqDoubleStack *S,SElemType e,int stackNumber)
{
        if (S->top1+1==S->top2)	/* 栈已满,不能再push新元素了 */
                return ERROR;	
        if (stackNumber==1)			/* 栈1有元素进栈 */
                S->data[++S->top1]=e; /* 若是栈1则先top1+1后给数组元素赋值。 */
        else if (stackNumber==2)	/* 栈2有元素进栈 */
                S->data[--S->top2]=e; /* 若是栈2则先top2-1后给数组元素赋值。 */
        return OK;
}

/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
{ 
        if (stackNumber==1) 
        {
                if (S->top1==-1) 
                        return ERROR; /* 说明栈1已经是空栈,溢出 */
                *e=S->data[S->top1--]; /* 将栈1的栈顶元素出栈 */
        }
        else if (stackNumber==2)
        { 
                if (S->top2==MAXSIZE) 
                        return ERROR; /* 说明栈2已经是空栈,溢出 */
                *e=S->data[S->top2++]; /* 将栈2的栈顶元素出栈 */
        }
        return OK;
}

Status StackTraverse(SqDoubleStack S)
{
        int i;
        i=0;
        while(i<=S.top1)
        {
                visit(S.data[i++]);
        }
        i=S.top2;
        while(i<MAXSIZE)
        {
                visit(S.data[i++]);
        }
        printf("\n");
        return OK;
}

int main()
{
        int j;
        SqDoubleStack s;
        int e;
        if(InitStack(&s)==OK)
        {
                for(j=1;j<=5;j++)
                        Push(&s,j,1);
                for(j=MAXSIZE;j>=MAXSIZE-2;j--)
                        Push(&s,j,2);
        }

        printf("栈中元素依次为:");
        StackTraverse(s);

        printf("当前栈中元素有:%d \n",StackLength(s));

        Pop(&s,&e,2);
        printf("弹出的栈顶元素 e=%d\n",e);
        printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));

        for(j=6;j<=MAXSIZE-2;j++)
                Push(&s,j,1);

        printf("栈中元素依次为:");
        StackTraverse(s);

        printf("栈满否:%d(1:否 0:满)\n",Push(&s,100,1));

        
        ClearStack(&s);
        printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));
        
        return 0;
}

4.4 栈的链式存储和实现

简称栈链。

在这里插入图片描述

#include "stdio.h"    
#include "stdlib.h"   

#include "math.h"  
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */

typedef int Status; 
typedef int SElemType; /* SElemType类型根据实际情况而定,这里假设为int */


/* 链栈结构 */
typedef struct StackNode
{
        SElemType data;
        struct StackNode *next;
}StackNode,*LinkStackPtr;


typedef struct
{
        LinkStackPtr top;
        int count;
}LinkStack;

Status visit(SElemType c)
{
        printf("%d ",c);
        return OK;
}

/*  构造一个空栈S */
Status InitStack(LinkStack *S)
{ 
        S->top = (LinkStackPtr)malloc(sizeof(StackNode));
        if(!S->top)
                return ERROR;
        S->top=NULL;
        S->count=0;
        return OK;
}

/* 把S置为空栈 */
Status ClearStack(LinkStack *S)
{ 
        LinkStackPtr p,q;
        p=S->top;
        while(p)
        {  
                q=p;
                p=p->next;
                free(q);
        } 
        S->count=0;
        return OK;
}

/* 若栈S为空栈,则返回TRUE,否则返回FALSE */
Status StackEmpty(LinkStack S)
{ 
        if (S.count==0)
                return TRUE;
        else
                return FALSE;
}

/* 返回S的元素个数,即栈的长度 */
int StackLength(LinkStack S)
{ 
        return S.count;
}

/* 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR */
Status GetTop(LinkStack S,SElemType *e)
{
        if (S.top==NULL)
                return ERROR;
        else
                *e=S.top->data;
        return OK;
}

/* 插入元素e为新的栈顶元素 */
Status Push(LinkStack *S,SElemType e)
{
        LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode)); 
        s->data=e; 
        s->next=S->top;	/* 把当前的栈顶元素赋值给新结点的直接后继,见图中① */
        S->top=s;         /* 将新的结点s赋值给栈顶指针,见图中② */
        S->count++;
        return OK;
}

/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(LinkStack *S,SElemType *e)
{ 
        LinkStackPtr p;
        if(StackEmpty(*S))
                return ERROR;
        *e=S->top->data;
        p=S->top;					/* 将栈顶结点赋值给p,见图中③ */
        S->top=S->top->next;    /* 使得栈顶指针下移一位,指向后一结点,见图中④ */
        free(p);                    /* 释放结点p */        
        S->count--;
        return OK;
}

Status StackTraverse(LinkStack S)
{
        LinkStackPtr p;
        p=S.top;
        while(p)
        {
                 visit(p->data);
                 p=p->next;
        }
        printf("\n");
        return OK;
}

int main()
{
        int j;
        LinkStack s;
        int e;
        if(InitStack(&s)==OK)
                for(j=1;j<=10;j++)
                        Push(&s,j);
        printf("栈中元素依次为:");
        StackTraverse(s);
        Pop(&s,&e);
        printf("弹出的栈顶元素 e=%d\n",e);
        printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));
        GetTop(s,&e);
        printf("栈顶元素 e=%d 栈的长度为%d\n",e,StackLength(s));
        ClearStack(&s);
        printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));
        return 0;
}

4.5 栈的应用

二进制转换

#include <iostream>
#define ElemType int
#define INIT_SIZE 10  //初始容量
#define INCRE_SIZE 20 //自增量
//定义了一个顺序存储的栈
typedef struct
{
	ElemType *base; //栈底指针
	ElemType *top;	//栈顶指针
	int stackSize;	//最大容量
} sqStack;
//初始化
void initStack(sqStack *s)
{
	s->base = (ElemType *)malloc(INIT_SIZE * sizeof(ElemType));
	if (!s->base)
	{
		exit(0);
	}
	s->top = s->base; //初始化,栈顶就是栈底
	s->stackSize = INIT_SIZE;
}
void push(sqStack *s, ElemType e)
{
	if (s->top - s->base == s->stackSize)
	{ //判断栈满,追加空间
		//realloc 将原来的内存复制到一片新的内存中
		s->base = (ElemType *)realloc(s->base, (s->stackSize + INCRE_SIZE) * sizeof(ElemType));
		if (!s->base)
		{
			exit(0);
		}
		s->top = s->base + s->stackSize;		  //设置新的栈顶
		s->stackSize = s->stackSize + INCRE_SIZE; //重置最大值
	}
	*(s->top)++ = e;
}
ElemType pop(sqStack *s)
{
	if (s->top == s->base)
	{
		std::cout << "栈为空" << std::endl;
		return -1;
	}
	return *(--s->top);
}
//清空栈,让栈顶回到栈底即可
void clearStack(sqStack *s)
{
	s->top = s->base;
}
//销毁栈(和清空不同)
void destroyStack(sqStack *s)
{
	s->top = s->base;
	//释放为此栈所分配的内存空间,
	//在malloc时,系统会记住申请连续空间的起始地址和大小,
	//free时,只要把起始地址告诉系统,系统知道会free多大空间
	free(s->base);
	
	s->stackSize = 0;
}
ElemType size(sqStack *s){
	return s->top-s->base;
}
int main()
{
	sqStack s;
	initStack(&s);
	std::cout << "请输入2进制数,#号结尾"   << std::endl;
	char c;
	scanf("%c",&c);
	while(c!='#'){
		push(&s,c-48); //0的ascii码是48 所以1-0=1
		scanf("%c",&c);
	}
	getchar();
	int n =1,sum=0;
	while(s.base!=s.top){
		sum += pop(&s) * n;
		n *= 2;
	}
	std::cout << "十进制数为:" << sum << std::endl;
	return 0;
}

(逆波兰)四则运算表达式求值

20世纪50年代,波兰逻辑学家Jan Eukasiewicz,想到了一种不需要括号的后缀表达法,我们也把它称为逆波兰(Reverse Polish Notation,RPN)表示。我想可能是他的名字太复杂了,所以后人只用他的国籍而不是姓名来命名。这种后缀表示法,是表达式的一种新的显示方式,非常巧妙地解决了程序实现四则运算的难题。

对于“9+(3-1)×3+10÷2”,如果要用后缀表示法应该是什么样子:“9 3 1 - 3 * + 10 2 / +”,这样的表达式称为后缀表达式,叫后缀的原因在于所有的符号都是在要运算数字的后面出现 。显然,这里没有了括号。对于从来没有接触过后缀表达式的同学来讲,这样的表述是很难受的。

int main()
{
	sqStack s;
	initStack(&s);
	std::cout << "请按逆波兰表达式输入数据,数字以空格隔开,#号结尾"   << std::endl;
	char c;
	ElemType p,q;
	scanf("%c",&c);
	while(c!='#'){
		if(c != ' '){
			switch(c){
				case '+':
					p = pop(&s);
					q = pop(&s);
					push(&s,q+p);
					break;
				case '-':
					p = pop(&s);
					q = pop(&s);
					push(&s,q-p);
					break;
				case '*':
					p = pop(&s);
					q = pop(&s);
					push(&s,q*p);
					break;
				case '/':
					p = pop(&s);
					q = pop(&s);
					push(&s,q/p);
					break;
				default:
					push(&s,c-48);
			}
		}			  
		scanf("%c",&c);
	}
	getchar();
	std::cout << "后缀表达式的计算结果为:" << pop(&s) << std::endl;
	return 0;
}
//改写程序:可以计算小数
typedef double ElemType;
int main()
{
	sqStack s;
	initStack(&s);
	std::cout << "请按逆波兰表达式输入数据,数字以空格隔开,#号结尾" << std::endl;
	char c;
	char str[5];//假设最大4位字符
	int i=0;
	ElemType p, q, d;
	scanf("%c", &c);
	while (c != '#')
	{
		while (isdigit(c) || c == '.')
		{ //过滤数字或小数
			str[i++] = c;
			str[i] = '\0'; //结尾是0,下一次还是数字会覆盖
			if(i>=5){
				printf("输入数字过大");
				exit(0);
			}
			scanf("%c", &c);
			if (c == ' ')
			{
				push(&s, atof(str));
				i = 0;
				break;
			}

		}
		if (c != ' ')
		{
			switch (c)
			{
			case '+':
				p = pop(&s);
				q = pop(&s);
				push(&s, q + p);
				break;
			case '-':
				p = pop(&s);
				q = pop(&s);
				push(&s, q - p);
				break;
			case '*':
				p = pop(&s);
				q = pop(&s);
				push(&s, q * p);
				break;
			case '/':
				p = pop(&s);
				q = pop(&s);
				push(&s, q / p);
				break;
			}
		}
		scanf("%c", &c);
	}
	getchar();
	std::cout << "计算结果为:" << pop(&s) << std::endl;
	return 0;
}

我们把平时所用的标准四则运算表达式,即“9+(3-1)×3+10÷2”叫做中缀表达式。因为所有的运算符号都在两数字的中间,现在我们的问题就是中缀到后缀的转化。9+(3-1)*3+10/2 —> 9 3 1 - 3 * + 10 2 / +

总结规则:从左到右遍历中缀表达式的每个数字和符号,若是数字则直接输出,若是符号,则判断其与栈顶符号的优先级,是右括号或者优先级低于栈顶符 号,栈顶元素依次出栈并输出,直到遇到左括号或栈空才将最后的那个符号入栈。

typedef char ElemType; //栈存符号
int main()
{
	sqStack s;
	initStack(&s);
	std::cout << "请输入中缀表达式,#号结尾" << std::endl;
	char c;
	int i = 0;
	ElemType p, q, d;
	scanf("%c", &c);
	while (c != '#')
	{
		while (c >= '0' && c <= '9')
		{
			printf("%c", c);
			scanf("%c", &c);
			if (!isdigit(c))
			{
				printf(" ");
			}
		}
		if (c == '#')
		{
			break;
		}
		if (c == ')')
		{
			ElemType e = pop(&s);
			printf("%c ", e);
			pop(&s);
		}
		if (c == '+' || c == '-')
		{
			if (s.top == s.base)
			{ //如果栈空,直接push,因为+-优先级最低
				push(&s, c);
			}
			else
			{
				ElemType e;
				do
				{
					e = pop(&s);
					if (e == '(') //如果是左括号在栈顶,继续push
					{
						push(&s, c);
					}
					else //如果是其他符号,一直输出直到栈空或再遇左括号
					{
						printf("%c ", e);
					}
				} while (s.top != s.base && e != '(');
				push(&s, c);
			}
		}
		if (c == '*' || c == '/' || c == '(')
		{
			push(&s, c);
		}

		scanf("%c", &c);
	}
	getchar();
	while (s.base != s.top)//最后还有表达式,全pop
	{
		c = pop(&s);
		printf("%c ", c);
	}

	return 0;
}

4.6 队列的定义

队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。假设队列是q=( a1,a2,……,an),那么a1就是队头元素,而an是队尾元素。这样我们就可以删除时,总是从 a1开始,而插入时,列在最后。
在这里插入图片描述

4.7 队列的抽象数据类型

ADT队列(queue)
Data
	同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
	InitQueue (*o):初始化操作,建立一个空队列Q。
    DestroyQueue (*o):若队列Q存在,则销毁它。
    ClearQueue (*o):将队列Q清空。
    QueueEmpty (o):若队列Q为空,返回true,否则返回false。
    GetHead(o,*e):若队列Q存在且非空,用e返回队列Q的队头元素。
    EnQueue (*o,e):若队列Q存在,插入新元素e到队列Q中并成为队尾元素。
 	DeQueue (*o, *e):删除队列Q中队头元素,并用e返回其值。
	QueueLength(o):返回队列Q的元素个数
endADT

4.8 循环队列

为了避免当只有一个元素时,队头和队尾重合使处理变得麻烦,所以引入两个指针,front 指针指向队头元素,rear指针指向队尾元素的下一个位置,这样当front 等于rear时,此队列不是还剩一个元素,而是空队列。我们把队列的这种头尾相接的顺序存储结构称为循环队列。

在这里插入图片描述

队列满的条件是(rear+1) %QueueSize == front(取模“%”的目的就是为了整合rear 与front大小为一个问题)。

#include "stdio.h"    
#include "stdlib.h"   

#include "math.h"  
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */

typedef int Status; 
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */

/* 循环队列的顺序存储结构 */
typedef struct
{
	QElemType data[MAXSIZE];
	int front;    	/* 头指针 */
	int rear;		/* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SqQueue;

Status visit(QElemType c)
{
	printf("%d ",c);
	return OK;
}

/* 初始化一个空队列Q */
Status InitQueue(SqQueue *Q)
{
	Q->front=0;
	Q->rear=0;
	return  OK;
}

/* 将Q清为空队列 */
Status ClearQueue(SqQueue *Q)
{
	Q->front=Q->rear=0;
	return OK;
}

/* 若队列Q为空队列,则返回TRUE,否则返回FALSE */
Status QueueEmpty(SqQueue Q)
{ 
	if(Q.front==Q.rear) /* 队列空的标志 */
		return TRUE;
	else
		return FALSE;
}

/* 返回Q的元素个数,也就是队列的当前长度 */
int QueueLength(SqQueue Q)
{
	return  (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}

/* 若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR */
Status GetHead(SqQueue Q,QElemType *e)
{
	if(Q.front==Q.rear) /* 队列空 */
		return ERROR;
	*e=Q.data[Q.front];
	return OK;
}

/* 若队列未满,则插入元素e为Q新的队尾元素 */
Status EnQueue(SqQueue *Q,QElemType e)
{
	if ((Q->rear+1)%MAXSIZE == Q->front)	/* 队列满的判断 */
		return ERROR;
	Q->data[Q->rear]=e;			/* 将元素e赋值给队尾 */
	Q->rear=(Q->rear+1)%MAXSIZE;/* rear指针向后移一位置, */
								/* 若到最后则转到数组头部 */
	return  OK;
}

/* 若队列不空,则删除Q中队头元素,用e返回其值 */
Status DeQueue(SqQueue *Q,QElemType *e)
{
	if (Q->front == Q->rear)			/* 队列空的判断 */
		return ERROR;
	*e=Q->data[Q->front];				/* 将队头元素赋值给e */
	Q->front=(Q->front+1)%MAXSIZE;	/* front指针向后移一位置, */
									/* 若到最后则转到数组头部 */
	return  OK;
}

/* 从队头到队尾依次对队列Q中每个元素输出 */
Status QueueTraverse(SqQueue Q)
{ 
	int i;
	i=Q.front;
	while((i+Q.front)!=Q.rear)
	{
		visit(Q.data[i]);
		i=(i+1)%MAXSIZE;
	}
	printf("\n");
	return OK;
}

int main()
{
	Status j;
	int i=0,l;
	QElemType d;
	SqQueue Q;
	InitQueue(&Q);
	printf("初始化队列后,队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));

	printf("请输入整型队列元素(不超过%d个),-1为提前结束符: ",MAXSIZE-1);
	do
	{
		/* scanf("%d",&d); */
		d=i+100;
		if(d==-1)
			break;
		i++;
		EnQueue(&Q,d);
	}while(i<MAXSIZE-1);

	printf("队列长度为: %d\n",QueueLength(Q));
	printf("现在队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
	printf("连续%d次由队头删除元素,队尾插入元素:\n",MAXSIZE);
	for(l=1;l<=MAXSIZE;l++)
	{
		DeQueue(&Q,&d);
		printf("删除的元素是%d,插入的元素:%d \n",d,l+1000);
		/* scanf("%d",&d); */
		d=l+1000;
		EnQueue(&Q,d);
	}
	l=QueueLength(Q);

	printf("现在队列中的元素为: \n");
	QueueTraverse(Q);
	printf("共向队尾插入了%d个元素\n",i+MAXSIZE);
	if(l-2>0)
		printf("现在由队头删除%d个元素:\n",l-2);
	while(QueueLength(Q)>2)
	{
		DeQueue(&Q,&d);
		printf("删除的元素值为%d\n",d);
	}

	j=GetHead(Q,&d);
	if(j)
		printf("现在队头元素为: %d\n",d);
	ClearQueue(&Q);
	printf("清空队列后, 队列空否?%u(1:空 0:否)\n",QueueEmpty(Q));
	return 0;
}

从这一段讲解,大家应该发现,单是顺序存储,若不是循环队列,算法的时间性能是不高的,但循环队列又面临着数组可能会溢出的问题,所以我们还需要研究一下不需要担心队列长度的链式存储结构。

4.9 队列的链式存储结构及实现

队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。为了操作上的方便,我们将队头指针指向链队列的头结点,而队尾指针指向终端节点。空队列时,front和rear都指向头结点。

在这里插入图片描述

#include "stdio.h"    
#include "stdlib.h"   

#include "math.h"  
#include "time.h"

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */

typedef int Status; 

typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */

typedef struct QNode	/* 结点结构 */
{
   QElemType data;
   struct QNode *next;
}QNode,*QueuePtr;

typedef struct			/* 队列的链表结构 */
{
   QueuePtr front,rear; /* 队头、队尾指针 */
}LinkQueue;

Status visit(QElemType c)
{
	printf("%d ",c);
	return OK;
}

/* 构造一个空队列Q */
Status InitQueue(LinkQueue *Q)
{ 
	Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode));
	if(!Q->front)
		exit(OVERFLOW);
	Q->front->next=NULL;
	return OK;
}

/* 销毁队列Q */
Status DestroyQueue(LinkQueue *Q)
{
	while(Q->front)
	{
		 Q->rear=Q->front->next;
		 free(Q->front);
		 Q->front=Q->rear;
	}
	return OK;
}

/* 将Q清为空队列 */
Status 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 OK;
}

/* 若Q为空队列,则返回TRUE,否则返回FALSE */
Status QueueEmpty(LinkQueue Q)
{ 
	if(Q.front==Q.rear)
		return TRUE;
	else
		return FALSE;
}

/* 求队列的长度 */
int QueueLength(LinkQueue Q)
{ 
	int i=0;
	QueuePtr p;
	p=Q.front;
	while(Q.rear!=p)
	{
		 i++;
		 p=p->next;
	}
	return i;
}

/* 若队列不空,则用e返回Q的队头元素,并返回OK,否则返回ERROR */
Status GetHead(LinkQueue Q,QElemType *e)
{ 
	QueuePtr p;
	if(Q.front==Q.rear)
		return ERROR;
	p=Q.front->next;
	*e=p->data;
	return OK;
}


/* 插入元素e为Q的新的队尾元素 */
Status EnQueue(LinkQueue *Q,QElemType e)
{ 
	QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
	if(!s) /* 存储分配失败 */
		exit(OVERFLOW);
	s->data=e;
	s->next=NULL;
	Q->rear->next=s;	/* 把拥有元素e的新结点s赋值给原队尾结点的后继,见图中① */
	Q->rear=s;		/* 把当前的s设置为队尾结点,rear指向s,见图中② */
	return OK;
}

/* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */
Status DeQueue(LinkQueue *Q,QElemType *e)
{
	QueuePtr p;
	if(Q->front==Q->rear)
		return ERROR;
	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 OK;
}

/* 从队头到队尾依次对队列Q中每个元素输出 */
Status QueueTraverse(LinkQueue Q)
{
	QueuePtr p;
	p=Q.front->next;
	while(p)
	{
		 visit(p->data);
		 p=p->next;
	}
	printf("\n");
	return OK;
}

int main()
{
	int i;
	QElemType d;
	LinkQueue q;
	i=InitQueue(&q);
	if(i)
		printf("成功地构造了一个空队列!\n");
	printf("是否空队列?%d(1:空 0:否)  ",QueueEmpty(q));
	printf("队列的长度为%d\n",QueueLength(q));
	EnQueue(&q,-5);
	EnQueue(&q,5);
	EnQueue(&q,10);
	printf("插入3个元素(-5,5,10)后,队列的长度为%d\n",QueueLength(q));
	printf("是否空队列?%d(1:空 0:否)  ",QueueEmpty(q));
	printf("队列的元素依次为:");
	QueueTraverse(q);
	i=GetHead(q,&d);
	if(i==OK)
	 printf("队头元素是:%d\n",d);
	DeQueue(&q,&d);
	printf("删除了队头元素%d\n",d);
	i=GetHead(q,&d);
	if(i==OK)
		printf("新的队头元素是:%d\n",d);
	ClearQueue(&q);
	printf("清空队列后,q.front=%u q.rear=%u q.front->next=%u\n",q.front,q.rear,q.front->next);
	DestroyQueue(&q);
	printf("销毁队列后,q.front=%u q.rear=%u\n",q.front, q.rear);
	
	return 0;
}

4.10 总结回顾

这一章讲的是栈和队列,它们都是特殊的线性表,只不过对插入和删除操作做了限制。
栈(stack)是限定仅在表尾进行插入和删除操作的线性表。
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
它们均可以用线性表的顺序存储结构来实现,但都存在着顺序存储的一些弊端。因此有各自解决技巧。对于栈来说,如果是两个相同数据类型的栈,则可以用数组的两端作栈底的方法来让两个栈共享数据,这就可以最大化地利用数组的空间。对于队列来说,为了避免数组插入和删除时需要移动数据,于是就引入了循环队列,使得队头和队尾可以在数组中循环变化。解决了移动数据的时间损耗,使得本来插入和删除是O(n)的时间复杂度变成了0(1)。
它们也都可以通过链式存储结构来实现,实现原则上与线性表基本相同

    • 顺序栈
      • 两栈共享空间
    • 栈链
  • 队列
    • 顺序队列
      • 循环队列
    • 链队列
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值