栈和队列

326 阅读8分钟

栈和队列是限定插入和删除只能在表的 “端点” 进行的线性表

栈和队列的特点

定义: 栈(stack) 是一个特殊的线性表,是限定仅在一端(通常是表尾)进行插入和删除操作的线性表。 又称为 后进先出 的线性表。

逻辑结构: 与线性表相同,认为一对一的关系。

存储结构: 用顺序栈或者链栈存储均可,但以顺序栈更常见。

运算规则: 只能在栈顶运算,且访问节点时依照后进先出的原则。

实现方式: 关键是编写入栈和出栈函数,具体实现依顺序栈或链栈的不同而不同。

入栈:

image.png

出栈:

image.png

队列

定义: 只能在表的一段进行插入运算,在表的另一端进行删除运算的线性表。头删尾插

逻辑结构: 与线性表相同,仍为一对一关系。

存储结构: 顺序队或链队,以循环队列更常见。

运算规则: 只能在队首和队尾运算,且访问结点时依照先进先出的原则。

实现方式: 关键是掌握入队和出队操作,具体实现依顺序队或链队的不同而不同。

栈的表示和实现

栈的案例

image.png

上面这个例子,利用了栈的 后进先出 的特性,每一次除以 8 后将余数存储在栈中,最后依次输出栈里面的内容,就将十进制转换为了八进制。

image.png

上面这个案例,也是这个样子,当我们遇到 ( 或者 [ 的时候,我们就把他们存储在栈中,但是当我们遇到了 ) 或者 ] 就查询栈中栈顶的元素是否匹配,匹配就继续下去,不匹配我们就输出 FALSE。

顺序栈的操作的实现

注意事项

image.png

为了操作方便,我们通常将 top 指针指向真正栈顶元素之上的下标地址。

image.png

  1. 空栈:base == top 是栈空的标志
  2. 栈满:top-base == stackSize
  • 上溢:栈已经满,又要压入元素
  • 下溢:栈已经空,还要弹出元素

数据结构的准备


/* 顺序栈结构 */
typedef struct
{
    SElemType *base;
    SElemType *top;
    int stackSize; //当前分配的储存空间
}SqStack;

顺序表的入栈

  • 判断是否栈满,若满则出错(上溢)
  • 元素 e 压入栈顶
  • 栈指针加 1
Status Push(SqStack &s,SElemType e){
    // 栈满
    if(s.top - s.base == s.stackSize) return ERROR;
    *s.top++ = e;
    return OK;
}

顺序表的出栈

  • 判断是否栈空,若空则出错(下溢)
  • 获取栈顶元素e
  • 栈顶指针-1
Status Pop(SqStack &s,SElemType &e){
    //若栈不空,则删除s的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
    if(s.top == s.base) return ERROR;
    e = *--s.top;
}

完整实现

#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 100 /* 存储空间初始分配量 */

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

/* 顺序栈结构 */
typedef struct
{
    SElemType *base;
    SElemType *top;
    int stackSize; //当前分配的储存空间
}SqStack;

/* 初始化顺序表 */
Status InitStack(SqStack &s){
    s.base = (SElemType *)malloc(MAXSIZE * sizeof(SElemType));
    if(!s.base) exit(-1);
    s.top = s.base;
    s.stackSize = MAXSIZE;
    return OK;
}

/* 判断顺序栈是否为空 */
/* 如果栈为空,返回TURE;否则返回FALSE */
Status StackEmpty(SqStack &s){
    if(s.top == s.base) return TRUE;
    else return FALSE;
}


/* 求栈的长度 */
Status StackLength(SqStack s){
    return s.top - s.base;
}

/* 清空顺序栈 */
/* 并不需要将所有指针指向的数据全部清空,我们就当他们是空的就行了 */
Status ClearStack(SqStack s){
    if(s.base) s.top = s.base;
    return OK;
}

/* 销毁顺序栈 */
Status DestoryStack(SqStack &s){
    if(s.base){
        free(s.base);
        s.stackSize = 0;
        s.base = NULL;
        s.top = NULL;
    }
    return OK;
}


/* 顺序栈的入栈 */
Status Push(SqStack &s,SElemType e){
    // 栈满
    if(s.top - s.base == s.stackSize) return ERROR;
    *s.top++ = e;
    return OK;
}


