数据结构栈和队列

本文详细介绍了数据结构中的栈和队列。栈是一种后进先出(LIFO)的线性表,包括顺序栈和链栈的存储结构及其基本运算。队列则是先进先出(FIFO)的线性表,讨论了循环队列的实现和操作。文章提供了相关算法设计和应用实例。

一、栈(stack)


1、基本概念

  • 栈是一种特殊的线性表,又称为后进先出LIFO (Last In First Out)或先进后出FILO (First In Last Out)线性表
  • 插入删除运算仅限定在线性表的某一端进行,不能再表中间和另一端进行
  • 栈的插入操作称为进栈(入栈、Push),删除操作称为出栈(退栈、Pop)
  • 栈顶:允许进行插入和删除的一端称为栈顶        栈底:另一端,固定不变
  • 栈顶元素:处于栈顶位置的数据元素
  • 空栈:不含任何数据元素的栈

2、基本运算

初始化栈 :InitStack(S)建立一个空栈S。
销毁栈 :DestroyStack(S)释放栈S占用的内存空间。进栈Push(S, x)。将元素x插入栈S中,使x成为栈S的栈顶元素。
出栈:Pop(S, x)当栈S不空时,将栈顶元素赋给x,并从栈中删除当前栈顶。
取栈顶元素 :GetTop(S)若栈S不空,取栈顶元素x并返回1;否则返回0。
判断栈空 :StackEmpty(S)。判断栈S是否为空栈。
示例

 

3、栈的顺序存储结构(顺序栈)

顺序栈类型声明:

静态分配:

  • 顺序栈通常由一个一维数组data和一个记录栈顶元素位置的变量top组成
  • 习惯上将栈底放在数组下标小的那端栈顶元素由栈顶指针top所指向

动态分配

  • base表示栈底指针,栈底固定不变
  • top指示当前栈顶位置,栈顶随着进栈和退栈操作而变化
  • top==base作为栈空的标记
  • 每次top指向栈顶数组中的下一个存储位置
#define STACK_INIT_SIZE 100			//存储空间初始分配量 
#define STACK_INCREAMENT 10			//存储空间分配增量 
typedef char ElemType;

//静态分配
typedef struct{
    ElemtType elem[STACK_INIT_SIZE];    //一维数组
    int top;                              //记录栈顶元素位置的变量
}SqStack;


//动态分配
typedef struct
{
	ElemType *base;					//栈底指针 
	Elemtype *top;					//栈顶指针 
	int stacksize;					//栈空间大小 
 } SqStack;							//顺序栈 

 

插入删除过程中只有top指针变化

对于顺序栈S,可通过动态申请得到其存储空间,它有4个要素

栈空条件:S.top==S.base

栈满条件:S.top-S.base>=S.stacksize

元素e进栈操作:*(S.top)++=e                //先进行*(S.top)运算再进行++运算

元素e出栈操作:e=*- - S.top                  //先进行- -操作再把- -s.top的值赋给e

基本算法设计

1、初始化栈

主要操作:通过动态申请所需的存储空间,并且初始化相关的参数(top,base,stacksize)。
void InitStack(SqStack &S)
{
	if(!(S.base=(Elemtype*)malloc(STACK_INIT_SIZE *sizeof(ElemtType))))
	exit(OVERFLOW);//存储分配失败
	S.tpo=S.base;//空栈 
	S.stacksize=STACK_INIT_SIZE; //栈空间大小 
}

2、销毁栈

主动释放空间,  栈顶、栈底两个指针置空,栈空间大小置零
void DestroyStack(SqStack &S)
{
	free(S.base);
	S.base=NULL;
	S.top=NULL;
	S.stacksize=0;
}

3、进栈

主要操作:判断是否存在“溢出”
void Push(Sqstack &S,Elemtype e)
{
	if(S.top-S.base>=S.stack)
	{	//栈满,追加存储空间 
		S.base=(ElemType*)realloc(S.base,(S.stacksize+STACK+INCREMENT)*sizeof(ElemType));
		if(S.base)
			exit(OVERFLOW);            //存储分配失败	
		S.top=S.base+S.stacksize;	
		S.stacksize+=STACK_INCREMENT;
	}
	*(S.top)++=e;                      //先进行*(S.top)运算再进行++运算
}

