栈(动态数组与链表实现)

  1. 栈是一个先入后出(FILO-First In Last Out)的有序列表。栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。

  2. 允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。

  3. 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除

在这里插入图片描述

教学视频方案

package com.stack;

import java.util.Arrays;

/**
 * @author ming
 * @create 2020-02-13 9:05
 */
public class ArrayStackDemo {
    public static void main(String[] args) {
        //创建对象
        ArrayStack arrayStack = new ArrayStack(4);
        //入栈
        arrayStack.push(1);
        arrayStack.push(0);
        arrayStack.push(22);
        arrayStack.push(799);
        //遍历
        arrayStack.list();//799 22 0 1
        //出栈
        int pop = arrayStack.pop();
        arrayStack.pop();
        arrayStack.pop();
        //遍历
        arrayStack.list();//1
    }
}

//定义栈ArrayStack
class ArrayStack {
    private int maxSize;
    private int[] stack;
    //栈顶
    private int top = -1;

    public ArrayStack() {
    }

    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }

    //栈满
    public boolean isFull() {
        return top == maxSize - 1;
    }

    //栈空
    public boolean isEmpty() {
        return top == -1;
    }

    //入栈
    public void push(int value) {
        //判满
        if (isFull()) {
            System.out.println("入栈,栈满");
        }
        stack[++top] = value;
    }

    //出栈,将栈顶的数据返回
    public int pop() {
        if (isEmpty()) {
            throw new RuntimeException("出栈,栈空");

        }
        return stack[top--];
    }

    //遍历,后进先出
    public void list() {
        //判空
        if (isEmpty()) {
            throw new RuntimeException("遍历,栈空");
        }
        for (int i = top; i >= 0; i--) {
            System.out.println(stack[i]);
        }
    }

    public int getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    public int[] getStack() {
        return stack;
    }

    public void setStack(int[] stack) {
        this.stack = stack;
    }

    public int getTop() {
        return top;
    }

    public void setTop(int top) {
        this.top = top;
    }
}

书中方案1

动态调整数组,实现迭代的栈

package com.z_stack;


import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * @author ming
 * @create 2020-03-01 9:55
 */
public class ResizingArrayStack<Item> implements Iterable<Item> {
    private Item[] a;         // array of items
    private int n;            // number of elements on stack

    /**
     *
     */
    public ResizingArrayStack() {
        //定义泛型数组
        a = (Item[]) new Object[2];
        n = 0;
    }

    /**
     * @return 返回true则栈为空
     */
    public boolean isEmpty() {
        return n == 0;
    }

    /**
     * @return 栈长度
     */
    public int size() {
        return n;
    }


    // 调整包含元素的数组大小
    private void resize(int capacity) {
        assert capacity >= n;

        //将栈移动到一个大小为capacity
        Item[] copy = (Item[]) new Object[capacity];
        for (int i = 0; i < n; i++) {
            copy[i] = a[i];
        }
        a = copy;
    }


    /**
     *
     */
    public void push(Item item) {
        if (n == a.length) resize(2 * a.length);    //数组扩容
        a[n++] = item;                            // 数据加入栈顶
    }

    /**
     * 出栈
     */
    public Item pop() {
        if (isEmpty()) throw new NoSuchElementException("Stack underflow");
        Item item = a[n - 1];
        a[n - 1] = null;                              // 避免对象游离
        n--;
        // 数组减容
        if (n > 0 && n == a.length / 4) resize(a.length / 2);
        return item;
    }


    /**
     * 查看栈顶元素
     */
    public Item peek() {
        if (isEmpty()) throw new NoSuchElementException("Stack underflow");
        return a[n - 1];
    }

    /**
     *
     */
    public Iterator<Item> iterator() {
        return new ReverseArrayIterator();
    }

    //
    private class ReverseArrayIterator implements Iterator<Item> {
        private int i;

        public ReverseArrayIterator() {
            i = n - 1;
        }

        public boolean hasNext() {
            return i >= 0;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        public Item next() {
            if (!hasNext()) throw new NoSuchElementException();
            return a[i--];
        }
    }