/* 顺序栈的出栈 */
Status Pop(SqStack &s,SElemType &e){
    //若栈不空,则删除s的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
    if(s.top == s.base) return ERROR;
    e = *--s.top;
    return OK;
}


/* 从栈底到栈顶,依次输出元素 */
void StackTraverse(SqStack s){
    SElemType *cur = s.base;
    printf("当前栈的元素为:");
    while(cur < s.top){
        printf(" %d",*cur);
        cur++;
    }
    printf("\n");
}



int main(){
    int j;
    SqStack s;
    int e;

    // 初始化顺序表
    j = InitStack(s);
    printf("初始化顺序表:%d (1表示成功,0表示失败)\n",j);

    // 插入元素
    for(j=1;j<=10;j++){
        Push(s,j);
    }
    StackTraverse(s);

    // 删除元素
    Pop(s,e);
    printf("删除的元素为: %d\n",e);
    StackTraverse(s);

    // 判断栈是否为空
    j = StackEmpty(s);
    printf("数组是否为空:%d(1表示空,0表示非空)\n",j);

    // 判断栈中的元素个数
    j = StackLength(s);
    printf("数组的程度为 %d \n",j);

    // 清空栈
    ClearStack(s);
    j = StackEmpty(s);
    printf("数组是否为空:%d(1表示空,0表示非空)\n",j);

    // 销毁栈
    DestoryStack(s);

    return 0;
}

链栈的操作和实现

注意事项

链栈是运算受限的单链表,只能在链表头部进行操作。

image.png

  1. 链表的头指针就是栈顶
  2. 不需要头结点
  3. 基本不存在栈满的情况
  4. 空栈相当于头结点指向空
  5. 插入和删除仅在栈顶处执行

链栈的实现

主要关注初始化,插入和删除的操作

#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 100 /* 存储空间初始分配量 */

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 InitStack(LinkStack &s){
    s.top = (LinkStackPtr)malloc(sizeof(StackNode));
    if(!s.top) return ERROR;
    s.top = NULL;
    s.count = 0;
    return OK;
}

/* 将链栈置为空栈 */
Status ClearStack(LinkStack &s){
    LinkStackPtr p,q;
    p = s.top;
    while(p){
        q=p;
        p=p->next;
        free(q);
    }
    s.count = 0;
    s.top = NULL;
    return OK;
}

/* 判断s是否为空栈 */
Status StackEmpty(LinkStack s){
    if(s.count == 0) return TRUE;
    else FALSE;
}

/* 返回元素长度 */
int StackLength(LinkStack s)
{
        return s.count;
}

/* 获取元素 */
Status GetTop(LinkStack s,SElemType e){
    if(s.top == NULL) return ERROR;
    else e = s.top->data;
    return OK;
}

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

