0
点赞
收藏
分享

微信扫一扫

C++算法与数据结构_19

泠之屋 2022-04-23 阅读 49

栈的定义

栈的概念

  • 栈是线性表的一种;
  • 允许进行插入和删除操作的一端被称为栈顶,相对地,把另一端称为栈底;
  • 这是一种后进先出(LIFO, Last In First Out)或先进后出(FILO, First In Last Out)结构,最先(晚)到达栈的结点将最晚(先)被删除。

LgNwUf.png

栈的基本操作

  • 创建一个栈create():创建一个空的栈;
  • 进栈push(x):将x插入栈中,使之成为栈顶元素;
  • 出栈pop():删除栈顶元素并返回栈顶元素值;
  • 读栈顶元素top():返回栈顶元素值但不删除栈顶元素;
  • 判栈空isEmpty():若栈为空,返回true,否则返回false。

栈的抽象类

template <class elemType>
class stack {
    public: 
        virtual bool isEmpty() const = 0; 
        virtual void push(const elemType &x) = 0; 
        virtual elemType pop() = 0;              
        virtual elemType top() const = 0;
        // 虚析构函数防止内存泄漏
        virtual ~stack() {}
}; 

顺序栈

顺序栈的概念

顺序栈指栈的顺序实现。

  • 用连续的空间存储栈中的结点,即数组。
  • 下标为0的一端为栈底。
  • 进栈和出栈总是在栈顶一端进行,不会引起类似顺序表中的大量数据的移动。

顺序栈类定义

template <class elemType>
class seqStack: public stack<elemType>{
    private:
        elemType *elem;
        int top_p;
        int maxSize;
        void doubleSpace();
    public:
        seqStack(int initSize = 10);
        // 析构函数
        ~seqStack() { delete [] elem; }
        // 判断栈是否为空,若top_p的值为-1,返回true,否则返回false。
        bool isEmpty() const { return top_p == -1; }      
        void push(const elemType &x) ;
        // 出栈:返回栈顶元素,并把元素数量减1
        elemType pop() { return elem[top_p--]; }
        // top:与pop类似,只是不需要将top_p减1
        elemType top() const { return elem[top_p]; }
};

顺序栈成员函数实现

  • 构造函数:按照用户估计的栈的规模申请一个动态数组。
template <class elemType>
seqStack<elemType>::seqStack(int initSize) {
    elem = new elemType[initSize];
    maxSize = initSize ;
    top_p = -1;  
}
  • push(x):将top_p1,将x放入top_p指出的位置中。但要注意数组满的情况。
template <class elemType>
void seqStack<elemType>::push(const elemType &x) { 
    if (top_p == maxSize - 1)
        doubleSpace();
    elem[++top_p] = x; 
}

