栈知识介绍、存储方式介绍及栈的应用

1.知识体系

栈知识

2.栈的基本概念

2.1 基本概念

栈是一种只能在一端进行插入或删除操作的线性表。

  • 栈顶:允许进行插入或删除的一端
  • 栈底:固定的,不允许插入或删除的一端
  • 空栈:不含任何元素的空表
  • 满栈: 表空间满,无法存储新元素。
  • 栈的特点: 先进后出(FILO)

2.2 数学性质

n个不同元素进栈,出栈元素不同排列的个数为
1 n + 1 C ( n 2 n ) \frac{1}{n+1}C{n \choose 2n} n+11C(2nn)
以上数字成为卡特兰数。

3.顺序栈

顺序栈,按其存储方式,主要分为:静态顺序栈、动态顺序栈。
通常用b(bottom)表示栈底指针,用t(top)指示当前栈顶位置,栈可以存放n个元素,在进行顺序栈的初始化时,可以选取以下几种:

序号 初始化 栈空条件 栈顶指针位置 栈满条件 入栈 出栈 栈元素数量
方法1 b=t=-1 b==t 指向栈顶元素 t-b>=n t++;放入元素 弹出元素;t- - t-b
方法2(常用) b=t=0 b==t 栈顶元素的下一个空位置 t-b>=n 放入元素;t++ t- -;弹出元素 t-b
方法3 b=t=n b==t 指向栈顶元素 b-t>=n t- -;放入元素 弹出元素;t++ b-t
方法4 b=t=n-1 b==t 栈顶元素的下一个空位置 b-t>=n 放入元素;t- - t++;弹出元素 b-t

3.1 静态顺序栈

静态栈分配空间后,内存地址将不会发生变化。

3.1.1 栈的定义
//静态顺序栈
#define MAX_STACK_SIZE 100
typedef int ElementType;
typedef struct static_sq_stack{
    
    
    ElementType stack_array[MAX_STACK_SIZE];
    int top;
}SqStack;
3.1.2 栈的初始化
void InitStack(SqStack *S){
    
    
    //初始化操作
    S->top = 0;
}
3.1.3 栈的入栈
bool push_stack(SqStack S, ElementType e){
    
    
    //静态顺序栈进栈
    if (S.top == MAX_STACK_SIZE) return false;
    S.data[S.top] = e;
    S.top++;
    //以上可简写为 S.data[S.top++] = e
    return true;
}

3.1.4 栈的出栈
bool pop_stack(SqStack S, ElementType * e){
    
    
    //静态顺序栈出栈
    if (S.top == 0) return false;
    S.top--;
    * e = S.data[S.top];
    return true;
}

3.2 动态顺序栈

所谓动态,指的是可以动态改变栈的大小。可以用bottom表示栈底指针,固定不变;用top(称为栈顶指针)指示当前栈顶位置,栈顶随着进栈和退栈操作而变化。
注意:假如原栈的大小为50个,其栈顶位置为t1,栈底位置为b1;则将栈大小改为60个,则其栈顶内存地址将可能会由t1变为t2,栈底内存地址可能会由b1变为b2。即t1不一定等于t2,b1不一定等于b2。

3.2.1 定义
// 动态顺序栈
#define STACK_SIZE 100       /*栈初始数组大小*/
#define STACKINCREMENT 10    /*存储空间分配增量*/
typedef int ElementType;
// 用bottom表示栈底指针,栈底是固定不变的;用top(成为栈顶指针)表示当前栈顶位置,栈顶随着进栈和出栈操作而发生变化;
typedef Struct sqstack{
    
    
    ElementType *bottom; //栈不存在时,值为NULL
    ElementType *top;   //栈顶指针
    int stacksize;      //当前分配空间,以元素为单位
}SqStack_dynamic;

3.2.2 动态顺序链栈的初始化
bool Init_dynamic_stack(SqStack_dynamic S){
    
    
    //动态顺序栈进行初始化
    S.bottom = (ElementType *)malloc(STACK_SIZE * sizeof(ElementType));
    if (!S.bottom) return false;
    S.top = S.bottom;
    S.stacksize = STACK_SIZE;
    return true;
}