4、出栈操作

主要操作:判断是否为空栈。
若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
Status Pop(SqStack &S,ElemType &e)
{
	if(S.top==S.base)
		return ERROR;
	e=*--S.top;                 //先进行- -操作再把- -s.top的值赋给e
	return OK;
}

5、取栈顶元素

主要操作:通过top指针,取出栈顶元素。需要注意的是,top指针没有发生改变
(无 - - 操作)
若栈不空,则用e返回S的栈顶元素,并返回OK; 否则返回ERROR
Status GetTop(SqStack S,ElemType &e)
{
	if(S.top > S.base)
		e=*(S.top - 1);
		return OK;
	else
		return ERROR;
}

6、判断栈空

主要操作:若栈为空(top==base)则返回值1,否则返回值0
Status StackEmpty(SqStack S)
{
	if(S.top == S.base)
		return TRUE;
	else
		return FALSE;
}

4、栈的链式存储结构(链栈)

  • 采用不带头节点的单链表作为链栈

  首结点为栈顶,插入删除操作在链表头部进行  

链栈的结点声明

typedef char ElemType;
typedef struct node
{
	ElemType data;
	struct node *next;
}LinkStack;
  • 链栈top初始时top=NULL,其4个要素如下

栈空条件:top==NULL

栈满条件:不考虑

元素x进栈操作:创建存放元素x的结点p,将其插入到栈顶位置上

元素x出栈操作:置x为栈顶结点的data域,并删除该节点

基本算法

1、初始化

主要操作:用top==NULL标识栈为空栈。
void InitStack(LinkStack *top)
{
	top=NULL;
}

2、销毁

void DestroyStack(LinkList *&top)
{
	LinkStack *pre=top,*p;
	if(pre==NULL)return;		//考虑空栈情况 
	p=pre->next;
	while(p!=NUll)
	{
		free(pre);				//释放pre结点 
		pre=p;
		p=p->next;				//pre、p同步后移 
	}
	free(pre);					//释放尾接结点 
}

3、进栈

主要操作:先创建一个新结点,其data域值为x然后将该结点插入到top结点之后作为栈顶结点。
int Push(LinkStack *&top,ElemType x)
{
	LinkStack *p;
	p=(LinkStack *)malloc(sizeof(LinkStack));
	p->data=x;
	p->next=top;				//插入p结点作为栈顶结点
	top=p;
	return 1;
}

4、出栈

主要操作:将栈顶结点(即top->next所指结点) data域值赋给x,然后删除该栈顶结点。
int Pop(LinkStack *&top,ElemType &x)
{
	LinkStack *p;
	if(top==NULL)			//栈空,下溢出返回0
		return 0;
	else					//栈不空时出栈元素x并返回1
	{
		p=top;				//p指向栈顶结点
		x=p->data;			//取栈顶元素x
		top=p->next;		//删除结点p
		free(p);			//释放p结点
		return 1;
	}
}

5、取栈顶元素

主要操作:将栈顶结点(即top->next所指结点)data域值赋给x

int GetTop(LinkStack *top,ElemType &x)
{
	if(top==NULL)			//栈空,下溢出时返回0
		return 0;
	else					//栈不空,取栈顶元素x并返回1
	{
		x=top->data;
		return 1;
	}
}

6、判断栈空

