当前位置: 首页 > news >正文

栈(Stack)和队列(queue)

目录

栈(Stack)

概念

栈的使用

栈的模拟实现

栈的应用场景

1. 改变元素的序列

​编辑

2. 将递归转化为循环

3. 括号匹配

4. 逆波兰表达式求值

​编辑

5. 出栈入栈次序匹配

6. 最小栈

概念区分

队列(Queue)

概念

队列的使用

队列模拟实现

循环队列

双端队列 (Deque)

面试题

1. 用队列实现栈

2. 用栈实现队列


栈(Stack)

概念

:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。

:上述两幅图示是为了理解栈的机制的。


栈的使用

public class Test {
    /**
     * 这是使用栈底层代码生成的测试文件
     * @param args
     */
    public static void main(String[] args) {
        /**
         * 栈的一些方法的实现
         */
        Stack<Integer> stack = new Stack<>();   // 构造一个栈对象出来

        // 往栈里面放元素【原则:先进的后出】
        stack.push(12);
        stack.push(23);
        stack.push(34);
        stack.push(45);   // 通过这个往栈里面添加元素,其中栈底为最先放的那个[12],栈顶为最后放的那个[45]

        // 从栈顶弹出元素[删除]
        int number1 = stack.pop();
        System.out.println(number1);   // 结果就是45

        // 获取栈顶元素【只是获取,但并不删除】
        System.out.println(stack);  // 先打印看一下栈里面有哪些元素
        int number2 = stack.peek();
        System.out.println(number2);
        System.out.println(stack);  // 再打印一下这个栈,验证一下是否被删除


        // 看一下栈是否为空
        boolean result1 = stack.empty();
        System.out.println(result1);
        // 再把栈中的元素全部删完
        stack.pop();
        stack.pop();
        stack.pop();
        // 这个时候再看一下栈是否为空
        boolean result2 = stack.empty();
        System.out.println(result2);


        // 获取栈的长度【因为刚刚已经把栈中的元素删完了,所以这里需要往栈里面添加一些元素】
        stack.push(99);
        stack.push(101);
        stack.push(103);
        stack.push(105);
        // 一共是添加了4个元素,接下来验证一样是不是4个元素
        int ret = stack.size();
        System.out.println(ret);

    }

上述代码就是对栈的一些方法进行一些简单的测试


栈的模拟实现

下述代码是我们自己模拟实现的一个的方法:

package stack_queue;

import java.util.Arrays;

public class Mystack {
    private int[] elem;
    private int usedSize;



    private static final int DEFAULT_CAPACIPY = 10;  // 默认数组的长度是10
    // 再来提供一个构造方法
    public Mystack(){
        this.elem = new int[DEFAULT_CAPACIPY];
    }


    /**
     * 入栈方法
     * @param data
     */
    public void push(int data){
        // 如果满的话要进行扩容
        if (isFull()){
            this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
        }
        this.elem[this.usedSize] = data;
        this.usedSize++;
    }


    /**
     * 删除栈中的元素【从栈顶开始删除】
     * @return
     */
    public int pop(){
        // 栈为空能删除元素吗?
        if (empty()){
            System.out.println("当前栈为空!!");
            return -1;
        }
//        return this.elem[usedSize--];
        int oldval = elem[usedSize-1];
        usedSize--;
        return oldval;
    }


    /**
     * 判断栈是否为空
     * @return
     */
    public boolean empty(){
        if (usedSize == 0){
            return true;
        }
        return false;
    }


    /**
     * 获取栈顶元素,但是不删除栈顶元素
     * @return
     */
    public int peek(){
        // 首先要判断一下是否为空
        if (empty()){
            System.out.println("当前栈为空!!");
            return -1;
        }
        return elem[usedSize-1];
    }


    /**
     * 获取栈中元素的个数
     * @return
     */
    public int size(){
        return usedSize;
    }



