数据结构(三) 栈 链队列ADT List基本操作函数与应用

一、栈

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

栈最直观形象的模型为弹匣“压弹”、“退弹” ,栈是一种遵循“先进后出”的出栈原则

一、栈操作数据元素的方法

  1. 数据元素用栈的数据结构存储起来,称为“入栈”,也叫“压栈”。
  2. 数据元素由于某种原因需要从栈结构中提取出来,称为“出栈”,也叫“弹栈”

二、 栈的两种表示方式

因为栈也属于线性表,故其也有链式结构,称为“链栈”,本篇只针对顺序栈

三、栈的“上溢”和“下溢”

上溢”:在栈已经存满数据元素的情况下,如果继续向栈内存入数据,栈存储就会出错。
“下溢”:在栈内为空的状态下,如果对栈继续进行取数据的操作,就会出错。

四、顺序栈的表达

//栈 
//@date 8.18
//----------ADT Stack 的表示与实现------------
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
#define StackInitSize 100   //初次空间分配大小
#define StackIncrement 10  //空间增量大小

typedef struct {
    int *base;//栈底指针
    int *top;//栈顶指针
    int stacksize;//stack指示栈的当前可使用的最大容量
}SqStack;

//构造一个空栈
void InitStack(SqStack &S)//S为栈
{
    S.base = (int*)malloc(StackInitSize*sizeof(int));//为栈开辟空间.且S.base得到的是开辟空间的首地址
    if(!S.base)//S.base指针始终指向栈底,若哦S.base = NULL则说明栈不存在
    {
        cout<<"error-1,内存空间开辟失败"<<endl;
        exit;
    }
    S.top = S.base;//空栈,top为栈顶指针,其初值指向栈底,当插入新的栈顶元素时,top+1 
    /*
    if(!S.base)   
    {
        cout<<"duigqwigvwuiqeguiwegv"<<endl;
    } 
    */                       
    S.stacksize = StackInitSize;//栈的最大容量为定义的StackInitSize
    if(S.top == S.base)
    {
        cout<<"空栈构建成功"<<endl;
    }
    else
    cout<<"空栈构建失败"<<endl;
}

//若栈存在,则销毁栈
void DestroyStack(SqStack &S)
{
    if(S.base != NULL) free(S.base);
    else cout<<"栈不存在,不需要进行销毁"<<endl;
    if(S.base == NULL) cout<<"栈销毁成功"<<endl;
    else cout<<"栈销毁失败"<<endl;
}

//判断栈是否为空,若栈不空则用e返回S的栈顶元素
void GetTop(SqStack S,int e)
{
/*
  注意栈为空与栈不存在不同,前者只是未赋值
    栈为空的判断方式   S.base == S.top
    栈不存在的判断方式  S.base == NULL
*/
    if(S.base == S.top)
    {
        cout<<"栈为空"<<endl;
        exit;
    }
    cout<<"栈非空"<<endl;
//在非空栈中的栈顶始终在栈顶元素的下一个位置上
    e = *(S.top-1);
}

//把栈置为空栈
void ClearStack(SqStack &S)
{
    if(S.base != S.top)
    {
        cout<<"栈为非空"<<endl;
        S.base = S.top;
    }
    if(S.base == S.top) cout<<"栈置空成功"<<endl;
    else cout<<"置空失败"<<endl;
}

//若栈非空则返回真,否则则返回假
bool StackEmpty(SqStack S)
{
    if(S.base == S.top) 
    {
        cout<<"栈为空"<<endl;
        return true;
    }
    else return false;
}

//返回S的元素个数,即栈的长度
int StackLength(SqStack S)
{
    return S.top - S.base;
}

//插入e元素为新的栈顶元素,压栈操作,即对栈进行赋值操作
void Push(SqStack &S,int e)
{
//当栈满时候
    if(S.top-S.base >= S.stacksize)
    {
    /*   
     开辟空间首先释放原内存空间S.base,再将内存空间在原基础上增加StackIncrement的4倍
     sizeof(int*) 64,32位均为4位
    */
       S.base = (int*)realloc(S.base,(S.stacksize+StackIncrement)*sizeof(int*));
       if(S.base == NULL) 
       {
        cout<<"内存空间开辟失败"<<endl;
        exit;
       }
    /*
    如果在追加空间的时候改变了指针S.base的值,那么必须保证S.top也作响应的改变
    */
       S.top = S.base+S.stacksize;//在重新开辟空间时,与顺序表类似要重新定位指针的位置,即S.top要上移
       S.stacksize+=StackIncrement;//需要注意的是,在每次重新分配内存空间时,其指针与数据的内存结构都要发生改变
    }
    cout<<"栈顶e的元素值为"<<endl;
    scanf("%d",&e);
    //cin>>e;
    *S.top++ = e;//先执行*S.top=e,再S.top++,即指针上移

}

