• 3.0 栈和队列



    title: 数据结构 | 栈和队列
    date: 2019-12-6 22:34:58
    tags: 数据结构


    顺序栈、链栈、循环队列、链队列

    • 学习目标
      掌握栈和队列这两种抽象数据类型的特点,并能在相应的应用问题中正确选用它们。
      熟练掌握栈类型的两种实现方法。
      熟练掌握循环队列和链队列的基本操作实现算法。
      理解递归算法执行过程中栈的状态变化过程。
    • 知识点
      顺序栈、链栈、循环队列、链队列

    只允许在一端插入和删除的顺序表
    允许插入和删除的一端称为栈顶 (top) —表尾
    另一端称为栈底(bottom) —表头
    不含元素的空表称空栈
    特点: 先进后出(FILO)或后进先出(LIFO)

    ADT

    ADT Stack
    {
    数据对象:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0}
    数据关系:R={<ai-1,ai>|ai,ai-1∈D,i=2,…,n}
    		          约定:an为栈顶,a1为栈底
    基本操作:
    	InitStack(&S)
    	操作结果:构造一个空的栈S。 
    	DestroyStack(&S)
    	初始条件: 栈S已经存在。
    	操作结果: 销毁栈S。
    	ClearStack(&S)
    	初始条件: 栈S已经存在。
    	操作结果: 将栈S重置为空栈。	
    	StackEmpty(S)
    	初始条件: 栈S已经存在。
    	操作结果: 若栈S为空栈,则返回TURE;否则返回FALSE
    		-判定栈是否为空栈是栈在应用程序中经常使用的操作,通常以它作为循环结束的条件。 
    	StackLength(S)
    	初始条件: 栈S已经存在。
    	操作结果: 返回栈S中的数据元素个数。
    	GetTop(S,&e)
    	初始条件: 栈S已经存在且非空。
    	操作结果: 用e返回栈S中栈顶元素的值。
    		-这是取栈顶元素的操作,只以 e 返回栈顶元素,并不将它从栈中删除。
    	Push(&S,e)
    	初始条件: 栈S已经存在。
    	操作结果: 插入元素e为新的栈顶元素。
    		-这是入栈操作,在当前的栈顶元素之后插入新的栈顶元素。 
    	Pop(&S,&e)
    	初始条件: 栈S已经存在且非空。
    	操作结果: 删除S的栈顶元素并用e返回其值。
    		-这是出栈操作,不仅以 e 返回栈顶元素,并将它从栈中删除。 
    	StackTraverse(S,visit ())
    	初始条件: 栈S已经存在且非空。
    	操作结果: 从栈底到栈顶依次对S的每个元素调用函数visit ()。一旦visit ()失败,则操作失败。
    		-这是对栈进行从栈底到栈顶的"遍历"操作,应用较多的场合是,输出栈中所有数据元素。
    }
    

    栈的表示和实现

    两种

    • 顺序存储结构__顺序栈
    • 链式存储结构__链栈

    顺序栈的定义

    利用一组地址连续的存储单元依次自栈底到栈顶存放栈的数据元素。

    • 栈顶指针top
      指向实际栈顶后的空位置,初值为0
    • top=0,栈空, 此时出栈,则下溢(underflow)
      top=M,栈满,此时入栈,则上溢(overflow)

    顺序栈的数据类型

    #define STACK_INIT_SIZE   100;//存储空间初始分配量
    #define STACKINCREMENT   10;//存储空间分配增量
    
    typedef struct
    {
         SElemType   *base; //在栈构造之前和销毁之后,值为null
         SElemType   *top;   //栈顶指针
         int   StackSize;  //当前已分配的存储空间,以元素为单位
    } SqStack;
    

    顺序栈的操作

    InitStack

    Status InitStack( SqStack &S )
    {
    	S.Base=(SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
    	if(!S.Base)
    	{
    		return OVERFLOW;
    	}
    	S.Top = S.Base;
    	S.StackSize = STACK_INIT_SIZE;
    	return OK;
    }// InitStack
    

    GetTop

    // 用e返回栈S的栈顶元素,若栈空,函数返回ERROR
    Status GetTop( SqStack S, SElemType &e)  
    {
    	if( S.Top != S.Base )		// 栈空吗?
    	{
    		e = *( S.Top – 1 );
    		return OK;
    	}
    	else
    	{
    		return ERROR;
    	}
    }// GetTop
    

    Push

    //把元素e入栈
    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 )
    			return OVERFLOW; //存储分配失败
    		S.Top = S.Base + S.StackSize;
    		S.StackSize += STACKINCREMENT;
    	} 
    	*S.Top = e;
    	S.Top++;
    	return OK;
    }// Push
    

    Pop

    // 出栈
    Status Pop( SqStack &S, SElemType &e )
    {
    	if( S.Top == S.Base )	// 空吗?
    	{
    		return ERROR;
    	}
    	S.Top --;
    	e = *S.Top;
    	return OK;
    }// Pop
    

    链栈

    定义

    typedef struct{    
    	SLink top;  // 栈顶指针    
    	int length; // 栈中元素个数  
    }Stack; 
    typedef struct node{   
    	int data;
        struct node *next;
    }*SLink;
    

    栈的应用举例

    把10进制数159转换成8进制数


    因此,需要先保存在计算过程中得到的八进制数的各位,然后逆序输出,因为它是按"后进先出"的规律进行的,所以用栈最合适。

    void conversion () 
    {// 对于输入的任意一个非负十进制整数,打印输出与其等值的八进制数
    	InitStack(S); // 构造空栈
    	scanf ("%d",N);
    	while (N) {
    		Push(S, N % 8);
    	    N = N/8;
    	}
    	while (!StackEmpty(S)) {
    	    Pop(S,e);
    	    printf ( "%d", e );
    	}
    } // conversion
    

    括弧匹配检验

    现在的问题是,要求检验一个给定表达式中的括弧是否正确匹配?
    检验括号是否匹配的方法可用“期待的急迫程度”这个概念来描述。

    status matching(string& exp) {
    // 检验表达式中所含括弧是否正确嵌套,若是,则返回
    // OK,否则返回ERROR
    	int state = 1;
    	while (i<=length(exp) && state) {
    		swith of exp[i] {
    	    	case "(": {Push(S,exp[i]); i++; break;}
    	        case ")": {   
    	            if (NOT StackEmpty(S) && GetTop(S) = "(")
    	                { Pop(S,e); i++; }   
    	            else{ state = 0 }
    	                break;
    	        }
    	 …  }
    	}
    	if ( state && StackEmpty(S) ) 
    		return OK
    	else return ERROR;
    }
    

    行编辑程序问题

    • 一个简单的行编辑程序的功能是:接受用户从终端输入的程序或数据,并存入用户的数据区。每接受一个字符即存入用户数据区。
    • 较好的做法是,设立一个输入缓冲区,用以接受用户输入的一行字符,然后逐行存入用户数据区。允许用户输入出差错,并在发现有误时可以及时更正。
      例如,可用一个退格符“#”表示前一个字符无效;可用一个退行符“@”,表示当前行中的字符均无效。
      例如,假设从终端接受了这样两行字符:

    whli##ilr#e(s#s)
    outcha@putchar(
    s=#++);

    则实际有效的是下列两行:

    while (s)
    putchar(
    s++);

    void LineEdit() {
    // 利用字符栈S,从终端接收一行并传送至调用过程
    // 的数据区。
    	InitStack(S); //构造空栈S
    	ch = getchar(); //从终端接收第一个字符
    	while (ch != EOF) { //EOF为全文结束符
    		while (ch != EOF && ch != '
    ') {
    			switch (ch) {
    				case '#' : Pop(S, c); break; // 仅当栈非空时退栈
    				case '@': ClearStack(S); break; // 重置S为空栈
    				default : Push(S, ch); break; // 有效字符进栈,未考虑栈满情形
    				}
    			ch = getchar(); // 从终端接收下一个字符
    			}
    			将从栈底到栈顶的字符传送至调用过程的数据区
    			ClearStack(S); // 重置S为空栈
    			if (ch != EOF) ch = getchar();
    		}
    	DestroyStack(S);
    }
    

    表达式求值

    • 规则:
      先乘除,后加减;
      从左到右;
      先括号内,后括号外;
    • 把运算符和界限符统称为算符
    • “算符优先法”
      4 - 10 / 5 + 2 * ( 3 + 8 )

    4
    4 -
    4 - 10
    4 - 10 /
    4 - 10 / 5
    4 - 10 / 5 + => 4 – 2 +
    4 - 2 + => 2 +
    2 + 2
    2 + 2 *
    2 + 2 * (
    2 + 2 * ( 3
    2 + 2 * ( 3 +
    2 + 2 * ( 3 + 8
    2 + 2 * ( 3 + 8 ) => 2 + 2 * 11 => 2 + 22 => 26

    算符优先法
    根据这个运算优先关系的规定来实现对表达式的编译或解释执行。

    算符间的优先关系

    算法描述

    //表达式求值
    OpendType EvaluateExpression( ){
    	InitStack( OPTR );
    	Push( OPTR, ‘#’ );
    	InitStack( OPND );
    	c = getchar( );
    	while(!(c == ‘#’ && GetTop( OPTR ) == ‘#’) ){
    		if(!In(c,OP)) // c是运算符?,OP是运算符集合
    		{
    			Push( OPND, c);
    			c = getchar( );
    		}
    		else
    		{
    		switch( Precede ( GetTop( OPTR), c ))
    		{
    			case ‘<’ :     //栈顶元素优先权低
    				Push( OPTR, c );
    				c = getchar( );
    				break;
    			case ‘=‘ :	// c为’)’
    				Pop( OPTR, x );
    				c = getchar( );
    				break;
    			case ‘>’:  //退栈并将运算结果入栈
    				Pop( OPTR, t );
    				Pop( OPND, b );
    				Pop( OPND, a );
    				Push( OPND, Operate( a, t, b ));
    				break;
    		}// switch
    	}// while
    	return  GetTop( OPND );
    }// EvaluateExpression
    

    过程的嵌套调用

    ……

    汉诺塔问题

    ……

    队列

    定义

    一种先进先出的线形表。只允许在表一端插入,在另一端删除。

    • 概念
      队尾rear:插入端,线性表的表尾。
      队头front:删除端,线性表的表头。
      FIFO(First In First Out)

    ADT

    ADT Queue
    {
    数据对象:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0}
    数据关系:R={<ai-1,ai>|ai,ai-1∈D,i=2,…,n}
    		          约定:a1为队列头,an为队列尾
    基本操作:
    	InitQueue( &Q );	// 初始化空队列
    	DestroyQueue( &Q );	// 销毁队列
    	ClearQueue( &Q );	// 清空队列
    	QueueEmpty( Q );	// 队列空?
    	QueueLength( Q );	// 队列长度
    	GetHead( Q, &e );	// 取对头元素
    	EnQueue( &Q, e );	// 入队列
    	DeQueue( &Q, &e );	// 出队列
    	QueueTraverse( Q, visit( ));	// 遍历
    }//ADT Queue;
    

    双端队列简介

    可以从两端进行插入或者删除操作的队列。

    队列的表示和实现

    两种

    • 链式存储结构__链队列;
    • 顺序存储结构__循环队列;

    链队列

    类型说明

    typedef struct Qnode   {
    	QElemType data;
    	struct QNode *next;
    }QNode,*QueuePtr;		// 结点
    typedef struct{
    	QueuePtr fornt;	// 队头指针
    	QueuePtr rear;	// 队尾指针
    }LinkQueue;
    

    结点定义

    typedef struct Qnode{
    	QElemType data;
    	struct QNode *Next;
    }QNode
    


    链队列示意图

    链队列的基本操作

    初始化
    //  初始化一个空队列
    Status InitQueue( LinkQueue &Q )
    {
    	Q.Front=Q.Rear=(QueuePtr)malloc(sizeof(QNode));
    	if(!Q.front)
    	{
    		return OVERFLOW;
    	}
    	Q.front->Next=NULL;
    }//InitQueue
    
    销毁
    // 销毁队列
    Status DestroyQueue(LinkQueue &Q){
    	while(Q.Front){  
            QRear = Q.Front ->next;
            free( Q.Front );   
            Q.Front= QRear 
        }
    	return OK; 
    }// DestroyQueue
    
    入队
    // 入队列
    Stauts EnQueue( LinkQueue &Q, QEmemType e )
    {
        p=(QueuePtr)malloc(sizeof(QNode));
    	if( !p){return OVERFLOW;  }//存储分配失败
    	p->data = e;  p->next = null ;
    	Q.Rear->next = p;
    	Q.Rear=p;
    	return OK;
    }
    
    出队
    // 出队列 3-3-12-1.swf
    Status DeQueue( LinkQueue &Q, QElemType &e )
    {
    	if(Q.Front==Q.rear)	
    		return ERROR;
    	p = Q.Front->Next ;//p指向队头
    	e = p->data;//取队头元素值(可以直接用e完成对队头取值)
        Q.Front->next = p->next;//头结点指向原队头的下一个节点
        if(Q.rear==p) //尾头同指向,队列就空了,释放队列
        	Q.rear=Q.front; 
    	free( p );
    	return OK;
    }// Dequeue
    
    

    循环队列

    基本思想:把队列设想成环形,让sq[0]接在sq[M-1]之后,若rear+1==M,则令rear=0;
    》实现:利用“模”运算
    》入队: rear=(rear+1)%M; sq[rear]=x;
    》出队: front=(front+1)%M; x=sq[front];
    》队满、队空判定条件

    • 会存在判断队空和队满时,条件均为front==rear的情况
    • 解决方案:少用一个元素空间
      队空:front= =rear
      队满:(rear+1)%M= =front
      约定以“队列头指针在队列尾指针的下一位置上”作为队列呈“”状态的标志,在同一位置则是空。

    循环队列的基本操作

    基本模块说明

    #define MAXQSIZE 100	//队列最大长度
    typedef struct
    {
    	QElemType *Base; // pBase指向数组名(通常静态队列都使用循环队列)
    	int Front;		// 头指针,数组下标,此处规定从零开始
    	int Rear;		// 尾指针
    }SqQueue;
    
    初始化
    Status InitQueue( SqQueue &Q ){
    	Q.Base = (QElemTYpe *)malloc( MAXQSIZE * sizeof( QElemType ));
    	if( !Q.Base ){
    		return OVERFLOW;}
    	Q.Front = Q.Rear = 0;
    	return OK;
    }// InitQueue
    
    队列长度
    int QueueLength( SqQueue Q )
    {
         return Q.Rear – Q.Front  + MAXQSIZE) % MAXQSIZE;
    }
    
    3 入队列
    Status EnQueue( SqQueue &Q, QElemType e ){
    	 if( (Q.Rear + 1)%MAXQSIZE == Q.Front ){
    		return ERROR;}                    // 队列满?
    	Q.Base[Q.Rear] = e;
    	Q.Rear = ( Q.Rear + 1 ) % MAXQSIZE;
    }// EnQueue
    
    出队列
    Status DeQueue( SqQueue &Q, QElemType &e ){
    	if( Q.Front == Q.Rear ){
    		return ERROR;}                    // 对列空?
    	e = Q.Base[Q.Front];
    	Q.Front = (Q.Front + 1) % MAXQSIZE;
    	return OK;
    }// DeQueue;
    

    队列应用举例

    由事件驱动的程序

    银行业务模拟系统

    • 顾客到达银行
      选择一个人数最少的柜台排队,如果该柜台没有人,则直接开始办理业务,并准备该顾客的业务完成事件。
      如果未到下班时间,则准备下一个顾客到达银行的事件。
    • 顾客业务完成
      顾客完成业务离开,下一个顾客开始办理业务,并准备该队列中下一个顾客的业务完成事件。

    初始化事件队列;
    起始事件(第一个顾客到达事件)入队;
    循环,直至事件队列空
    事件出队;
    若为到达事件,则
    选择人数最少的柜台排队;
    若该柜台无人排队,则业务完成事件入队;
    若未到下班时间,则下一个顾客到达事件入队;
    若为业务完成事件,则
    顾客离开;
    若该柜台还有人排队,则准备下一个人的业务完成事件;

  • 相关阅读:
    php hash_hmac HMAC_SHA1 加密
    文件上传最好用绝对路径
    composer 安装插件
    php 账单生成
    php 求当前日期到下一年的实际天数
    php 求两个日期之间相差的天数
    PHP闭包 function() use()
    php 验证参数为0 但不为空
    php 原生mysqli
    前端框架选择
  • 原文地址:https://www.cnblogs.com/nightland/p/13504413.html
Copyright © 2020-2023  润新知