主要操作:若栈为空(即top->next==NULL则返回值1,否则返回值0
int StackEmpty(LinkStack *top)
{
	if(top==NULL)
		return 1;
	else
		return 0;
}

 

 

5、栈的应用示例

  • 在较复杂的数据处理过程中,通常需要保存多个临时产生的数据,如果先产生的数据后进行处理,那么需要用来保存这些数据
  • 后面示例算法中均采用顺序栈,实际上采用链栈完全相同,一般用顺序栈

eg:

 AD均合法,而BC不合法。

② 用一个顺序栈st来判断操作序列是否合法。
#include"Sqstack.cpp"				//包含前面的顺序栈基本运算函数
int Judge(char str[],int n)
{
	int i;
	ElemType x;
	SqStack st;						//定义一个顺序栈st
	InitStack(st);					//栈初始化
	for(i=0;i<n;i++)				//遍历str的所有字符
	{
		if(str[i]=='I')				//为'I'时进栈
			Push(st,str[i]);
		else if(str[i]=='o')		//为'O'时出栈
			if(!Pop(st,x))			//若栈下溢出,则返回0
			{
				DestroyStack(st);
				return 0;
			}
	}
	if(StackEmpty(st))				//栈为空时返回1;否则返回0
	{
		DestroyStack(st);return 1;
	}
	else
	{
		DestroyStack(st); return 0;
	}
 } 

int Palindrome(char str[], int n)
{
    SqStack st;                                 //定义一个顺序栈st
    InitStack(st);                               //栈初始化
    int i;
    char ch;
    for (i = 0; i < n; i++)                 //所有字符依次进栈
        Push(st, str[i]);
    i = 0;                                           //从头开始遍历str
    while (!StackEmpty(st))               //栈不空循环
    {
        Pop(st, ch);                            //出栈元素ch
        if (ch != str[i++])                   //两字符不相同时返回0
            return 0;
    }
    return 1;                                   //所有相应字符都相同时返回1 
}

 思路:

  • 设置一个顺序栈st,定义一个整型flag变量(初始为1)。
  • 用 扫描表达式exp,当i<n并且flag=1时循环:
  • 当遇到左括号“(”、“[”、“{”时,将其进栈;
  • 遇到“}”、“]”、“)”时,出栈字符ch,若出栈失败(下溢出)或者ch不匹配,则置flag=0退出循环;
  • 否则直到exp扫描完毕为止。
  • 若栈空并且flag1则返回1,否则返回0
}
#include "SqStack.cpp"                                  //包含前面的顺序栈基本运算函数
int Match(char exp[], int n)                            //exp存放表达式
{
    SqStack st;                                                 //定义一个顺序栈st
    InitStack(st);                                              //栈初始化
    int flag = 1, i = 0;
    char ch;
    while (i < n && flag == 1)                       //遍历表达式exp
    {
        switch (exp[i])
        {
        case '(': case '[': case '{':                       //各种左括号进栈
            Push(st, exp[i]); break;
        case ')':                                               //判断栈顶是否为'('
            if (!Pop(st, ch) || ch != '(')            //出栈操作失败或不匹配
                flag = 0;
            break;
        case ']':                                               //判断栈顶是否为'['
            if (!Pop(st, ch) || ch != '[')             //出栈操作失败或不匹配
                flag = 0;
            break;
        case '}':                                                //判断栈顶是否为'{'
            if (!Pop(st, ch) || ch != '{')              //出栈操作失败或不匹配
                flag = 0;
            break;
        }
        i++;
    }
    if (StackEmpty(st) && flag == 1)             //栈空且符号匹配则返回1
        return 1;
    else
        return 0;                                                //否则返回0 
}

二、队列


1、基本概念

  • 队列(简称队)也是一种运算受限的线性表,插入限定在表的某一端进行,删除限定在表的另一端进行,是一种先进先出(First In First Out ,简称FIFO)的线性表
  • 队列的插入操作称为进队删除操作称为出队
  • 允许插入的一段称为队尾,允许删除的一端称为队头(尾插头删)
  • 新插入的元素只能添加到队尾,被删除的只能是排在队头的元素

2、基本运算

初始化队列:InitQueue(Q)。建立一个空队Q。
销毁队:DestroyQueue(Q)。释放队列Q占用的内存空间。
进队:EnQueue(Q, x)。将x插入到队列Q的队尾。
出队:DeQueue(Q, x)。将队列Q的队头元素出队并赋给x
取队头元素:GetHead(Q, x)。取出队列Q的队头元素并赋给x,但该元素不出队
判断队空:QueueEmpty(Q)。判断队列Q是否为空。

3、循环队列——队列的顺序表示和实现

  • 队列有两种存储结构,顺序存储和链式存储结构
  • 队列的顺序存储结构简称为顺序队列,它由一个一维数组(用于存储队列中元素)及两个分别指示队头和队尾的变量组成,分别为“队头指针”和“队尾指针
  • 初始化建空队列时,令front=rear=0.在非空队列中,头指针始终指向队列头元素,而尾指针始终指向rear队尾元素的下一个位置

顺序队列的类型声明

