限定性线性表——栈和队列

限制线性表插入和删除等运算的位置(只允许在端点位置操作)

栈是限定仅在表的一端进行插入和删除的线性表

队列是限定仅在表的一端进行插入、在表的另一端进行删除的线性表

栈和队列是两种常用的抽象数据类型、都属于线性结构

栈:

栈可以改变数据的处理顺序      栈是函数过程嵌套调用递归调用管理的有效手段

定义:          只能在表的一端(栈顶)进行插入和删除运算的线性表

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

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

运算规则:     只能在栈顶运算、且访问结点时依照后进先出(LIFO)或先进后出(FILO)的原则

实现方法:      编写入栈出栈函数(关键)、具体实现顺序栈与链栈不同、基本操作:入栈、出栈、读取栈顶元素建栈判栈满判栈空

顺序栈:利用一组连续的存储单元依次存放自栈底到栈顶的数据元素,由于栈操作的特殊性、必须设一个位置指针top栈顶指针)来动态指示栈顶元素在顺序栈中的位置

顺序栈的基本操作实现:

1、初始化

2、判栈空

3、判栈满

4、进栈

5、出栈

6、读栈顶元素

7、两栈共享的数据结构

1、初始化

void InitStack(SeqStack*S)
{
    S->top=-1;//构造一个空栈
}



#define Stack_Size 50
typedef struct
{
    StackElementType elem[Stack_Size];
    int top;
}SeqStack;

 2、判栈空

int IsEmpty(SeqStack *S)
{//判断栈S为空栈时返回值为真、反之为假
    return (S->top==-1?TURE:FALSE);
}

#define Stack_Size 40
typedef struct
{
    StackElementType elem[Stack_Size];
    int top;
}SeqStack;

 3、判栈满

int IsFull(SeqStack *S)
{//判断S为满时返回真、反之返回假
    return(S->top==Stack_Size-1?TURE:FALSE);
}

#define Stack_Size 30
typedef struct
{
    StackElementType elem[Stack_Size];
    int top;
}SeqStack;

4、 进栈

int Push(SeqStack *S,StackElementType x)
{
    if(S->top==Stack_Size-1)
        return(FALSE);//栈已满
    S->top++;
    S->elem[S->top]=x;
    return(TRUE);
}

#define Stack_Size 30
typedef struct
{
    StackElementType elem[Stack_Size];
    int top;
}SeqStack;

5、出栈

int Pop(SeqStack *S,StackElementType *x)
{//将栈S的栈顶元素弹出、放在x所指的存储空间中
    if(S->top==-1)//栈为空
            return(FALSE);
        else
        {
            *x=S->elem[S->top];
            S->top--;//修改栈顶指针
            return(TRUE);
        }
}

#define Stack_Size 30
typedef struct
{
    StackElementType elem[Stack_Size];
    int top;
}SeqStack;

6、读取栈顶元素

int GetTop(SeqStack S,StackElementType *x)
{//将栈顶S的元素弹出、放到X所指的存储空间中、但栈顶指针保持不变
    if(S->top==-1)//栈为空
        return(FALSE);
    else
    {
        *x=S->elem[S->top];
        return(TRUE);
    }
}

#define Stack_Size 30
typedef struct
{
    StackElementType elem[Stack_Size];
    int top;
}SeqStack;

 链栈:

链表作为存储结构实现的栈。为了方便、采用带头结点的单链表实现栈。因栈的插入和删除操作仅限制在表头位置进行、所以链表的表头指针就作为栈顶指针

top->next=NULL  代表空栈             链栈在使用完毕后、应释放其空间

链栈数据结构定义:

typedef struct node
{
    StackElementType data;
    struct node *next;
}LinkStackNode;
typedef LinkStackNode *LinkStact
//指向结点的指针

 进栈:

typedef struct node
{
    StackElementType data;
    struct node *next;
}LinkStackNode,*LinkStack;

int Push(LinkStack top,StackElementType x)
{//将数据元素x压入栈top中
    LinkStackNode *temp;
    temp=(LinkStackNode *)malloc(sizeof(LinkStackNode));
    if(temp==NULL)    return(FALSE);/*申请空间失败*/
    temp->data=x;
    temp->next=top->next;
    top->next=temp;/*修改当前栈顶指针*/
    return(TRUE);


}

 出栈:

typedef struct node
{
    StackElementType data;
    struct node *next;
}LinkStackNode,*LinkStack;


int Pop(LinkStack top, StackElementType *x)
{/*将栈top的栈顶元素弹出,放到x所指的存储空间*/
    LinkStackNode * temp; temp=top->next;
    if(temp==NULL)/*栈为空*
        return(FALSE);
    top->next=temp->next;
    *x=temp->data;
    free(temp);/*释放存储空间*/
    return(TRUE);
}


 

栈、队列与一般线性表的区别:

栈、队列是一种特殊(操作受限)的线性表

区别:仅在于运算规则不同

一般线性表:

逻辑结构:一对一

存储结构:顺序表、顺序存取