/* 删除元素 */
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)
        {
             printf(" %d",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;
}

栈和递归

递归的定义

若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的。

如果一个过程直接或者间接地调用自己,则称这个过程是递归的过程。

eg:递归求n的阶乘

long Fact(long n){
    if(n==0) return 1;  // 基本项
    else return n*Fact(n-1);  // 归纳项
}

分治法求递归

分治法:对于一个较为复杂的问题,能够分解成几个相对简单且解法相同或类似的字问题来求解。

必备的三个条件:

  1. 能将一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些处理对象的变化时是有规律的。
  2. 可通过上述变化而使问题简化
  3. 必须有一个明确的递归出口,或称递归的边界。

分治法求递归的基本形式:

void p(参数表){
    if(递归结束条件) 可直接求解步骤; ---基本项
    else p(较小的参数);  ---归纳项
}

递归函数调用过程

调用前:

  1. 将实参,返回地址等等传递给被调用函数
  2. 为被调用函数的局部变量分配存储区
  3. 将控制转移到被调用函数入口

调用后: 1.保存被调用函数的计算结果 2.释放被调用函数的数据区 3.依照被调用函数保存的返回地址将控制转移到调用函数

image.png

解释一下这个函数的调用吧:

  1. 首先进入 main 函数,执行 fact(3) 之前的代码
  2. 然后进入 fact 函数,执行 mypow(3.5,2) 之前的函数
  3. 执行 mypow 函数里面的代码,并返回到 z
  4. fact 执行完毕后,返回到 y 执行剩下的代码

image.png

递归的优缺点

优点: 结构清晰,程序易读

缺点: 每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,回复状态信息。时间开销大。

顺序队列的操作和实现

注意事项

image.png

上图为入队,出队和空队的情况

真溢出和假溢出的情况

image.png

这种情况,front=0, rear=MAXSIZE,当再有元素进入队列的时候,这自然是 真溢出

image.png

但是当这种情况,很显然, front != 0 , reae == MAXSIZE 下面还有空间未使用,这就是假溢出

解决假上溢的方法:

  1. 将对中元素依次向对头方向移动。

    缺点:浪费时间。每移动一次,队中元素都要移动。

  2. 将对空间设想成为一个循环的表,即分配给队列的 m 个储存单位可以循环使用,当 rear 为 MAXSIZE 时,又可以从头使用空着的空间。当 frontMAXSIZE 时,也是一样的。

image.png

此时,我们会发现,对空和队满的情况都是 front == rear

解决方法:

1. 另外设置一个标志以区分对空和对满
2. 另设一个变量,记录元素个数
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 QElemType;

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

/* 初始化列表 */
Status InitQueue(SqQueue &Q)
{
    Q.base = (QElemType*)malloc(MAXSIZE*sizeof(QElemType));
    if(!Q.base) exit(-1);
	Q.front=0;
	Q.rear=0;
	return  OK;
}

/* 求队列的长度 */
int QueueLength(SqQueue Q){
    return ((Q.rear-Q.front+MAXSIZE)%MAXSIZE);
}

/* 判断队列是否为空 */
Status QueueEmpty(SqQueue Q)
{
	if(Q.front==Q.rear) /* 队列空的标志 */
		return TRUE;
	else
		return FALSE;
}

/* 循环队列入队 */
Status EnQueue(SqQueue &Q,QElemType e){
    if((Q.rear+1)%MAXSIZE == Q.front) return ERROR; // 队满
    Q.base[Q.rear] = e;
    Q.rear = (Q.rear+1)%MAXSIZE;
    return OK;
}


/* 循环队列出队 */
Status DeQueue(SqQueue &Q,QElemType &e){
    if(Q.front == Q.rear) return ERROR; // 对空
    e = Q.base[Q.front];
    Q.front = (Q.front+1)%MAXSIZE;
    return OK;
}


/* 返回头元素 */
QElemType GetHead(SqQueue Q){
    // 队列不为空,返回头指针元素的值
    if(Q.front != Q.rear) return Q.base[Q.front];
}

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

/* 将Q清为空队列 */
Status ClearQueue(SqQueue Q)
{
	Q.front=Q.rear=0;
	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为提前结束符\n",MAXSIZE-1);
	do
	{
		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);
		d=l+1000;
		EnQueue(Q,d);
	}
	l=QueueLength(Q);

	QueueTraverse(Q);

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

链栈的操作和实现

image.png

#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 InitQueue(LinkQueue &Q)
{
	Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));
	if(!Q.front)
		exit(OVERFLOW);
	Q.front->next=NULL;
	return OK;
}

/* 清空队列 */
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;
}

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

/* 判断是否为空队列 */
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;
}

/* 链队列的入队 */
Status EnQueue(LinkQueue &Q,QElemType e){
    QueuePtr p=(QueuePtr)malloc(sizeof(QNode));
    if(!p) exit(-1);
    p->data = e;
    p->next = NULL;
    Q.rear->next = p;
    Q.rear = p;
    return OK;
}


/* 链队列的出队 */
Status DeQueue(LinkQueue &Q,QElemType e){
    if(Q.front == Q.rear) return ERROR;
    QueuePtr p = Q.front->next;
    e=p->data;
    Q.front->next = p->next;
    // 链队列中只有一个元素
    if(Q.rear == p) Q.rear = Q.front;
    free(p);
    return OK;
}

/* 获取头结点 */
Status GetHead(LinkQueue Q,QElemType e)
{
	QueuePtr p;
	if(Q.front==Q.rear)
		return ERROR;
	p=Q.front->next;
	e=p->data;
	return OK;
}

/* 输出队列 */
Status QueueTraverse(LinkQueue Q)
{
	QueuePtr p;
	p=Q.front->next;
	while(p)
	{
		 printf(" %d",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:否) \n",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:否)  \n",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;
}

结语

文章如果有不正确的地方,欢迎指正,共同学习,共同进步。

若有侵权,请联系作者删除。