    /**
     * 判断一下这个栈有没有满,如果满的话,我们要做一些扩容【扩容的方法要记得】
     * @return
     */
    public boolean isFull(){
        return usedSize == elem.length;
    }
    

}

栈的应用场景

1. 改变元素的序列

大家可以做一下上面两道题,答案分别是C【题型:不可能的出栈顺序】、B

2. 将递归转化为循环

比如:逆序打印链表

// 递归方式
void printList(Node head){
    if(null != head){
        printList(head.next);
        System.out.print(head.val + " ");
   }
}
// 循环方式
void printList(Node head){
    if(null == head){
        return;
   }
    
    Stack<Node> s = new Stack<>();
    // 将链表中的结点保存在栈中
    Node cur = head;
    while(null != cur){
        s.push(cur);
        cur = cur.next;
   }
    // 将栈中的元素出栈
    while(!s.empty()){
        System.out.print(s.pop().val + " ");
   }
}

3. 括号匹配

20. 有效的括号 - 力扣(LeetCode)

核心要点:字符串遍历完成 && 栈为空 

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();  // 实例化一个栈对象
        for(int i = 0; i < s.length();i++){
            char ch = s.charAt(i);  // 肯定是括号
            if(ch == '(' || ch == '{' || ch == '['){
                //左括号就入栈
                stack.push(ch);
            }else{
                //不是左括号,肯定是右括号了
                if(stack.empty()){
                    return false;
                }
                // 喵一眼栈顶的元素
                char top = stack.peek();
                if(ch == '}' && top == '{' || ch == ')' && top == '(' || ch == ']' && top == '['){
                    stack.pop();
                }else{
                    return false;
                }
            }
        }
        if(!stack.empty()){
            return false;
        }
        return true;
    }
}

4. 逆波兰表达式求值

150. 逆波兰表达式求值 - 力扣(LeetCode)

首先我们要了解什么是中缀表达式?什么是后缀表达式?中缀表达式怎么转成后缀表达式?后缀表达式怎么转成中缀表达式?

小编以图解的但是对此 123*+45*6+7*+ 这个后缀表达式用栈的方式进行计算;

如上图所示是利用栈进行后缀表达式计算的所有图示过程。可利用上述进行代码思路的搭建。

方法1:是一种比较低效的方法

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < tokens.length; i++) {
            String token = tokens[i];
            if (token.equals("/") || token.equals("*") || token.equals("+") || token.equals("-")) {
                int right = stack.pop();
                int left = stack.pop();
                int result = 0;
                if (token.equals("/")) {
                    result = left / right;
                } else if (token.equals("*")) {
                    result = left * right;
                } else if (token.equals("+")) {
                    result = left + right;
                } else if (token.equals("-")) {
                    result = left - right;
                }
                stack.push(result);
            } else {
                stack.push(Integer.parseInt(token));
            }
        }
        return stack.pop();
    }
}

方法2:相比之前的方法,效率有所提高


5. 出栈入栈次序匹配

栈的压入、弹出序列_牛客题霸_牛客网

这个题就是我们在做“不可能的出栈顺序”体型的代码实现。【入栈的同时是可以出栈的!】

上述是小编关于这个题目要求写的 一个图解,希望能够帮助同学们搭建代码的结构。

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pushV int整型一维数组 
     * @param popV int整型一维数组 
     * @return bool布尔型
     */
    public boolean IsPopOrder (int[] pushV, int[] popV) {
        Stack<Integer> stack = new Stack<>();
        int j = 0;
        for(int i = 0;i < pushV.length;i++){
            stack.push(pushV[i]);
            while(!stack.empty()&&stack.peek() == popV[j] && j < popV.length){
                stack.pop();
                j++;
            }
        }
        if(stack.empty()){
            return true;
        }
        return false;
    }
}


6. 最小栈

155. 最小栈 - 力扣(LeetCode)

上述是小编关于这个题目要求写的 一个图解,希望能够帮助同学们搭建代码的结构。【上述两图,图一是入栈,图一是出栈】

代码示例:小编的代码只供参考

class MinStack {

    private Stack<Integer> stack;
    private Stack<Integer> minstack;

    public MinStack() {
        stack = new Stack<>();
        minstack = new Stack<>();  //初始化对象
    }
    
    public void push(int val) {
        stack.push(val);
        if(minstack.empty()|| minstack.peek() >= val){
            minstack.push(val);
        }
    }
    public void pop() {
        int delete = stack.pop();
        if(!minstack.empty()&& minstack.peek() == delete){
            minstack.pop();
        }
    }
    
    // 获取栈顶元素【注意不是删除】
    public int top() {
        return stack.peek();
    }
    