//获取栈顶元素
//若栈非空,则用e返回其值
void Pop(SqStack &S,int e)
{
    if(S.base == S.top)
    {
        cout<<"栈空,无法返回其值"<<endl;
        exit;
    }
    e = *--S.top;//运算符优先级为++(--) 然后是* 最后是=,所以--S.top先运算, 其次是*(--S.top),最后是e=*(--S.top)。
    cout<<"栈顶元素e值为"<<e<<endl;
}

//对栈的函数调用测试
int main()
{
    SqStack S;
    int e;
    //free(S.base);
    //构建个新的空栈
    InitStack(S);
    //判断栈是否为空
    StackEmpty(S);
    //返回栈的长度
    cout<<"当前栈长度为"<<":"<<StackLength(S)<<endl;
    Push(S,e);
    //返回赋值后的栈长度
    cout<<"当前栈长度为"<<":"<<StackLength(S)<<endl;
    Pop(S,e);
    ClearStack(S);
    StackEmpty(S);

    




}


二、栈的应用 进制转换 迷宫求解 表达式求值

一、进制转换

这个比较简单,取余数发现规律即可,此段代码是引入了c++ STL库,相同的编写完操作函数后,引入头文件,也可以达到相同的目的

//stcak库进行进制转换
#include <iostream>
#include <stdio.h>
#include <vector>
#include <algorithm>
#include <stack>
using namespace std;
int main()
{
    int N;
    stack<int>S;
    scanf("%d",&N);
    while(N)
    {
        S.push(N%8);
        N = N/8;
    }
    while(!S.empty())
    {
        printf("%d",S.top());
        S.pop();
    }
    return 0;
}

二、迷宫求解

这个问题起初我很困惑,不理解为什么迷宫求解要利用栈,仔细想想,迷宫求解的目的就是利用“穷举法”在所有路径中正确走出迷宫后将正确的路径输出,这一点非常符合栈“先进后出”的特点。

编写此程序时应先构出流程图,这点是非常重要的

/*----------------主要思路----------------

首先需要确定的是循环主体为while,循环终止条件为栈不空,只要栈不空就一直执行,当栈为空时代表找到出口

1、初始化:创建结构体与贪吃蛇相同需要首先确定当前位置与入口位置

2、if(若可通)   do(将当前位置进行压栈,若为出口则结束,否则进行切换位置,将右邻的位置作为新的位置)

3、else({if(若栈不空且除东邻外还有其他位置未进行搜索):切换方向进行下一次寻找}  

         if(若栈不空但栈顶的四周不通):删去此时的栈顶位置,重新测试新的栈顶位置,直至找到可通的相邻块或出至栈空)

----------------主要思路----------------*/

1、构建保存的数据结构体

//栈中的数据元素
typedef struct{
    int x,y;
    int di;//di为方向变量
}Box;
//创建结构体栈变量,为保存坐标与方向做准备
stack<Box>S;
//初始化
Box temp;
temp = {1,1,-1};

2、构建方向变量 

构建方向变量的目的很明确,对其变量数值的改变从而达到改变方向的目的,继而完成对每个方向的“寻路”

typedef struct{
    int incX;
    int incY;
}Direction;
Direction direct[4];

/*----------方向参数---------*/
direct[0] = {0,1};//右
direct[1] = {1,0};//下
direct[2] = {0,-1};//左
direct[3] = {-1,0};//上
/*----------方向参数---------*/

3、构建试探通路坐标元素

其中x,y,di与line,col的关系为x为要压栈的坐标位置, 即x是可通坐标,而line可理解为道路是否可通的试探坐标,对其line,col的改变与迷宫坐标是否为0从而判断道路是否可通,可通则压入,不可通则改变方向

int line,col;

4、完整代码