    /**
     *测试
     */
    public static void main(String[] args) {
        ResizingArrayStack<String> stack = new ResizingArrayStack<String>();

        stack.push("q");
        stack.push(null);
        stack.push("w");
        stack.push("e");
        stack.pop();

        for (String s : stack) {
            System.out.print(s + "\t");//w	null	q
        }

    }
}

书中方案2

链表实现栈

package com.z_stack;

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * 链表实现栈
 *
 * @author ming
 * @create 2020-03-01 11:36
 */
public class Stack<Item> implements Iterable<Item> {
    private Node<Item> first;     // 栈顶(最近添加的元素)
    private int n;                // 元素数量

    // 定义节点嵌套类
    //只有包含它的类能访问它的实例变量
    //非静态的嵌套类也被称为内部类
    private static class Node<Item> {
        private Item item;
        private Node<Item> next;
    }

    /**
     * Initializes an empty stack.
     */
    public Stack() {
        first = null;
        n = 0;
    }

    /**
     * @return 返回true则栈为空
     */
    public boolean isEmpty() {
        return first == null;
    }

    /**
     * @return 栈长度
     */
    public int size() {
        return n;
    }

    /**
     * @param item 要添加的数据,添加在栈顶
     */
    public void push(Item item) {
        Node<Item> oldfirst = first; // 保存头结点
        first = new Node<Item>(); // 新的头结点
        first.item = item;
        first.next = oldfirst; // 旧的头结点编程第2个节点
        n++;
    }

    /**
     * @return 删除的栈顶元素
     * @throws NoSuchElementException if this stack is empty
     */
    public Item pop() {
        if (isEmpty()) throw new NoSuchElementException("Stack underflow");
        Item item = first.item;        // 栈顶元素存入临时变量
        first = first.next;            // 栈顶位置下移
        n--;
        return item;
    }


    /**
     * @return 返回栈顶元素(查看)
     * @throws NoSuchElementException 栈为空
     */
    public Item peek() {
        if (isEmpty()) throw new NoSuchElementException("Stack underflow");
        return first.item;
    }

    /**
     * @return 此栈中按后进先出顺序排列的项序列,用空格分隔
     */
    public String toString() {
        StringBuilder s = new StringBuilder();
        for (Item item : this) {
            s.append(item);
            s.append('\t');
        }
        //StringBuilder重写了toString()方法
        return s.toString();
    }


    /**
     * @return Iterator<Item>()接口实现类
     */
    public Iterator<Item> iterator() {
        return new LinkedIterator(first);
    }

    // 一般不实现remove()方法
    private class LinkedIterator implements Iterator<Item> {
        private Node<Item> current;

        public LinkedIterator(Node<Item> first) {
            current = first;
        }

        public boolean hasNext() {
            return current != null;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        public Item next() {
            if (!hasNext()) throw new NoSuchElementException();
            Item item = current.item;
            current = current.next;
            return item;
        }
    }


    /**
     * 测试
     */
    public static void main(String[] args) {
        Stack<String> stack = new Stack<>();

        stack.push("z");
        stack.push("2");
        stack.push(null);
        stack.push("至尊宝");
        stack.push("w");
        stack.pop();
        System.out.println(stack.toString());//w	至尊宝	null	2	z
    }
}

前缀表达式

从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果

例如: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6,针对前缀表达式求值步骤如下:

  1. 右至左扫描,将6、5、4、3压入堆栈

  2. 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈

  3. 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈

  4. 最后是-运算符,计算出35-6的值,即29,由此得出最终结果


中缀表达式

运算符号位于两个运算数之间
符合人计算规则: (3+4)×5-6 对应的前缀表达式就是(3+4)×5-6

  1. 通过一个 index 值(索引),来遍历我们的表达式

  2. 如果我们发现是一个数字,就直接入数栈

  3. 如果发现扫描到是一个符号, 就分如下情况:

    a. 如果发现当前的符号栈为 空,就直接入栈
    b. 如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符, 就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈, 如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈.

  4. 当表达式扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行.