    // 获取堆栈中的最小元素【注意不是删除】
    public int getMin() {
        return minstack.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

概念区分


队列(Queue)

概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头(Head/Front.

队列的使用

在Java中,Queue是个接口,底层是通过链表实现的。

下面是关于队列的一些方法:

下面是关于上图中代码的测试:

    /**
     * 先来一个queue已有方法的测试
     */
    public static void main(String[] args) {
        Deque<Integer> deque = new LinkedList<>();  // 这是一个双端队列
        Queue<Integer> queue = new LinkedList<>();  // 这是一个单端队列

        // 往队列里面放元素
        queue.offer(11);
        queue.offer(22);
        queue.offer(33);
        System.out.println(queue);
        System.out.println("========================");

        // 从队列里面拿元素【先进的先出】(删除)
        int ret1 = queue.poll();
        System.out.println(ret1);
        System.out.println(queue);
        System.out.println("=========================");

        // 获取队头元素【这是回去不是删除】
        int ret2 = queue.peek();
        System.out.println(ret2);
        System.out.println(queue);
        System.out.println("=========================");

        // 获取队列的长度
        queue.offer(99);
        queue.offer(520);
        queue.offer(1314);
        System.out.println(queue);  // 先打印一下这个列表
        int size = queue.size();
        System.out.println("列表的长度为:" + size);


        // 检测队列是否为空
        boolean result1 = queue.isEmpty();
        System.out.println(result1);
        queue.poll();
        queue.poll();
        queue.poll();
        queue.poll();
        queue.poll();
        boolean result2 = queue.isEmpty();
        System.out.println(result2);
    }


如上图所示,在队列里面,其中某一个功能有很多方法可以实现,但是每个方法都有其区别,大家可以私下在查阅资料进行比对。下面是他们的一些适用场景:


队列模拟实现

队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有两种:顺序结构链式结构。同学们思考下:队列的实现使用顺序结构还是链式结构好?

方法实现代码:

package stack_queue;

/**
 * 这个模拟队列的实现基于 双链表 去写
 */
public class Myqueue {
    static class Listnode{
        public int val;
        public Listnode next;
        public Listnode pre;

        // 再来一个构造方法
        public Listnode(int val) {
            this.val = val;
        }

    }

    public Listnode head;  //头节点【队头】
    public Listnode last; //尾巴节点【队尾】

    /**
     * 1.模拟实现往队列里面放元素
     */
    public void offer(int val){
        Listnode node = new Listnode(val);
        // 该链表为空【即该队列为空】
        if (head == null){
            head = node;
            last = node;
            return;
        }
        // 如果链表不为空【进行尾插法】
        last.next = node;
        node.pre = last;
        last = node;
    }

    /**
     * 2.模拟实现从队列中取出元素
     */
    public int poll(){
        // 如果链表为空
        if (head == null){
           return -1;
        }
        Listnode cur = head;
        // 如果链表只有一个元素
        if (head.next == null){
            head = null;
            last = null;
            return cur.val;
        }
        // 链表有两个或两个以上的元素
        head = head.next;
        head.pre = null;   //把head的前驱置空
        return cur.val;
    }


    /**
     * 3.获取队头元素
     */
    public int peek(){
        // 如果链表为空
        if (head == null){
            return -1;  // 或者选择抛一个异常【这样更好】
        }
        // 如果链表不为空
        return head.val;
    }


    /**
     * 4.获取队列长度
     */
    public int size(){
        int usedSize = 0;
        Listnode cur = head;
        while (cur != null){
            usedSize++;
            cur = cur.next;
        }
        return usedSize;
    }

    /**
     * 检测队列是否为空
     */
    public boolean isEmpty(){
        if (size() == 0){
            return true;
        }
        return false;
    }




    /**
     * 重写toString,为了方便测试的时候看测试结果
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");  // 以数组的格式展示

        Listnode cur = head;
        while (cur != null) {
            sb.append(cur.val);
            if (cur.next != null) {  // 不是最后一个节点,就添加逗号
                sb.append(", ");
            }
            cur = cur.next;
        }

        sb.append("]");
        return sb.toString();
    }


}

测试代码:

/**
     * 这个是测试模拟队列的实现
     */
    public static void main(String[] args) {
        Myqueue myqueue = new Myqueue();

        /**
         * 1.往队列里面放元素
         */
        myqueue.offer(11);
        myqueue.offer(22);
        myqueue.offer(33);
        myqueue.offer(44);
        System.out.println(myqueue.toString());
        System.out.println("======================");


        /**
         * 2.从队列中取出元素
         */
        int ret1 = myqueue.poll();
        System.out.println(myqueue.toString());
        System.out.println("取出的元素是:"+ret1);
        System.out.println(myqueue.toString());
        System.out.println("======================");


        /**
         * 3.获取队头元素
         */
        int ret2 = myqueue.peek();
        System.out.println(myqueue.toString());
        System.out.println("队头元素是:" + ret2);
        System.out.println(myqueue.toString());
        System.out.println("======================");


        /**
         * 4.获取队列长度
         */
        System.out.println(myqueue.toString());  // 打印一下列表来验证长度
        int size = myqueue.size();
        System.out.println("队列的长度为:" + size);
        System.out.println("======================");


        /**
         * 5.判断队列是否为空
         */
        boolean result1 = myqueue.isEmpty();
        System.out.println(result1);
        // 然后把队列中的元素都删除
        myqueue.poll();
        myqueue.poll();
        myqueue.poll();
        // 再来测试一下
        boolean result2 = myqueue.isEmpty();
        System.out.println(result2);
        System.out.println("=======================");
    }

注:小编再这里要做一个必要的提醒,小编的代码引导,是其中的一个思路。并不是唯一的实现方法,也不一定是最优的方法。


循环队列

622. 设计循环队列 - 力扣(LeetCode)

提出一个问题:根据前面关于队列的知识,我们在入队的同时,也可以出队。但是对于数组来讲,如果一个数字只能存放10个元素,然后当我们在入队的同时出队,实际上数组还有空位,但是此时按照队列的思路,我们无法往数组里面添加元素了,那数组的存储空间不是白白浪费掉了吗?因此,我们提出了一个循环队列。

实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列通常使用数组实现。

那到底什么时候才满呢?提供以下集中思路:

判断是否满了:

1.定义一个usedSize == len;

2.做一个标记;

3.浪费一个空间来区分;

下标的变化:

实现代码示例:

/**
下面这个代码是浪费一个数组空间来实现环形队列的
 */
class MyCircularQueue {
    private int[] elem;    // 创建出一个数组
    private int front;
    private int rear;

    public MyCircularQueue(int k) {
        // 给这个数组分配一个内存空间
        this.elem = new int[k+1];   // 分配一个k+1长度的数组空间【因为需要浪费一个空间】
    }
    
    public boolean enQueue(int value) {
        if(isFull()){
            return false;
        }
        elem[rear] = value;
        rear = (rear+1)%elem.length;
        return true;
    }
    
    public boolean deQueue() {
        if(isEmpty()){
            return false;
        }
        front = (front+1)%elem.length;
        return true;
    }
    
    public int Front() {
        if(isEmpty()){
            return -1;
        }
        return elem[front];
        
    }
    
    // public int Rear() {
    //     if(isEmpty()){
    //         return -1;
    //     }
    //     return elem[(rear - 1 + elem.length) % elem.length];  

    // }
    public int Rear() {
    if(isEmpty()){
        return -1;
    }
    return elem[(rear - 1 + elem.length) % elem.length]; 
}

    
    public boolean isEmpty() {
       return front == rear;
    }
    
    public boolean isFull() {
       return (rear + 1) % elem.length == front;
    }
}

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue obj = new MyCircularQueue(k);
 * boolean param_1 = obj.enQueue(value);
 * boolean param_2 = obj.deQueue();
 * int param_3 = obj.Front();
 * int param_4 = obj.Rear();
 * boolean param_5 = obj.isEmpty();
 * boolean param_6 = obj.isFull();
 */

双端队列 (Deque)

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。

Deque是一个接口,使用时必须创建LinkedList的对象。

在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口。

Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

在 Java 集合框架(Collection Framework) 中:

  • Deque接口,表示一个双端队列(Double-ended Queue),即可以在头部和尾部插入或删除元素。
  • ArrayDequeLinkedList 都是 Deque 的实现类,提供了两种不同的数据结构方式:
  • Deque<E>(接口)

    • Deque 继承自 Queue 接口,提供 双端插入、删除 功能。
    • 既可以用作队列(FIFO)也可以用作(LIFO)。
  • ArrayDeque<E>(基于数组的双端队列实现)

    • 底层是 可扩容的数组,类似 ArrayList
    • 优点
      • 随机访问快,比 LinkedList 速度更快。
      • 无锁,比 LinkedBlockingDeque 更高效。
    • 缺点
      • 内存占用大,因为需要提前分配数组空间,并且扩容会有一定的开销。
  • LinkedList<E>(基于双向链表的双端队列实现)

    • 底层是 双向链表,类似 LinkedList 结构。
    • 优点
      • 动态扩容,不需要像 ArrayDeque 那样预留数组空间。
      • 插入和删除快,因为不需要移动大量元素(相比 ArrayDeque)。
    • 缺点
      • 访问慢,因为链表结构没有连续内存,访问需要遍历。


面试题

1. 用队列实现栈

225. 用队列实现栈 - 力扣(LeetCode)

思路:

1.当两个队列都为空的时候,说明我模拟实现的栈是空的

2."出栈"的时候,出并不为空的队列,size-1个【size--】.最后那个元素就是我出栈的元素

3."入栈"的时候入到不为空的队列

代码示例:



class MyStack {
    /**
先创建两个队列
 */
 Queue<Integer> queue1 = null;
 Queue<Integer> queue2 = null;

    public MyStack() {
        queue1 = new LinkedList();
        queue2 = new LinkedList();
    }
    
    public void push(int x) {
        if(!queue1.isEmpty()){
            queue1.offer(x);  // 那个队列不为空就入那个队列
        }else{
            queue2.offer(x);
        }
    }
    
    public int pop() {
    if (empty()) {
        return -1;
    }
    Queue<Integer> active = queue1.isEmpty() ? queue2 : queue1;
    Queue<Integer> backup = queue1.isEmpty() ? queue1 : queue2;

    while (active.size() > 1) {
        backup.offer(active.poll());
    }
    return active.poll();
}

    
    public int top() {
    if (empty()) {
        return -1;
    }
    Queue<Integer> active = queue1.isEmpty() ? queue2 : queue1;
    Queue<Integer> backup = queue1.isEmpty() ? queue1 : queue2;

    int res = -1;
    while (!active.isEmpty()) {
        res = active.poll();
        backup.offer(res);
    }
    return res;
}

    
    public boolean empty() {
        // 当两个队列都为空的时候,说明我当前模拟实现的栈是空的
        return (queue1.isEmpty() && queue2.isEmpty());
        
    }
}

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

2. 用栈实现队列

232. 用栈实现队列 - 力扣(LeetCode)

核心思路:

1.入队的时候,把所有元素全部放在第一个栈中

2.在出队之前要判断一下两个栈空不空,如果两个栈都为空,说明该队列中已经没有元素了。假设栈没有空,判断一下第二个栈中的元素为不为空,如果不为空则出第二个栈中元素;如果为空则把第一个栈中元素全部倒回第二个栈中,然后紧接着出第二个栈的元素。

上述是小编关于这个题目要求写的 一个图解,希望能够帮助同学们搭建代码的结构。

代码示例:

class MyQueue {
    private Stack<Integer> stack1;
    private Stack<Integer> stack2;

    // 进行栈的实例化
    public MyQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    
    public void push(int x) {
        // 往stack1里面放
        stack1.push(x);
    }
    
    public int pop() {
        // 首先判断一下队列是否为空
        if(empty()){
            return -1;
        }
        // 如果队列不为空
        if(!stack2.empty()){
            // 如果栈2不为空就从栈2中取出元素
            return stack2.pop();
        }else{
            // 如果栈2为空,则需要把栈2中所有的元素都搬到栈2中来【注意是所有元素】
            while(!stack1.empty()){
                stack2.push(stack1.pop());
            }
            return stack2.pop();
        }
    }

    // 偷偷瞄一眼但是不删除
    public int peek() {
        // 首先判断一下队列是否为空
        if(empty()){
            return -1;
        }
        // 如果队列不为空
        if(!stack2.empty()){
            // 如果栈2不为空就从栈2中取出元素
            return stack2.peek();
        }else{
            // 如果栈2为空,则需要把栈2中所有的元素都搬到栈2中来【注意是所有元素】
            while(!stack1.empty()){
                stack2.push(stack1.pop());
            }
            return stack2.peek();
        }
    }
    
    public boolean empty() {
        // 两个栈为空,队列才为空
        return (stack1.empty() && stack2.empty());
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

相关文章:

  • 高斯数据库的空分区的查看和清理
  • 01 Overview
  • Mysql配套测试之查询篇
  • 网络不可达
  • [AI速读]CHISEL vs. SystemVerilog:用RISC-V核心对比两种硬件设计语言
  • milvus实战-基于Ollama+bge-large-zh搭建嵌入模型,fastAPI提供http服务将PDF文件写入milvus向量库
  • 算法·动态规划·入门
  • Parsing error: Unexpected token, expected “,“
  • 矩阵可相似对角化
  • 深入分析和讲解虚拟化技术原理
  • 洛谷 [语言月赛 202503] 题解(C++)
  • vlan路由间配置
  • 飞牛-NAS风扇速度设置
  • 1、双指针法
  • 自由学习记录(46)
  • UE4学习笔记 FPS游戏制作11 把枪提出为对象
  • 2025.3.23机器学习笔记:文献阅读
  • soft回归用内置函数
  • 软考-高项,知识点一览八 整合管理
  • CUDA Lazy Loading:优化GPU程序初始化与内存使用的利器
  • 联合国报告:全球经济前景恶化,面临高度不确定性
  • 丹麦外交大臣拉斯穆森将访华
  • 中央军委决定调整组建3所军队院校
  • 现场丨在胡适施蛰存等手札与文献间,再读百年光华
  • 经济日报评外卖平台被约谈:行业竞争不能背离服务本质
  • 商人运作亿元“茅台酒庞氏骗局”,俩客户自认受害人不服“从犯”判决提申诉