//寻找路径
bool findPath(int maze[M+2][N+2],Direction direct[],stack<Box>&S)
{
    direct[0] = {0,1};//右
    direct[1] = {1,0};//下
    direct[2] = {0,-1};//左
    direct[3] = {-1,0};//上
    //Box为要入栈的数据分别为:当前坐标位置x,y  方向dir
    Box temp;
    //x,y,dir为迷宫格子当前处理的坐标与方向
    int x,y,di;
    //line,col为迷宫数组的下一单元坐标
    int line,col;
    //将起始位置设置为-1,则之后每当通过一个单元都将其赋值为-1,进行标记
    maze[1][1] = -1;
    //栈的初始化
    temp = {1,1,-1};
    //创建数据类型为结构体的栈
    stack<Box>S;
    S.push(temp);
    while(!S.empty())
    {
        S.pop();
        temp = S.top();
        //改变方向,查找通路,方向由direct[]数组值确定
        /*其中x,y,di与line,col的关系为x为要压栈的坐标位置,
            即x是可通坐标,而line可理解为道路是否可通的试探坐标,对其line,col
            的改变与迷宫坐标是否为0从而判断道路是否可通,可通则压入,不可通则
            改变方向,若div>4解释如下
        */
        x = temp.x;//将退栈后的位置赋给x,y,di即执行后退操作
        y = temp.y;
        di = temp.di+1;
        /*方向未查找完,此while循环目的在于查找四个方向,
           若有放向可通(迷宫坐标仅为0时可通,若为-1也不可通),
           当di>4时候,证明四个方向都已查找完且无通路则进行退栈,
           即退出第二个while循环,进入第一个循环进行退栈
           返回上一个位置进行查找,当有通路时候标记-1,赋值temp,压栈
        */
        while(di<4)
        {
        /*改变x,y的位置寻找可通路径,依次寻找,寻找方向为右(0)下(1)左(2)上(3)*/
            line = x+direct[di].incX;
            col = y+direct[di].incY;
            //若当前迷宫坐标为0时候,则可通,赋值temp压栈,记录位置与方向
            if(maze[line][col]==0)
            {
                temp = {x,y,di};
                S.push(temp);
                //将已通过的路径在迷宫上标记-1               
                x = line;
                y = col;
                maze[line][col] = -1;
                //---------end--------------
                //判断是否到达地图边界,即判断是否走出迷宫
                if(x==M&&y==N)
                {
                    return true;
                }else
                {
                      di = 0;
                    return false;
                }

            }else
            di++;

        }
    }  

}

 三、队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

1、队列(Queue)与栈一样,是一种线性存储结构,它具有如下特点:

1)队列中的数据元素遵循“先进先出”(First In First Out)的原则,简称FIFO结构;

(2)在队尾添加元素,在队头删除元素

2、队列的相关概念:

(1)队头与队尾: 允许元素插入的一端称为队尾,允许元素删除的一端称为队头;

(2)入队:队列的插入操作;

(3)出队:队列的删除操作。

3、队列的操作:

(1)入队: 通常命名为push()

(2)出队: 通常命名为pop()

(3)求队列中元素个数

(4)判断队列是否为空

(5)获取队首元素

4、队列的分类:

(1)基于数组的循环队列(循环队列)

(2)基于链表的队列(链队列)

本篇主要针对链队列进行说明

一、链队列结构的构建

​
typedef struct QNode//结点结构与链表类似此结构不影响链队列结构
{
    int data;
    struct QNode *next;//指针结点
}QNode,*QueuePtr;//数据域,指针域
typedef struct //队列的链表结构
{
    QueuePtr front;//链队列的头指针
    QueuePtr rear;//链队列的尾指针
}LinkQueue;

​

 二、空链队列

//构建空的链队列
bool INitQueue(LinkQueue &Q)
{
    //为头指针开辟空间大小为QueuePtr类型大小为QNode节点的一倍
    Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
    if(!Q.front)
    return false;
    Q.front = Q.rear->next = NULL;
    return true;
    //让头尾指针指向一个结点,并赋值为NULL
}

三、出队

注意出队只能是队列第一个而不能是中的某一个,思路也比较简单只需将头指针赋给头结点的下一个结点,从而达到“越过”的操作即达到了出队,这个方式与链表的插入思路类似,都是对指针指向的改变

//出队
/*注意出队只能是队列第一个而不能是中的某一个,思路也比较简单只需将头指针赋给头结点的下一个结点,
从而达到“越过”的操作即达到了出队
*/
int DeQueue(LinkQueue &Q,int e)
{
    QueuePtr p;
    if(Q.front == Q.rear)
    return false;
    else{
        //将头指针指向第一个结点,做好出队准备
        p = Q.front->next;
        e = p->data;
        /*--------core----------*/
        Q.front->next = p->next;//将节点的后继节点指针赋给头指针达到“越过”的目的
        /*--------core----------*/
        if(Q.rear==p)
        //若当此时队列只有一个元素,出队将恢复到空队列的状态,故执行Q.front = Q.rear;
        Q.front = Q.rear;
        free(p);
    }
    return e;
}

四、入队

与出队类似,入队只能是从尾部入队,即将新插入的结点作为尾结点,尾指针指向该结点,结点指针指向NULL