  5. 最后在数栈只有一个数字,就是表达式的结果

package com.stack;

/**
 * @author ming
 * @create 2020-02-14 9:34
 */
public class Calculator {
    public static void main(String[] args) {
        String expression = "200-2*6-4/2";
        ArrayStack2 numStack = new ArrayStack2(10);
        ArrayStack2 operStack = new ArrayStack2(10);
        //扫描
        int index = 0;
        int num1 = 0;
        int num2 = 0;
        int oper = 0;
        int res = 0;
        char ch = ' ';
        //定义字符串
        String keepNum = "";
        while (true) {
            //依次得到每一个字符
            ch = expression.substring(index, index + 1).charAt(0);
            //判断数字与字符
            if (operStack.isOper(ch)) {//为运算符

                //判符号栈空
                if (!operStack.isEmpty()) {//不为空
                    //判栈顶符号与当前符号优先级
                    //当前 < 栈顶
                    if (operStack.priority(ch) < operStack.priority(operStack.keep())) {
                        //从numStack栈中抛出2个数计算
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        //将operStack栈顶符号返回
                        oper = operStack.pop();
                        //计算
                        res = numStack.cal(num1, num2, oper);
                        //结果如numStack栈
                        numStack.push(res);
                        //当前符号入operStack栈
                        operStack.push(ch);
                    } else {
                        //当前 > 栈顶 直接入栈
                        operStack.push(ch);
                    }

                } else {
                    //入栈
                    operStack.push(ch);
                }
            } else {//为数字,入栈

                //numStack.push(ch - 48);
                //若为多位数,则需多判断一位,和进行拼接
                keepNum += ch;
                //当某位数字前面的运算符为"-"时,将当前数字改为相反数,并将运算符"-"改为"+"
                if (index != 0) {
                    if (operStack.isOper(expression.substring(index - 1, index).charAt(0))) {
                        if (operStack.keep() == '-') {
                            operStack.pop();
                            operStack.push('+');
                            keepNum = "-" + ch;
                        }
                    }
                }
                //判断下一个字符,为数字继续扫描,为字符则数字入numStack栈
                if (index == expression.length() - 1) {//为最后一个数字
                    numStack.push(Integer.parseInt(keepNum));
                } else {
                    //当前数字的下一个为运算符时
                    if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
                        numStack.push(Integer.parseInt(keepNum));
                        //
                        keepNum = "";
                    }
                }
            }
            index++;
            //扫描结束
            if (index >= expression.length()) {
                break;
            }
        }
        while (true) {
            //opercStack栈为空,则numStack栈中为结果
            if (operStack.isEmpty()) {
                break;
            }
            //从numStack栈中返回出2个数计算
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            //计算
            res = numStack.cal(num1, num2, oper);
            //结果入numStack栈
            numStack.push(res);
        }
        //打印结果
        System.out.println("结果:" + numStack.pop());
    }
}

//创建栈
class ArrayStack2 {
    private int maxSize;
    private int[] stack;
    //栈顶
    private int top = -1;

    public ArrayStack2() {
    }

    public ArrayStack2(int maxSize) {
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }


    //当前operStack栈顶的运算符,看一看
    public int keep() {
        return stack[top];
    }

    //栈满
    public boolean isFull() {
        return top == maxSize - 1;
    }

    //栈空
    public boolean isEmpty() {
        return top == -1;
    }

    //入栈
    public void push(int value) {
        //判满
        if (isFull()) {
            System.out.println("入栈,栈满");
        }
        stack[++top] = value;
    }

    //出栈,将栈顶的数据返回
    public int pop() {
        if (isEmpty()) {
            throw new RuntimeException("出栈,栈空");
        }
        return stack[top--];
    }

    //遍历,后进先出
    public void list() {
        //判空
        if (isEmpty()) {
            throw new RuntimeException("遍历,栈空");
        }
        for (int i = top; i >= 0; i--) {
            System.out.println(stack[i]);
        }
    }

    //返回运算符的优先级
    public int priority(int oper) {
        if (oper == '*' || oper == '/') {
            return 1;
        } else if (oper == '+' || oper == '-') {
            return 0;
        } else {
            return -1;
        }
    }

    //判断是否为运算符
    public boolean isOper(char val) {
        return val == '+' || val == '-' || val == '*' || val == '/';
    }

    //运算方法
    public int cal(int num1, int num2, int oper) {
        int res = 0;
        switch (oper) {
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num2 - num1;
                break;
            case '*':
                res = num1 * num2;
                break;
            case '/':
                res = num2 / num1;
                break;
            default:
                break;
        }
        return res;
    }


    public int getMaxSize() {
        return maxSize;
    }

    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }

    public int[] getStack() {
        return stack;
    }

    public void setStack(int[] stack) {
        this.stack = stack;
    }