3.2.3 动态顺序链栈的入栈
bool push_d_stack(SqStack_dynamic S, ElementType e){
    
    
    //动态顺序栈的入栈操作
    if (S.top - S.bottom >= S.stacksize){
    
    
        //栈满时,追加存储空间
        S.bottom = (ElementType *)realloc(S.bottom, (STACKINCREMENT + S.stacksize) * sizeof(ElementType));
        if (!S.bottom) return false;
        S.top = S.bottom + S.stacksize;
        S.stacksize += STACKINCREMENT;
    }
    //将元素放入到顶指针指向的空位置。
    * S.top = e;
    // 栈顶指针加1,e成为新的栈顶
    S.top++;
    return true;
}
3.2.4 动态顺序链栈的出栈
bool pop_d_stack(SqStack_dynamic S, int *e){
    
    
    //弹出栈顶元素,并将值赋给e;
    if(S.top == 0) return false;
    S.top--;
    *e = *S.top;
    return true;
}

3.3 共享顺序栈

共享顺序栈,又称对顶栈。
假如有两个顺序栈,让两个栈共享同一片存储空间,这片存储空间不单独属于任何一个栈,某个栈需要的多一点,它就可能得到多一些的存储空间。两个栈的栈底在这片存储空间的两端,当元素入栈时,两个栈的栈顶指针相向而行,这样的栈称为对顶栈。
栈满条件: |t1 - t2| == 1

4.链栈

链栈其实是头出头插的单链表,即入栈时头插法,将新结点插在头结点的后面;出栈时是头出法,将头结点后的结点删除。

4.1 链栈的定义

//定义链栈
typedef struct Stack_Node{
    
    
    ElementType data;
    struct Stack_Node * next;
}Stack_Node;

一个栈顶指针为top的链栈指向头结点,其链栈为空的条件是:
top->next == NULL

4.2 链栈的初始化

Stack_Node *Init_Link_Stack(){
    
    
    // 链栈的初始化
    Stack_Node *top;
    top = (Stack_Node *)malloc(sizeof(Stack_Node));
    top ->next = NULL;
    return top;
}

4.3 链栈的入栈

bool push_Link_Stack(Stack_Node * S, ElementType e){
    
    
    //链栈的入栈
    Stack_Node * p;
    p = (Stack_Node *)malloc(sizeof(Stack_Node));
    if (!p) return false;
    p->data = e;
    p->next = S->next;
    S->next = p;
    return true;
}

4.4 链栈的出栈

bool pop_Link_Stack(Stack_Node * S, ElementType * e){
    
    
    // 链栈的出栈
    Stack_Node * p;
    p = S->next;
    if (p == NULL) return false;
    S->next = p->next;
    * e = p->data;
    free(p);
    return true;
}

4.5 链栈的注意事项

  1. 若以链表作为栈的存储结构,退栈时,需要判断栈是否为空;
  2. 与顺序栈相比,链栈的优势是通常不会出现栈满的情况;
  3. 假设链表不带头结点且所有操作均在表头进行,则最不适合作为链栈的是:只有表头指针没有表尾指针的循环单链表

5.栈的应用

5.1 括号的匹配

能够匹配成对出现的( ) [ ] { }

5.2 函数和递归的调用

递归函数,先调用,后执行。

5.3 表达式求解

中序表达式转后缀表达式:
①转化前先建立两个栈,命名为s1和s2。用栈s1存储表达式中的运算数,用栈s2存储符号。
②在转化的过程中,需从左到右扫描中缀表达式的每个运算数和符号,若是运算数则压入到栈s1
③若扫描到的是符号,设符号是C,则可能出现以下情况。
a.若s2为空,则C压入到栈s2中,
b.若C为左括号,则C压入到栈s2中。
c.若s2的栈顶符号为左括号,则C压入到栈s2中。
d.若s2的栈顶符号的优先级小于C,则C压入到栈s2中(右括号的优先级最低)。
C.否则,就要将s2的栈顶符号弹出,从s1中弹出两个(也可能是1个)运算数,进行运算,并把运算的结果压回s1。
④扫描到表达式结束,若最后栈s2中还有符号,则将其依次出栈,并从s1中弹出两个(也可能是1个)
运算数,进行运算,并把运算的结果压回s1。
⑤最终完成转化过程。

5.4 进制转换

将十进制数100,转为8进制数为?

数字n n / 8(取商) n%8(取余数)
100 12 4
12 1 4
1 0 1

将上述最后一列存入栈中,按序取出即为转换后的结果:144

猜你喜欢

转载自blog.csdn.net/qq_41780234/article/details/127277173