//入队,插入元素e为新的队尾元素
//Q->next实质与链表头结点一样,均指向第一个结点
bool EnQueue(LinkQueue &Q,int e)
{
    QueuePtr p = (QueuePtr)malloc(sizeof(QNode));
    if(!p)
    return false;
    else{
        p->data = e;
        p->next = NULL;//将p的指针部分赋为NULL,作为尾结点
        Q.front->next = p;//将第一个结点指向新插入的结点
        Q.rear = p;
    }
    return true;
}

五、完整代码

//链队列操作函数
//@date: 2022.9.21
//@special:队列添加一个头结点,并令队头指针指向头结点,队尾指针指向链表中最后一个结点
//判断空队列 Q.front->next = NULL
#include <iostream>
#include <stdio.h>
#include <vector>
#include <algorithm>
#include <stack>
#include <string>
using namespace std;
typedef struct QNode//结点结构与链表类似此结构不影响链队列结构
{
    int data;
    struct QNode *next;//指针结点
}QNode,*QueuePtr;//数据域,指针域
typedef struct //队列的链表结构
{
    QueuePtr front;//链队列的头指针
    QueuePtr rear;//链队列的尾指针
}LinkQueue;

//构建空的链队列
bool INitQueue(LinkQueue &Q)
{
    //为头指针开辟空间大小为QueuePtr类型大小为QNode节点的一倍
    Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
    if(!Q.front)
    return false;
    Q.front = Q.rear->next = NULL;
    return true;
    //让头尾指针指向一个结点,并赋值为NULL
}

//销毁链队列
bool DestroyQueue(LinkQueue &Q)
{
    //若链队列存在,front前,rear后
    while(Q.front)
    {
        //头指针依次指向每一个结点,释放其内存
        Q.front = Q.rear->next;
        free(Q.front);
        Q.front = Q.rear;
    }
    return true;
}

//将Q清空为空队列,销毁结点指针,但保留头尾指针
bool ClearQueue(LinkQueue &Q)
{
    QueuePtr q,p;
    if(Q.rear->next != NULL)
    cout<<"此为非空链队列"<<endl;
    //此时要对其队列结点进行改变
    while(!Q.front)
    {
        //结构结点指向头结点的下一个
        p = Q.front->next;
        Q.front->next = NULL;
        //p->data = NULL;
        /*--------对其带有数据结点进行的操作-----------*/
        /*此时要对带有数据的结点操作应该用结点数据类型定义变量
        此时变量类型与队列无关,与链表类似
        */
        while (p)
        {
            p = q->next;
            free(p);
            q = p;
        }
        /*--------对其带有数据结点进行的操作-----------*/
        Q.rear = Q.front;
    }
    return true;
}

//判断是否为空链队列
bool QueueEmpty(LinkQueue Q)
{
    if(Q.front->next = NULL)
        return true;
    else
        return false;
}

//返回队列的长度,即队列的长度
int QueueLength(LinkQueue Q)
{
    QueuePtr q,p;
    int Length = 0;
    while (!Q.front)
    {
        //初始化使得Q.front头指针指向下一结点为q,不断深入查找下一个结点直到为空,循环结束
        p = Q.front->next;
        while (q)
        {
            q = p->next;
            Length++;
        }
        
    }
    return Length;
}

//若队列不空则返回队头元素
bool GetHead(LinkQueue Q,int e)
{
    QueuePtr p;
    if(Q.front == Q.rear)
        return false;
    else{
        p = Q.front->next;
        e = p->data;
    }
   return e;
}

//入队,插入元素e为新的队尾元素
//Q->next实质与链表头结点一样,均指向第一个结点
bool EnQueue(LinkQueue &Q,int e)
{
    QueuePtr p = (QueuePtr)malloc(sizeof(QNode));
    if(!p)
    return false;
    else{
        p->data = e;
        p->next = NULL;//将p的指针部分赋为NULL,作为尾结点
        Q.front->next = p;//将第一个结点指向新插入的结点
        Q.rear = p;
    }
    return true;
}

//出队
/*注意出队只能是队列第一个而不能是中的某一个,思路也比较简单只需将头指针赋给头结点的下一个结点,
从而达到“越过”的操作即达到了出队
*/
int DeQueue(LinkQueue &Q,int e)
{
    QueuePtr p;
    if(Q.front == Q.rear)
    return false;
    else{
        //将头指针指向第一个结点,做好出队准备
        p = Q.front->next;
        e = p->data;
        /*--------core----------*/
        Q.front->next = p->next;//将节点的后继节点指针赋给头指针达到“越过”的目的
        /*--------core----------*/
        if(Q.rear==p)
        //若当此时队列只有一个元素,出队将恢复到空队列的状态,故执行Q.front = Q.rear;
        Q.front = Q.rear;
        free(p);
    }
    return e;
}
int main()
{
    return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值