    public int getTop() {
        return top;
    }

    public void setTop(int top) {
        this.top = top;
    }
}

后缀表达式

时间复杂度O(n)

运算符号位于两个运算数之后。

从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

例如: (30+4)×5-6 对应的后缀表达式就是 30 4 + 5 × 6 -,针对后缀表达式求值步骤如下:

  1. 从左至右扫描,将30和4压入堆栈;

  2. 遇到+运算符,因此弹出4和30(4为栈顶元素,30为次顶元素),计算出30+4的值,得34,再将34入栈;

  3. 将5入栈;

  4. 接下来是×运算符,因此弹出5和34,计算出34×5=170,将170入栈;

  5. 将6入栈;

  6. 最后是-运算符,计算出170-6的值,即164,由此得出最终结果

package com.stack;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * @author ming
 * @create 2020-02-16 8:58
 */
public class Poland {
    public static void main(String[] args) {
        //后缀表达式
        String suffixExpression = "30 4 + 5 * 6 -";
        List<String> list = getListString(suffixExpression);
        System.out.println(list);

        //
        int calculate = calculate(list);
        System.out.println("结果:" + calculate);
    }

    //后缀表达式放入ArrayList中,并返回
    public static List<String> getListString(String suffixExpression) {
        //以" "分割
        String[] split = suffixExpression.split(" ");
        ArrayList<String> array = new ArrayList<>();
        for (String ele : split) {
            array.add(ele);
        }
        return array;
    }

    //对后缀表达式的运算
    public static int calculate(List<String> list) {
        //创建栈
        Stack<String> stack = new Stack<>();
        //遍历传入的集合
        for (String s : list) {
            //使用正则表达式
            if (s.matches("\\d+")) {//匹配一位或多位数
                //为数入栈
                stack.push(s);
            } else {
                //为运算符,从栈中返回2个数计算
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if ("+".equals(s)) {
                    res = num1 + num2;
                } else if ("-".equals(s)) {
                    res = num1 - num2;
                } else if ("*".equals(s)) {
                    res = num1 * num2;
                } else if ("/".equals(s)) {
                    res = num1 / num2;
                } else {
                    throw new RuntimeException("符号出错");
                }
                //再将结果入栈
                stack.push("" + res);
            }
        }
        //结果
        return Integer.parseInt(stack.pop());
    }
}

中缀转后缀