顺序栈性能分析

  • 除了进栈操作以外,所有运算实现的时间复杂度都是O(1)
  • 进栈运算在最坏情况下的时间复杂度是O(N)。(需要doubleSpace
  • 均摊分析法:最坏情况在N次进栈操作中至多出现一次。如果把扩展数组规模所需的时间均摊到每个插入操作,每个插入只多了一个拷贝操作,因此从平均的意义上讲,插入运算还是常量的时间复杂度

练习

验证栈序列

给出两个序列 pushedpoped 两个序列,其取值从 1 到n (1 ≤ n ≤ 10^5)。已知入栈序列是1 2 3 … n,如果出栈序列有可能是 poped,则输出Yes,否则输出No。每个测试点有多组数据。

输入描述:

第一行一个整数q,询问次数。

接下来q个询问,对于每个询问:

第一行一个整数n表示序列长度;

第二行n个整数表示出栈序列。

输出描述:

对于每个询问输出单独一行答案。如可能,则输出Yes;反之,则输出No。

示例 1:

输入:
2
5
5 4 3 2 1
4
2 4 1 3
输出:
Yes
No

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100005 
using namespace std;

int a[N], b[N], s[N], top, n;

int main() {
    int T;
    scanf("%d", &T);
    for (int i = 0; i <100000; i++)
        a[i] = i + 1;
    while (T--) {
        memset(b, 0, sizeof(b));
        memset(s, 0, sizeof(s));
        scanf("%d", &n);
        for (int i = 0; i < n; ++i) scanf("%d", &b[i]);
        int j = 0;
        top = 0;
        for (int i = 0; i < n; ++i) {
            while (j < n && (!top || s[top - 1] != b[i])) 
                s[top++] = a[j++];
            if (s[top - 1] == b[i]) 
                --top;
        }
        if (!top) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

链接栈

链接栈的概念

链接栈指的是用链接实现的栈。

  • 由于栈的操作都是在栈顶进行的,因此用单链表就足够了
  • 链接栈不需要头结点,因为对栈来讲只需要考虑栈顶元素的插入删除。从栈的基本运算的实现方便性考虑,可将单链表的头指针指向栈顶。

链接栈类定义

template <class elemType>
class linkStack: public stack<elemType> {
    private:
        struct node {
            elemType  data;
            node *next;
            node(const elemType &x, node *N = NULL) { 
                data = x; 
                next = N;
            }
            node():next(NULL) {}
            ~node() {}
        };

        node *top_p;
        
    public:
        // 构造函数:将top_p设为空指针
        linkStack() { top_p = NULL; }	   
        ~linkStack();
        // isEmpty():判top_p是否为空指针
        bool isEmpty() const { return   top_p == NULL; }
        // push(x):在表头执行单链表的插入。
        void push(const elemType &x) { op_p = new node(x, top_p); }
        elemType pop();
        // top():返回top_p指向的结点的值。
        elemType top() const { return top_p->data; }	    
}; 

链接栈成员函数实现

  • 析构函数:注意需要释放空间。
template <class elemType>
linkStack<elemType>::~linkStack() {
    node *tmp;

    while (top_p != NULL) {
        tmp = top_p; 
        top_p = top_p ->next;
        delete tmp;
    }
}
  • pop():出栈操作,在表头执行单链表的删除。
template <class elemType>
elemType linkStack<elemType>::pop() {
    node *tmp = top_p;
    elemType x = tmp->data;              

    top_p = top_p->next;                   
    delete tmp;                          
    return x;
} 

链接栈性能分析

由于所有的操作都是对栈顶的操作,与栈中的元素个数无关。所以,所有运算的时间复杂度都是O(1)

栈的应用

栈的一个主要应用就是把递归函数转换成非递归函数。

递归函数调用过程

  • 递归是一种特殊的函数调用,是在一个函数中又调用了函数本身。
  • 递归程序的本质是函数调用,而函数调用是要花费额外的时间和空间。
  • 在系统内部,函数调用是用栈来实现。

L2wzan.jpg

递归消除方法

如果程序员可以自己控制程序调用的栈,就可以消除递归调用。

方法如下:

定义一个存放子问题的栈
把整个问题放入栈中
While (栈非空)  
    执行解决问题的过程,分解出的小问题进栈

举例:快速排序的非递归实现

void quicksort(int a[], int low, int high) {
    int mid;

    if (low >= high) return;

    mid = divide(a, low, high);
    quicksort( a, low, mid-1);
    quicksort( a, mid+1, high);
}
  • 快排非递归实现的思想:

    • 设置一个栈,记录要做的工作,即要排序的数据栈。
    • 栈里面的每个元素为一个node,有left和right两个成员,表示对下标从left到right这一部分数据进行排序。
    • 操作步骤:
      先将整个数组进栈
      重复下列工作,直到栈空:
          从栈中弹出一个元素,即一个排序区间。
          将排序区间分成两半。
          检查每一半,如果多于两个元素,则进栈。
      
  • 快排非递归实现的过程:

    • 左边表示要排序的数组,右边表示我们的栈里面的内容。

      L20wz8.jpg

  • 快排非递归实现的代码:

// 每个node表示要对从left到right这一部分数据进行排序
struct  node {
    int left;
    int right;
};

void quicksort( int a[],  int size) { 
    seqStack <node> st;
    int mid, start, finish;
    node s;

    if (size <= 1) return;

    // 排序整个数组
    s.left = 0; 
    s.right = size - 1;
    st.push(s);
    while (!st.isEmpty())  {
        s = st.pop(); 
        start = s.left;
        finish = s.right; 
        mid = divide(a, start, finish);
        if (mid - start > 1) { 
            s.left = start; 
            s.right = mid - 1; 
            st.push(s); 
        }
        if (finish - mid > 1) { 
            s.left = mid + 1; 
            s.right = finish;
            st.push(s);
        }
    }
}

练习

表达式括号匹配

假设一个表达式有英文字母(小写)、运算符+ — * /和左右小(圆)括号构成,以@作为表达式的结束符。请编写一个程序检查表达式中的左右圆括号是否匹配,若匹配,则返回YES;否则返回NO。表达式长度小于255,左圆括号少于20个。

输入描述:

一行,输入的表达式。

输出描述:

一行,YES或NO

示例 1:

输入:
2*(x+y)/(1-x)@
输出:
YES

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

char s[1010];

int main() {
    int a = 0;
    scanf("%s", s);
    int len = strlen(s);
    for (int i = 0; i < len; i++) {
        if (s[i] =='(') {
            a++;
        }
        if (s[i] == ')') {
            a--;
            if (a < 0) {
                printf("NO");
                return 0;
            }
        }
    }
    if (a == 0)
        printf("YES");
    else
        printf("NO");
    return 0; 
}
 for (int i = 0; i < len; i++) {
        if (s[i] =='(') {
            a++;
        }
        if (s[i] == ')') {
            a--;
            if (a < 0) {
                printf("NO");
                return 0;
            }
        }
    }
    if (a == 0)
        printf("YES");
    else
        printf("NO");
    return 0; 
}
举报

相关推荐

0 条评论