运算规则:随机、顺序存取

栈:

逻辑结构:一对一

存储结构:顺序栈、链栈

运算规则:后进先出

 队列:

逻辑结构:一对一

存储结构:顺序队、链队

运算规则:先进先出

检验括号是否匹配可用栈来实现

 

参数传递:

函数调用时传递给形参表的实参必须与形参在类型、个数、顺序上保持一致

值传递:单向传递、实参的值不会被改变(改变的是副本的值)、浪费内存空间          参数为整型、实型、字符型等

地址传递:双向传递、可以改变传递过来的参数内容、即形参的改变会影响实参、通过指针类型将结果带进带出、只是传递的地址、节省内存空间                             参数为指针变量     参数为数组名

几点说明
1、传递一般变量给函数时.形参的变化不会影响实参;传递指针变量给函数时,形参变化实参也发生变化。
2、指针变量作参数,在内存中并没有产生实参的副本,它直接对实参操作;而一般变量作参数,形参与实参就占用不同的存储单元,所以形参变量的值是实参变量的副本。因此,当参数传递的数据量较大时,用指针变量比用一般变量传递参数的时间和空间效率都好。
3、需要注意的是:用指针变量作参数时,在主调函数的调用点处、必须用变量的地址作为实参。在被调函数中需要重复使用*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;

算法表达式求值:

表达式:操作数(operand)、运算符(operator)、界限符(deliniter)
 

运算符界限符统称为运算符(操作符)

操作数:整型常数

运算符:加减乘除

界限符:表达式起始符“#”、表达式结束符“#”

表达式格式:#1+2*3-4/2#

算法实现:

两个工作栈:

操作符栈:存放操作符

操作数栈:存放操作数和中间结果

算法基本过程:

初始化操作数栈操作符栈,并将表达式起始符#压入操作符栈;
依次读入表达式中的每个字符,若是操作数则直接进入操作数栈,若是操作符,则与操作符栈栈顶操作符进行优先权比较,并做如下处理:

(⑴)若刚读入的操作符的优先级高于栈顶操作符,则让刚读入的操作符进入操作符栈;
(2)若刚读入的操作符的优先级低于或等于栈顶操作符,则将栈顶操作符退栈,送入0,同时将操作数栈退栈两次,得到两个操作数a、b,对a、b进行0运算后,将运算结果作为中间结果推入操作数栈;

当两个#相遇时,整个表达式求值完毕

知识回顾:

函数的调用:

当一个函数在运行期间调用另一个函数时、在运行该被调函数之前、需要完成以下三项任务

1、将所有实参、返回地址等信息传递给被调用函数保存

2、为被调用函数的局部变量分配存储区

3、将控制转移到被调用函数的入口

从被调用函数返回调用函数之前、应完成以下三项任务

1、保存返回计算结果(用函数名、引用参数)

2、释放被调用函数的数据区(局部量)

3、依照被调函数中保存的返回地址将控制转移到调用函数

 

多个函数嵌套调用的规则是:后调用先返回——栈
上述函数之间的信息传递和控制转移必须通过栈来实现。系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为它在栈顶分配一个存储区,而每当从一个函数退出时,就释放它的存储区。此时的内存管理实行“栈式管理”,当前正在运行的函数的数据区必在栈顶。
函数的调用是通过栈来实现的,满足栈的“后进先出的特点”


每个函数数据区含有:参数、局部变量、返回地址等信息

递归的基本概念:

定义:在定义自身的同时、又出现了对自身的调用

直接递归函数:函数在其定义体内直接被调用

间接递归函数:函数经过一系列的中间调用语句、通过其他函数间接调用自己

具有递归特性的例子:

递归定义的数学函数

具有递归特点的数据结构

可递归求解问题

具有递归特性的数据结构:二叉树、广义表

 

优点:递归算法是一种分而治之、把复杂问题分解为简单问题的求解方法。递归程序结构清晰、程序可读性强,且其正确性易于证明,给用户编程带来很大方便。适合解决比较精细的问题


当问题满足如下条件时,可设计递归算法:
1、原向题可以层层分解为类似的子问题,子问题规模比原问题小;

2、规模最小的子问题具有直接解。


设计递归算法的方法是;
1.寻找分解方法,将原问题分解为类似的子问题求解;

2.设升递归出口,按最小规模子问题确定递归终止条件;


缺点:每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,恢复状态信息。时间开销大。递归算法效率较低(时间和空间两方面),若处理数据规模较大时,要多加权衡。


                                                       递归的消除:递归->非递归
 

1单向递归→循环结构
单向递归是指递归函数虽然有一处以上的递归调用语句,但各次递归调用语句的参数只和主调函数

关,相互之间参数无关,并且这些递归调用语句处于算法的最后
                                                      例:计算斐波那契数列的递归算法


2尾递归→循环结构
尾递归是指递归调用语句只有一个,而且是处于算法的最后,尾递归是单向递归的特例。
                                            例:求n!的递归算法与非递归算法
 

 

 

 

 

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值