运算数相对顺序不变;运算符号顺序发生改变。

  1. 从左至右扫描中缀表达式;
  2. 遇到操作数时,将其压s2;
  3. 遇到运算符时,比较其与s1栈顶运算符的优先级:
    1. 若s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    2. 当前运算符优先级 > 栈顶运算符优先级,也将运算符压入s1;
    3. 当前运算符优先级 <= 栈顶运算符的优先级,将s1栈顶的运算符弹出并压入到s2中,再将当前运算符与新的s1的栈顶运算符比较,重复操作;
  4. 遇到括号时:
    1. 如果是左括号“(”,则直接压入s1
    2. 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  5. 重复步骤2至5,直到表达式的最右边
  6. 将s1中剩余的运算符依次弹出并压入s2
  7. 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

总结:

  1. (处于栈外,优先级较高,处于栈内是优先级最低;
  2. 当遇到)时,将运算符全部抛出,直达遇到(,并丢弃(
  3. 遇到相同优先级,处于栈中的符号优先级高(同优先级,从左向右计算);

以此为例:
“1+((2+3)×4)-5” --> "1 2 3 + 4 × + 5 –"

扫描到的元素s2(栈底->栈顶)s1(栈底->栈顶)说明
11数字直接入s2
+1+s1为空,运算符直接入s1
(1+ (直接入栈s1
(1+ ( (直接入栈s1
21 2+ ( (数字
+1 2+ ( ( +s1栈顶为 ( , 运算符直接入栈
31 2 3+ ( ( +数字
)1 2 3 ++ (弹出s1中运算符入s2,直到遇到 ( ,并丢弃 (
*1 2 3 ++ ( *s1栈顶为 ( , 运算符直接入栈
41 2 3 + 4+ ( *数字
)1 2 3 + 4 *+弹出s1中运算符入s2,直到遇到 ( ,并丢弃 (
-1 2 3 + 4 * +-- 与 + 优先级相同,弹出 + 入s2,再压入 -
51 2 3 + 4 * + 5-数字
达到最右端1 2 3 + 4 * + 5 -s1中运算符入s2
package com.stack;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * @author ming
 * @create 2020-02-16 8:58
 */
public class Poland {
    public static void main(String[] args) {
        //中缀存入list1
        List<String> list1 = toInfixExpressionList("1+((30+2*2)/2)-5");
//        System.out.println(list1);
        //list1转后缀存入list2
        List<String> list2 = parseSuffixExpressionList(list1);
//        System.out.println(list2);
        //计算结果,打印
        int calculate = calculate(list2);
        System.out.println("结果:" + calculate);
    }

    //中缀转后缀
    public static List<String> parseSuffixExpressionList(List<String> list) {
        //定义两个栈
        Stack<String> s1 = new Stack<>(); // 符号栈
//        Stack<String> s2 = new Stack<>(); // 数字栈
        //s2只进不出,可用List代替
        ArrayList<String> s2 = new ArrayList<>();
        for (String s : list) {

            if (s.matches("\\d+")) {//为数字
                s2.add(s);
            } else if ("(".equals(s)) { // "("入栈
                s1.push(s);
            } else if (")".equals(s)) { //弹出s1中运算符入s2,直到遇到 ( ,并丢弃 (
                while (!"(".equals(s1.peek())) {
                    s2.add(s1.pop());
                }
                //并丢弃 (
                s1.pop();
            } else { // 其他运算符
                //当前运算符优先级 <= s1栈顶的运算符的优先级,将s1栈顶的运算符弹出并压入到s2中,
                //再将当前运算符与新的s1的栈顶运算符比较,重复操作
                while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(s)) {
                    s2.add(s1.pop());
                }
                //当前运算符入s1栈
                s1.push(s);
            }
        }

        //将s1中剩余的运算符依次弹出并压入s2
        while (s1.size() != 0) {
            s2.add(s1.pop());
        }
        //s2为集合,顺序输出即为中缀表达式对应的后缀表达式
        return s2;
    }

    //将中缀表达式放入ArrayList集合中,并返回
    public static List<String> toInfixExpressionList(String s) {
        //定义List
        ArrayList<String> list = new ArrayList<>();
        //指针
        int i = 0;

        String str;
        char c;
        do {
            //为非数字,直接加入list
            if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
                list.add("" + c);
                i++;
            } else {//为数字,考虑多位数得拼接
                str = "";
                while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57) {
                    str += c;
                    i++;
                }
                list.add(str);
            }
        } while (i < s.length());
        return list;
    }

    //后缀表达式放入ArrayList中,并返回
    public static List<String> getListString(String suffixExpression) {
        //以" "分割
        String[] split = suffixExpression.split(" ");
        ArrayList<String> array = new ArrayList<>();
        for (String ele : split) {
            array.add(ele);
        }
        return array;
    }

    //对后缀表达式的运算
    public static int calculate(List<String> list) {
        //创建栈
        Stack<String> stack = new Stack<>();
        //遍历传入的集合
        for (String s : list) {
            //使用正则表达式
            if (s.matches("\\d+")) {//匹配一位或多位数
                //为数入栈
                stack.push(s);
            } else {
                //为运算符,从栈中返回2个数计算
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if ("+".equals(s)) {
                    res = num1 + num2;
                } else if ("-".equals(s)) {
                    res = num1 - num2;
                } else if ("*".equals(s)) {
                    res = num1 * num2;
                } else if ("/".equals(s)) {
                    res = num1 / num2;
                } else {
                    throw new RuntimeException("符号出错");
                }
                //再将结果入栈
                stack.push("" + res);
            }
        }
        //结果
        return Integer.parseInt(stack.pop());
    }
}

class Operation {
    private static int ADD = 1;
    private static int SUB = 1;
    private static int MUL = 2;
    private static int DIV = 2;
    private static int LEFT = 0;

    private Operation() {
    }

    //调方法返回运算符优先级
    public static int getValue(String operation) {
        int result = 0;
        switch (operation) {
            case "+":
                result = ADD;
                break;
            case "-":
                result = SUB;
                break;
            case "*":
                result = MUL;
                break;
            case "/":
                result = DIV;
                break;
            default:
                result = LEFT;
        }
        return result;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值