#define MaxSize 20
typedef struct
{
    ElemType data[MaxSize];        //开数组保存队中元素
    int front,rear;                //设置指示队头和队尾的变量
}SqQueue;

 从中可以看到,图 (a)(e)都是队空的情况,均满front==rear的条件,所以可以将front==rear作为队空的条件

那么队满的条件如何设置呢?受顺序栈的启发,似乎很容易得到队满的条件为rear==MAXSIZE显然这里有问题,因为图 (d)(e)都满足这个“队满”的条件,而实际上队列并没有满。

这种因为队满条件设置不合理而导致的“溢出”称为假溢出,也就是说这种“溢出”并不是真正的溢出, 尽管队满条件成立了,但队列中还有多个存放元素的空位置。为了能够充分地使用数组中的存储空间,可以把数组的前端和后端“假想地”连接起来,形成一个环形的 表,即把存储队列元素的表从逻辑上看成一个环。
这个环形的表叫做循环队列环形队列

 循环队列:

循环队列中 模 MAXSIZE是关键;留出一个空间不存放数据 

判断队空:

        front==rear

判断队满:

     (rear+1)MOD MAXSIZE==front。

进队:

        Q.data[Q.rear]=x;

        Q.rear=(Q.rear+1)%MaxSize;

出队:

        x=Q.data[Q.front];

        Q.front=(Q.front+1)%MaxSize;

 循环队列声明

#define MAXSIZE 100
typedef struct
{
    ElemType *base;        //初始化的动态分配存储空间(指向数组的指针)
    int front;             //若队列不空,头指针指向队列头元素
    int rear;              //若队列不空,指向队列尾元素的下一个位置
}SqQueue;

基本算法

  • 初始化
void InitQueue(SqQueue& Q)
{
    Q.base = (QElemType*)malloc(MAXSIZE * sizeof(QElemType));//开辟一块内存空间
    if (!Q.base)                                //存储分配失败
        exit(OVERFLOW);
    Q.front = Q.rear = 0;                       //指示队头和队尾的变量赋0
}
  • 销毁队列
void DestroyQueue(SqQueue& Q)
{
    if (Q.base)
        free(Q.base);
    Q.base = NULL;                    //指针置空
    Q.front = Q.rear = 0;             //指示队头和队尾的变量赋0
}
  • 入队列
Status EnQueue(WqWueue& 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;
}
  • 出队列
Statuse 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;
}
  • 取队列头元素
Status GetJead(SqQueue Q, QElemType& e)
{
    if (Q.fornt == Q.rear)
        return ERROR;
    e = Q.base[Q.front];
    return OK;
}
  • 判断队空
Status QueueEmpty(SqQueue Q)
{
    if (Q.front == Q.rear)
        return true;
    else
        return FALSE;
}

应用举例

对于循环队列,写出求队列中元素个数的公式,并编写相应的算法。
int QueueLength(SqQueue Q)
{
    return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

如果用一个大小为MaxSize的环形数组表示队列,该队列只有一个队头指针front,不设队尾指rear,而改置一个计数器count,用以记录队列中
的元素个数。
1)队列中最多能容纳多少个元素?
2)设计实现队列基本运算的算法。
//设计队列类型
typedef struct
{
	ElemType data[MAXSIZE];
	int front;
	int count
}SqQueue;


//队初始化
void InitQueue(SqQueue &qu)

{
	qu.front=qu.count=0;
}

//销毁队
void DestoryQueue(SqQueue sq)
{
	
 } 
//入队

int EnQueue(&sq,ElemType)
{
	if(sq.count==MAXSIZE)
		return 0;
	sq.data[(sq.count+sq.front)%MAXSIZE]=x;
	sq.count++;
	return 1;
}

//出队
int DeQueue(SqQueue &sq,ElemType &x)
{
	if(sq.count==0)
		return 0;
	x=sq.data[sq.front];
	sq.front=(sq.front+1)%MAXSIZE;
	sq.count--;
	return 1;
}

//取队头元素
int GetHead(SqQueue sq,ElemType &x)
{
	if(sq.count==0)
		return 0;
	x=sq.data[sq.front];
	return 1;
}

//判断队空 
int QueueEmpty(SqQueue sq)
{
	return (sq.count==0);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值