HOME 首页
SERVICE 服务产品
XINMEITI 新媒体代运营
CASE 服务案例
NEWS 热点资讯
ABOUT 关于我们
CONTACT 联系我们
创意岭
让品牌有温度、有情感
专注品牌策划15年

    栈的实际应用(栈的实际应用举例)

    发布时间:2023-03-06 14:31:45     稿源: 创意岭    阅读: 905        问大家

    大家好!今天让创意岭的小编来大家介绍下关于栈的实际应用的问题,以下是小编对此问题的归纳整理,让我们一起来看看吧。

    创意岭作为行业内优秀的企业,服务客户遍布全球各地,相关业务请拨打电话:175-8598-2043,或添加微信:1454722008

    本文目录:

    栈的实际应用(栈的实际应用举例)

    一、堆栈有什么作用吗,请举几个具体的例子

    堆栈应用非常广的

    栈LIFO(后进先出)

    1、洗盘子。用过的盘子一个一个叠放,那么最上面的盘子先洗,然后是下面的。

    2、递归函数返回地址。程序先执行的函数地址扔到最底下,直到递送到有明确返回值函数地址

    后,在归回上一层处理它,直到最底部函数都处理完。

    二、在什么情况下可以用栈来存储数据?

    堆栈的特点是先进后出,速度快!在单片机设计中主要用于保留现场和恢复现场。在函数的跳转和中断中,堆栈的优点表现得淋漓尽致。

    下面是关于堆栈的一些详细讲述:

    堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。

    要点:

    堆:顺序随意

    栈:后进先出(Last-In/First-Out)

    编辑本段堆和栈的区别

    一、预备知识—程序的内存分配

    一个由c/C++编译的程序占用的内存分为以下几个部分

    1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

    2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

    3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放。

    4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放 。

    5、程序代码区—存放函数体的二进制代码。

    二、例子程序

    这是一个前辈写的,非常详细

    //main.cpp

    int a = 0; 全局初始化区

    char *p1; 全局未初始化区

    main()

    {

    int b; 栈

    char s[] = "abc"; 栈

    char *p2; 栈

    char *p3 = "123456"; 123456\0在常量区,p3在栈上。

    static int c =0; 全局(静态)初始化区

    p1 = (char *)malloc(10);

    p2 = (char *)malloc(20);

    }

    分配得来得10和20字节的区域就在堆区。

    strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。

    编辑本段堆和栈的理论知识

    1.申请方式

    stack:

    由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间

    heap:

    需要程序员自己申请,并指明大小,在c中malloc函数

    如p1 = (char *)malloc(10);

    在C++中用new运算符

    如p2 = new char[20];//(char *)malloc(10);

    但是注意p1、p2本身是在栈中的。

    2.申请后系统的响应

    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

    堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

    3.申请大小的限制

    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

    4.申请效率的比较

    栈由系统自动分配,速度较快。但程序员是无法控制的。

    堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.

    另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈,而是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活

    5.堆和栈中的存储内容

    栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

    当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

    6.存取效率的比较

    char s1[] = "aaaaaaaaaaaaaaa";

    char *s2 = "bbbbbbbbbbbbbbbbb";

    aaaaaaaaaaa是在运行时刻赋值的;

    而bbbbbbbbbbb是在编译时就确定的;

    但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。

    比如:

    #include

    void main()

    {

    char a = 1;

    char c[] = "1234567890";

    char *p ="1234567890";

    a = c[1];

    a = p[1];

    return;

    }

    对应的汇编代码

    10: a = c[1];

    00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]

    0040106A 88 4D FC mov byte ptr [ebp-4],cl

    11: a = p[1];

    0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]

    00401070 8A 42 01 mov al,byte ptr [edx+1]

    00401073 88 45 FC mov byte ptr [ebp-4],al

    第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。

    7.小结:

    堆和栈的区别可以用如下的比喻来看出:

    使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。

    使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

    编辑本段堆和栈的区别主要分:

    操作系统方面的堆和栈,如上面说的那些,不多说了。

    还有就是数据结构方面的堆和栈,这些都是不同的概念。这里的堆实际上指的就是(满足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权;栈实际上就是满足先进后出的性质的数学或数据结构。

    虽然堆栈,堆栈的说法是连起来叫,但是他们还是有很大区别的,连着叫只是由于历史的原因。

    三、求救:栈和队列在程序设计中的作用

    栈和队列是两种特殊的线性表,它们的逻辑结构和线性表相同,只是其运算规则较线性表有更多的限制,

    故又称它们为运算受限的线性表。栈和队列被广泛应用于各种程序设计中。

    栈的定义及基本运算

    1、栈的定义

    栈(Stack)是限制仅在表的一端进行插入和删除运算的线性表。

    (1)通常称插入、删除的这一端为栈顶(Top),另一端称为栈底(Bottom)。

    (2)当表中没有元素时称为空栈。

    (3)栈为后进先出(Last In First Out)的线性表,简称为LIFO 表。

    栈的修改是按后进先出的原则进行。每次删除(退栈)的总是当前栈中"最新"的元素,即最后插入

    (进栈)的元素,而最先插入的是被放在栈的底部,要到最后才能删除。

    【示例】元素是以a1,a2,…,an 的顺序进栈,退栈的次序却是an,an-1,…,a1。

    2、栈的基本运算

    (1)InitStack(S)

    构造一个空栈S。

    (2)StackEmpty(S)

    判栈空。若S 为空栈,则返回TRUE,否则返回FALSE。

    (3)StackFull(S)

    判栈满。若S 为满栈,则返回TRUE,否则返回FALSE。

    注意:

    该运算只适用于栈的顺序存储结构。

    (4)Push(S,x)

    进栈。若栈S 不满,则将元素x 插入S 的栈顶。

    (5)Pop(S)

    退栈。若栈S 非空,则将S 的栈顶元素删去,并返回该元素。

    (6)StackTop(S)

    取栈顶元素。若栈S 非空,则返回栈顶元素,但不改变栈的状态。

    顺序栈

    栈的顺序存储结构简称为顺序栈,它是运算受限的顺序表。

    1、顺序栈的类型定义

    #define StackSize 100 //假定预分配的栈空间最多为100 个元素

    typedef char DataType;//假定栈元素的数据类型为字符

    typedef struct{

    DataType data[StackSize];

    int top;

    }SeqStack;

    注意:

    ①顺序栈中元素用向量存放

    ②栈底位置是固定不变的,可设置在向量两端的任意一个端点

    ③栈顶位置是随着进栈和退栈操作而变化的,用一个整型量top(通常称top 为栈顶指针)来指示

    当前栈顶位置

    2、顺序栈的基本操作

    前提条件:

    设S 是SeqStack 类型的指针变量。若栈底位置在向量的低端,即S->data[0]是栈底元素。

    (1) 进栈操作

    进栈时,需要将S->top 加1

    注意:

    ①S->top==StackSize-1 表示栈满

    ②"上溢"现象--当栈满时,再做进栈运算产生空间溢出的现象。

    上溢是一种出错状态,应设法避免。

    (2) 退栈操作

    退栈时,需将S->top 减1

    注意:

    ①S->top<0 表示空栈

    ②"下溢"现象——当栈空时,做退栈运算产生的溢出现象。

    下溢是正常现象,常用作程序控制转移的条件。

    顺序栈在进栈和退栈操作时的具体变化情况【参见动画演示】

    3、顺序栈的基本运算

    (1) 置栈空

    void InitStack(SeqStack *S)

    {//将顺序栈置空

    S->top=-1;

    }

    (2) 判栈空

    int StackEmpty(SeqStack *S)

    {

    return S->top==-1;

    }

    (3) 判栈满

    int StackFull(SeqStack *SS)

    {

    return S->top==StackSize-1;

    }

    (4) 进栈

    void Push(S,x)

    {

    if (StackFull(S))

    Error("Stack overflow"); //上溢,退出运行

    S->data[++S->top]=x;//栈顶指针加1 后将x 入栈

    }

    (5) 退栈

    DataType Pop(S)

    {

    if(StackEmpty(S))

    Error("Stack underflow"); //下溢,退出运行

    return S->data[S->top--];//栈顶元素返回后将栈顶指针减1

    }

    (6) 取栈顶元素

    DataType StackTop(S)

    {

    if(StackEmpty(S))

    Error("Stack is empty");

    return S->data[S->top];

    }

    4、两个栈共享同一存储空间

    当程序中同时使用两个栈时,可以将两个栈的栈底设在向量空间的两端,让两个栈各自向中间延伸。

    当一个栈里的元素较多,超过向量空间的一半时,只要另一个栈的元素不多,那么前者就可以占用后者的

    部分存储空间。

    只有当整个向量空间被两个栈占满(即两个栈顶相遇)时,才会发生上溢。因此,两个栈共享一个长

    度为m 的向量空间和两个栈分别占用两个长度为└ m/2┘和┌m/2┐的向量空间比较,前者发生上溢的概

    率比后者要小得多。

    链栈

    栈的链式存储结构称为链栈。

    1、链栈的类型定义

    链栈是没有附加头结点的运算受限的单链表。栈顶指针就是链表的头指针。

    链栈的类型说明如下:

    typedef struct stacknode{

    DataType data

    struct stacknode *next

    }StackNode;

    typedef struct{

    StackNode *top; //栈顶指针

    }LinkStack;

    注意:

    ①LinkStack 结构类型的定义是为了方便在函数体中修改top 指针本身

    ②若要记录栈中元素个数,可将元素个数属性放在LinkStack 类型中定义。

    2、链栈的基本运算

    (1) 置栈空

    Void InitStack(LinkStack *S)

    {

    S->top=NULL;

    }

    (2) 判栈空

    int StackEmpty(LinkStack *S)

    {

    return S->top==NULL;

    }

    (3) 进栈

    void Push(LinkStack *S,DataType x)

    {//将元素x 插入链栈头部

    StackNode *p=(StackNode *)malloc(sizeof(StackNode));

    p->data=x;

    p->next=S->top;//将新结点*p 插入链栈头部

    S->top=p;

    }

    (4) 退栈

    DataType Pop(LinkStack *S)

    {

    DataType x;

    StackNode *p=S->top;//保存栈顶指针

    if(StackEmpty(S))

    Error("Stack underflow."); //下溢

    x=p->data; //保存栈顶结点数据

    S->top=p->next; //将栈顶结点从链上摘下

    free(p);

    return x;

    }

    (5) 取栈顶元素

    DataType StackTop(LinkStack *S)

    {

    if(StackEmpty(S))

    Error("Stack is empty.")

    return S->top->data;

    }

    注意:

    链栈中的结点是动态分配的,所以可以不考虑上溢,无须定义StackFull 运算。

    --------------------------------------------------------------------------------------------

    -----------------

    队列的定义及基本运算

    1、定义

    队列(Queue)是只允许在一端进行插入,而在另一端进行删除的运算受限的线性表

    (1)允许删除的一端称为队头(Front)。

    (2)允许插入的一端称为队尾(Rear)。

    (3)当队列中没有元素时称为空队列。

    (4)队列亦称作先进先出(First In First Out)的线性表,简称为FIFO 表。

    队列的修改是依先进先出的原则进行的。新来的成员总是加入队尾(即不允许"加塞"),每次离开的

    成员总是队列头上的(不允许中途离队),即当前"最老的"成员离队。

    【例】在队列中依次加入元素a1,a2,… ,an 之后,a1 是队头元素,an 是队尾元素。退出队列的次序

    只能是a1,a2,… ,an。

    2、队列的基本逻辑运算

    (1)InitQueue(Q)

    置空队。构造一个空队列Q。

    (2)QueueEmpty(Q)

    判队空。若队列Q 为空,则返回真值,否则返回假值。

    (3) QueueFull(Q)

    判队满。若队列Q 为满,则返回真值,否则返回假值。

    注意:

    此操作只适用于队列的顺序存储结构。

    (4) EnQueue(Q,x)

    若队列Q 非满,则将元素x 插入Q 的队尾。此操作简称入队。

    (5) DeQueue(Q)

    若队列Q 非空,则删去Q 的队头元素,并返回该元素。此操作简称出队。

    (6) QueueFront(Q)

    若队列Q 非空,则返回队头元素,但不改变队列Q 的状态。

    顺序队列

    1、顺序队列

    (1)顺序队列的定义

    队列的顺序存储结构称为顺序队列,顺序队列实际上是运算受限的顺序表。

    (2) 顺序队列的表示

    ①和顺序表一样,顺序队列用一个向量空间来存放当前队列中的元素。

    ②由于队列的队头和队尾的位置是变化的,设置两个指针front 和rear 分别指示队头元素和队尾元素

    在向量空间中的位置,它们的初值在队列初始化时均应置为0。

    (3) 顺序队列的基本操作

    ①入队时:将新元素插入rear 所指的位置,然后将rear 加1。

    ②出队时:删去front 所指的元素,然后将front 加1 并返回被删元素。

    注意:

    ①当头尾指针相等时,队列为空。

    ②在非空队列里,队头指针始终指向队头元素,尾指针始终指向队尾元素的下一位置。

    顺序队列基本操作【参见动画演示】

    (4)顺序队列中的溢出现象

    ① "下溢"现象

    当队列为空时,做出队运算产生的溢出现象。“下溢”是正常现象,常用作程序控制转移的条件。

    ② "真上溢"现象

    当队列满时,做进栈运算产生空间溢出的现象。“真上溢”是一种出错状态,应设法避免。

    ③ "假上溢"现象

    由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。当队列中

    实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。

    该现象称为"假上溢"现象。

    【例】假设下述操作序列作用在初始为空的顺序队列上:

    EnQueue,DeQueue,EnQueue,DeQueue,…

    尽管在任何时刻,队列元素的个数均不超过1,但是只要该序列足够长,事先定义的向量空间无论多大

    均会产生指针越界错误。

    链队列

    1、链队列的定义

    队列的链式存储结构简称为链队列。它是限制仅在表头删除和表尾插入的单链表。

    2、链队列的结构类型说明

    注意:

    增加指向链表上的最后一个结点的尾指针,便于在表尾做插入操作。

    链队列示意图见上图,图中Q 为LinkQueue 型的指针。

    3、链队列的基本运算

    (1) 置空队

    void InitQueue(LinkQueue *Q)

    {

    Q->front=Q->rear=NULL;

    }

    (2) 判队空

    intQueueEmpty(LinkQueue *Q)

    {

    return Q->front==NULL&&Q->rear==Null;

    //实际上只须判断队头指针是否为空即可

    }

    (3) 入队

    void EnQueue(LinkQueue *Q,DataType x)

    {//将元素x 插入链队列尾部

    QueueNode *p=(QueueNode *)malloc(sizeof(QueueNode));//申请新结点

    p->data=x; p->next=NULL;

    if(QueueEmpty(Q))

    Q->front=Q->rear=p; //将x 插入空队列

    else { //x 插入非空队列的尾

    Q->rear->next=p; //*p 链到原队尾结点后

    Q->rear=p; //队尾指针指向新的尾

    }

    }

    (4) 出队

    DataType DeQueue (LinkQueue *Q)

    {

    DataType x;

    QueueNode *p;

    if(QueueEmpty(Q))

    Error("Queue underflow");//下溢

    p=Q->front; //指向对头结点

    x=p->data; //保存对头结点的数据

    Q->front=p->next; //将对头结点从链上摘下

    if(Q->rear==p)//原队中只有一个结点,删去后队列变空,此时队头指针已为空

    Q->rear=NULL;

    free(p); //释放被删队头结点

    return x; //返回原队头数据

    }

    (5) 取队头元素

    DataType QueueFront(LinkQueue *Q)

    {

    if(QueueEmpty(Q))

    Error("Queue if empty.");

    return Q->front->data;

    }

    注意:

    ①和链栈类似,无须考虑判队满的运算及上溢。

    ②在出队算法中,一般只需修改队头指针。但当原队中只有一个结点时,该结点既是队头也是队尾,

    故删去此结点时亦需修改尾指针,且删去此结点后队列变空。

    ③以上讨论的是无头结点链队列的基本运算。和单链表类似,为了简化边界条件的处理,在队头结点

    前也可附加一个头结点,增加头结点的链队列的基本运算【参见练习】

    循环队列

    为充分利用向量空间,克服"假上溢"现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这

    种向量为循环向量。存储在其中的队列称为循环队列(Circular Queue)。

    (1) 循环队列的基本操作

    循环队列中进行出队、入队操作时,头尾指针仍要加1,朝前移动。只不过当头尾指针指向向量上界

    (QueueSize-1)时,其加1 操作的结果是指向向量的下界0。这种循环意义下的加1 操作可以描述为:

    ① 方法一:

    if(i+1==QueueSize) //i 表示front 或rear

    i=0;

    else

    i++;

    ② 方法二--利用"模运算"

    i=(i+1)%QueueSize;

    (2) 循环队列边界条件处理

    循环队列中,由于入队时尾指针向前追赶头指针;出队时头指针向前追赶尾指针,造成队空和队满时

    头尾指针均相等。因此,无法通过条件front==rear 来判别队列是"空"还是"满"。【参见动画演示】

    解决这个问题的方法至少有三种:

    ① 另设一布尔变量以区别队列的空和满;

    ② 少用一个元素的空间。约定入队前,测试尾指针在循环意义下加1 后是否等于头指针,若相等则认

    为队满(注意:rear 所指的单元始终为空);

    ③使用一个计数器记录队列中元素的总数(即队列长度)。

    (3) 循环队列的类型定义

    #define Queur Size 100 //应根据具体情况定义该值

    typedef char Queue DataType; //DataType 的类型依赖于具体的应用

    typedef Sturet{ //头指针,队非空时指向队头元素

    int front; //尾指针,队非空时指向队尾元素的下一位置

    int rear; //计数器,记录队中元素总数

    DataType data[QueueSize]

    }CirQueue;

    (4) 循环队列的基本运算

    用第三种方法,循环队列的六种基本运算:

    ① 置队空

    void InitQueue(CirQueue *Q)

    {

    Q->front=Q->rear=0;

    Q->count=0; //计数器置0

    }

    ② 判队空

    int QueueEmpty(CirQueue *Q)

    {

    return Q->count==0; //队列无元素为空

    }

    ③ 判队满

    int QueueFull(CirQueue *Q)

    {

    return Q->count==QueueSize; //队中元素个数等于QueueSize 时队满

    }

    ④ 入队

    void EnQueue(CirQueuq *Q,DataType x)

    {

    if(QueueFull((Q))

    Error("Queue overflow"); //队满上溢

    Q->count ++; //队列元素个数加1

    Q->data[Q->rear]=x; //新元素插入队尾

    Q->rear=(Q->rear+1)%QueueSize; //循环意义下将尾指针加1

    ⑤ 出队

    DataType DeQueue(CirQueue *Q)

    {

    DataType temp;

    if(QueueEmpty((Q))

    Error("Queue underflow"); //队空下溢

    temp=Q->data[Q->front];

    Q->count--; //队列元素个数减1

    Q->front=(Q->front+1)&QueueSize; //循环意义下的头指针加1

    return temp;

    }

    ⑥取队头元素

    DataType QueueFront(CirQueue *Q)

    {

    if(QueueEmpty(Q))

    Error("Queue if empty.");

    return Q->data[Q->front];

    }

    四、c语言 栈是怎么应用的 哪位大神能说的好理解点

    栈分为出栈和入栈,入栈是为了保护你刚刚正在进行的程序,把它放进指定的空闲位置,出栈是你执行完另一件事后把之前保存入栈的东西在从存放的地方拿出来。这是为了保护数据,防止丢失。!

    比如你正看着书呢,小伙伴叫你去玩,你就加个书签在读过的那一页放进书橱,玩回来了拿出书翻到那一页接着读。而你的书签就相当于起到保护作用,回来翻到那一页就相当于取出之前存入栈中的内容。

    以上就是关于栈的实际应用相关问题的回答。希望能帮到你,如有更多相关问题,您也可以联系我们的客服进行咨询,客服也会为您讲解更多精彩的知识和内容。


    推荐阅读:

    栈的实际应用(栈的实际应用举例)

    德阳花园景观设计企业(德阳花园景观设计企业名单)

    在线营销的主要内容(在线营销的主要内容是什么)