[C++] 容器适配器:深入理解Stack与Queue的底层原理

news2024/11/25 6:51:23

Kevin的技术博客.png

文章目录

  • 容器适配器简介
    • `deque`的缺陷
    • 为什么使用`deque`作为`stack`和`queue`的底层默认容器
  • `stack`和`queue`的简单讲解
    • Stack(栈)
      • 栈的操作图示
      • 栈的相关接口
    • Queue(队列)
  • `Stack`和`Queue`的模拟实现
    • Stack(栈)
      • 作为容器适配器的特性
      • 模拟实现
    • Queue(队列)
      • 作为容器适配器的特性
      • 模拟实现
  • `PriorityQueue`(优先级队列)
    • 优先级队列的特性
    • `priority_queue`的使用
      • 常用接口
      • 传入自定义类型的注意事项
      • 使用自定义类型传入应用示例
    • `priority_queue`的模拟实现
    • `deque`的实际应用
  • 仿函数(Functor)
    • 什么是仿函数?
    • 仿函数的定义
    • 仿函数的特性
      • 状态保存
      • 参数化
      • 灵活性
    • 仿函数的使用场景


容器适配器简介

适配器(Adapter)是一种设计模式,其主要作用是将一个类的接口转换为另一个客户希望的接口。在STL(Standard Template Library)中,适配器用来封装底层容器,提供特定的接口和行为。这种封装可以使得不同的底层容器在接口上保持一致,从而简化代码的使用和维护。

本文所涉及的stackqueuepriority_queue都是容器适配器,在底层都可以通过在接口传入的容器类型来进行底层的容器实现。
image.png
image.png
image.png

以上官方接口图示中,Container就是适配器初始化时容器类型的指定,Compare是仿函数,也可以实现相关的适配。

deque的缺陷

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。
与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。
但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

为什么使用deque作为stackqueue的底层默认容器

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()pop_back()操作的线性结构,都可以作为stack的底层容器,比如vectorlist都可以;queue是先进先出的特殊线性数据结构,只要具有push_backpop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stackqueue默认选择deque作为其底层容器,主要是因为:

  1. stackqueue不需要遍历(因此stackqueue没有迭代器),只需要在固定的一端或者两端进行操作。
  2. stack中元素增长时,dequevector的效率高(扩容时不需要搬移大量数据);queue中的
    元素增长时,deque不仅效率高,而且内存使用率高。结合了deque的优点,而完美的避开了其缺陷。

关于deque的详细讲解:
[C++] vector对比list & deque的引出-CSDN博客

stackqueue的简单讲解

Stack(栈)

栈的操作图示

栈的相关接口

栈是一种后进先出(LIFO, Last In First Out)的数据结构,通常用于存储临时数据或实现递归。其基本操作包括:

接口函数说明
push(x)将元素x压入栈顶
pop()移除并返回栈顶元素
top()返回栈顶元素
empty()判断栈是否为空
size()返回栈中元素个数

Queue(队列)

Queue-Data-Structures.png
队列是一种先进先出(FIFO, First In First Out)的数据结构,适用于需要顺序处理数据的场景。其基本操作包括:

接口函数说明
push(x)将元素x加入队尾
pop()移除并返回队头元素
front()返回队头元素
back()返回队尾元素
empty()判断队列是否为空
size()返回队列中元素个数

StackQueue的模拟实现

Stack(栈)

作为容器适配器的特性

  1. 后进先出(LIFO):栈是一种遵循 LIFO 原则的数据结构,这意味着最后被添加到栈中的元素将是第一个被移除的元素。
  2. 受限的接口:与完整的容器不同,栈的接口限制了用户只能通过栈顶进行操作,不允许直接访问栈中的其他元素。
  3. 主要操作
    • push:向栈顶添加一个元素。
    • pop:移除栈顶的元素。
    • top:访问栈顶的元素(不移除它)。
  4. 空栈检查:可以检查栈是否为空,以便在尝试访问或移除元素之前确保栈不为空。
  5. 大小限制:可以查询栈中元素的数量,但不允许直接通过索引访问元素。
  6. 迭代器:虽然栈的迭代器功能有限,但栈仍然提供了迭代器,允许遍历栈中的元素,尽管只能从栈顶开始。
  7. 异常中立性:栈的操作(如 pushpop)保证不抛出异常,除非是底层容器的操作抛出异常。
  8. 底层容器:栈通常使用 dequevector 作为底层容器来存储元素。选择哪种容器取决于具体的实现和性能要求。
  9. 模板类:栈是一个模板类,可以存储任意类型的元素。
  10. 不提供排序:栈不提供元素排序功能,它只提供了基本的 LIFO 操作。
  11. 不提供元素删除:除了 pop 操作外,栈不提供从栈中删除任意位置元素的功能。
  12. 不提供直接访问:不能直接访问或修改栈中的元素,除了栈顶元素。

模拟实现

template<class T, class Container = std::deque<T>>
class stack {
public:
    // 向栈顶添加一个元素
    void push(const T& x) {
        _con.push_back(x); // 使用底层容器的 push_back 方法
    }

    // 移除栈顶元素
    void pop() {
        if (empty()) {
            throw std::out_of_range("Stack<>::pop: empty stack");
        }
        _con.pop_back(); // 使用底层容器的 pop_back 方法
    }

    // 获取栈顶元素的引用
    const T& top() const {
        if (empty()) {
            throw std::out_of_range("Stack<>::top: empty stack");
        }
        return _con.back(); // 使用底层容器的 back 方法
    }

    // 获取栈中元素的数量
    size_t size() const {
        return _con.size();
    }

    // 检查栈是否为空
    bool empty() const {
        return _con.empty();
    }

private:
    Container _con; // 底层容器
};

Queue(队列)

作为容器适配器的特性

  1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
  2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
  3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
    1. empty:检测队列是否为空
    2. size:返回队列中有效元素的个数
    3. front:返回队头元素的引用
    4. back:返回队尾元素的引用
    5. push_back:在队列尾部入队列
    6. pop_front:在队列头部出队列
  4. 标准容器类dequelist满足了这些要求。默认情况下,如果没有为queue实例化指定容器
    类,则使用标准容器deque

模拟实现

template<class T, class Container = std::deque<T>>
class queue {
public:
    // 向队列尾部添加一个元素
    void push(const T& x) {
        _con.push_back(x); // 使用底层容器的 push_back 方法
    }

    // 移除队列头部的元素
    void pop() {
        if (empty()) {
            throw std::out_of_range("Queue<>::pop: empty queue");
        }
        _con.pop_front(); // 使用底层容器的 pop_front 方法
    }

    // 获取队列头部元素的引用
    const T& front() const {
        if (empty()) {
            throw std::out_of_range("Queue<>::front: empty queue");
        }
        return _con.front(); // 使用底层容器的 front 方法
    }

    // 获取队列尾部元素的引用
    const T& back() const {
        if (empty()) {
            throw std::out_of_range("Queue<>::back: empty queue");
        }
        return _con.back(); // 使用底层容器的 back 方法
    }

    // 获取队列中元素的数量
    size_t size() const {
        return _con.size();
    }

    // 检查队列是否为空
    bool empty() const {
        return _con.empty();
    }

private:
    Container _con; // 底层容器,默认为 deque
};

PriorityQueue(优先级队列)

优先级队列是一种特殊的队列,元素按照优先级排列。其基本操作类似于堆,主要用于调度算法、路径搜索等需要频繁获取最高优先级元素的场景。

优先级队列的特性

  1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
  2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
  3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
  4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
    1. empty():检测容器是否为空
    2. size():返回容器中有效元素个数
    3. front():返回容器中第一个元素的引用
    4. push_back():在容器尾部插入元素
    5. pop_back():删除容器尾部元素
  5. 标准容器类vectordeque满足这些需求。默认情况下,如果没有为特定的priority_queue
    类实例化指定容器类,则使用vector
  6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用
    算法函数make_heappush_heappop_heap来自动完成此操作。

priority_queue的使用

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使priority_queue

注意:默认情况下priority_queue是大堆。

常用接口

接口函数说明
push(x)插入元素x
pop()移除并返回最大(或最小)元素
top()返回最大(或最小)元素但不移除
empty()判断队列是否为空
size()返回队列中元素个数
emplace(x)就地构造元素x并插入队列
swap(q)交换当前优先级队列与q中的元素
std::less<T>默认仿函数,构建最大堆
std::greater<T>自定义仿函数,构建最小堆(需自定义仿函数参数)

传入自定义类型的注意事项

当你使用 std::priority_queue 时,它默认使用 < 运算符来确定元素之间的优先级关系,即默认情况下,较小的元素会被认为是具有较高优先级的。然而,std::priority_queue 也允许用户指定一个自定义的比较函数,这使得你可以定义自己的优先级规则。

所以:如果在priority_queue中放自定义类型的数据,需要在自定义类型中提供>或者< 的重载。

如果你要将自定义类型的对象放入 std::priority_queue 中,并且希望使用不同于默认的优先级规则(例如,你可能希望较大的元素具有较高的优先级),你需要提供一个自定义的比较函数。这个比较函数可以是:

  • 一个函数对象(functor)。
  • 一个普通的函数。
  • 一个 lambda 表达式。

这就是仿函数的基本用法。当使用自定义类型时,传入std::greater<T>std::less<T>会自动调用自定义类型重载的<>来构建优先级队列。

使用自定义类型传入应用示例

class Date {
public:
    // 构造函数,初始化日期
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day) {}

    // 重载小于运算符,用于比较两个日期
    bool operator<(const Date& d) const {
        return (_year < d._year) ||
               (_year == d._year && _month < d._month) ||
               (_year == d._year && _month == d._month && _day < d._day);
    }

    // 重载大于运算符,用于比较两个日期
    bool operator>(const Date& d) const {
        return (_year > d._year) ||
               (_year == d._year && _month > d._month) ||
               (_year == d._year && _month == d._month && _day > d._day);
    }

    // 友元函数,重载输出流运算符,用于输出日期
    friend std::ostream& operator<<(std::ostream& os, const Date& d) {
        os << d._year << "-" << d._month << "-" << d._day;
        return os;
    }

private:
    int _year;
    int _month;
    int _day;
};

void TestPriorityQueue() {
    // 使用默认的 priority_queue 创建最大堆
    std::priority_queue<Date> q1;
    q1.push(Date(2018, 10, 29));
    q1.push(Date(2018, 10, 28));
    q1.push(Date(2018, 10, 30));
    std::cout << "Max heap top: " << q1.top() << std::endl;

    // 使用自定义比较对象 greater<Date> 创建最小堆
    std::priority_queue<Date, std::vector<Date>, std::greater<Date>> q2;
    q2.push(Date(2018, 10, 29));
    q2.push(Date(2018, 10, 28));
    q2.push(Date(2018, 10, 30));
    std::cout << "Min heap top: " << q2.top() << std::endl;
}

int main() {
    TestPriorityQueue();
    return 0;
}

TestPriorityQueue 函数展示了如何使用 std::priority_queue 来创建最大堆和最小堆。最大堆 q1 使用 Date 类的 < 运算符来确定元素的优先级,而最小堆 q2 使用 std::greater<Date> 来实现,它将 Date 类型的 > 运算符作为比较函数。函数最后输出了两个堆的顶部元素。

priority_queue的模拟实现

template<class T>
class Less {
public:
    bool operator()(const T& x, const T& y) const { 
        return x < y;
    }
};

template<class T>
class Greater {
public:
    bool operator()(const T& x, const T& y) const { 
        return x > y;
    }
};

namespace bee {
    template<class T, class Container = std::vector<T>, class Compare = Less<T>>
    class priority_queue {
    public:
        void adjustUp(int child) {
            while (child > 0) {
                int parent = (child - 1) / 2;
                if (_compare(_con[child], _con[parent])) {
                    std::swap(_con[child], _con[parent]);
                    child = parent;
                } else {
                    break;
                }
            }
        }

        void push(const T& x) {
            _con.push_back(x);
            adjustUp(static_cast<int>(_con.size()) - 1);
        }

        void adjustDown(int parent) {
            int child = parent * 2 + 1;
            while (child < _con.size()) {
                int right_child = child + 1;
                if (right_child < _con.size() && _compare(_con[child], _con[right_child])) {
                    child = right_child;
                }
                if (_compare(_con[child], _con[parent])) {
                    std::swap(_con[child], _con[parent]);
                    parent = child;
                    child = parent * 2 + 1;
                } else {
                    break;
                }
            }
        }

        void pop() {
            std::swap(_con[0], _con.back());
            _con.pop_back();
            adjustDown(0);
        }

        T top() const { // 添加 const 并移除 return 关键字
            return _con[0];
        }

        size_t size() const {
            return _con.size();
        }

        bool empty() const {
            return _con.empty();
        }

    private:
        Container _con;
        Compare _compare; // 存储比较函数对象
    };
}

在底层使用堆进行维护,符合了deque的逻辑。

deque的实际应用

struct Task {
    int priority;
    std::string name;
    
    // 重载运算符,用于比较任务的优先级(priority),越小优先级越高
    bool operator<(const Task& other) const {
        return priority > other.priority; // priority值越小优先级越高
    }
};

int main() {
    std::priority_queue<Task> taskQueue;

    // 添加任务到队列
    taskQueue.push(Task{3, "Task A"});
    taskQueue.push(Task{1, "Task B"});
    taskQueue.push(Task{2, "Task C"});

    // 处理任务
    while (!taskQueue.empty()) {
        Task currentTask = taskQueue.top();
        std::cout << "Processing " << currentTask.name << " with priority " << currentTask.priority << std::endl;
        taskQueue.pop();
    }

在这个例子中,我们定义了一个Task结构体,每个任务有一个优先级和名称。我们使用std::priority_queue来管理这些任务,并通过重载operator<来定义任务的优先级比较规则。优先级最高的任务(priority值最小)会首先被处理。

仿函数(Functor)

什么是仿函数?

仿函数(Functor)是指实现了operator()的对象。在C++中,仿函数是一种能够像普通函数一样被调用的对象。它们通过重载函数调用运算符operator()来实现这一点,因此可以像函数一样使用。
通过重载operator(),仿函数可以模拟函数的行为,使得对象不仅可以保存状态,还可以执行操作。这种机制在C++中非常有用,特别是在STL(标准模板库)中,它允许用户自定义排序准则、筛选条件等。

仿函数的定义

仿函数是一个类或者结构体,通过重载operator()来实现。基本形式如下:

class Functor {
public:
    void operator()(/* 参数列表 */) {
        // 函数体
    }
};

仿函数的特性

状态保存

仿函数可以有成员变量,这允许它们在调用时保存状态。这是与普通函数的一个重要区别,因为普通函数没有状态。仿函数可以应用在需要保留上下文信息的场景。例如,计数器仿函数可以记录被调用的次数:

class Counter {
public:
    Counter() : count(0) {}

    void operator()() {
        ++count;
        std::cout << "Called " << count << " times" << std::endl;
    }

private:
    int count;
};

int main() {
    Counter countCalls;
    countCalls(); // 输出 "Called 1 times"
    countCalls(); // 输出 "Called 2 times"
    return 0;
}

在这个例子中,Counter仿函数保存了调用次数,并在每次调用时输出当前的调用次数。

参数化

仿函数可以通过构造函数参数传递数据,使得调用operator()时可以使用这些数据进行操作,也就是在上文适配器中关于仿函数的使用方式。

灵活性

仿函数可以重载operator()来实现不同的功能,比如比较、操作等,提供了很大的灵活性。结合灵活性与参数化,可以灵活的控制相关容器的底层存储。

template<class T, class Container = std::vector<T>, class Compare = Less<T>>

通过传递仿函数,用户可以自定义优先级队列的元素排列规则

例如在上文实现优先级队列的模拟实现代码中,就使用的仿函数作为模板参数:

priority_queue中,仿函数Compare决定了元素的优先级顺序。默认情况下,Less<T>会将较小的元素放在堆顶,形成最小堆。如果使用Greater<T>,则会形成最大堆。仿函数的灵活性允许用户根据需要自定义优先级队列的行为。仿函数的使用使得priority_queue能够支持多种排列规则,而不需要修改底层容器的实现。

仿函数的使用场景

  • 排序:在STL算法(如std::sort)中,可以使用仿函数自定义排序准则。
  • 筛选:在STL算法(如std::remove_if)中,可以使用仿函数定义筛选条件。
  • 优先级队列:在std::priority_queue中,仿函数用于定义元素的优先级排序。
  • 延迟计算:通过在仿函数中保存状态,用户可以实现延迟计算的逻辑。

具体的应用请通过上文优先级队列理解。


image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1967638.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新时代的北斗时空智能:助力节能减排,共筑低碳未来

随着全球环境问题日益加剧&#xff0c;节能减排和低碳生活成为了全世界共同的目标。中国作为一个崛起中的大国&#xff0c;在这一领域肩负着重要的责任。近年来&#xff0c;中国的北斗卫星导航系统&#xff08;BDS&#xff09;异军突起&#xff0c;其时空智能技术在多个方面特别…

盘点国内ERP系统,看看你都用过哪一款?

在数字化转型的浪潮中,企业资源计划(ERP)系统已成为企业提升管理效率、优化资源配置的关键工具。国内市场上,众多优秀的ERP软件产品如雨后春笋般涌现,为企业提供了丰富多样的选择。 本文将为您深度盘点10款国产高端ERP软件,重点围绕公司背景、专业实力、产品核心能力、解…

【机器学习】逻辑损失函数的基本概念和探索为什么平方误差损失函数不适用于逻辑回归以及探索逻辑损失函数

引言 在机器学习中&#xff0c;逻辑损失函数&#xff08;Logistic Loss Function&#xff09;是用于训练逻辑回归模型的一种损失函数。逻辑回归是一种广泛用于分类问题的模型&#xff0c;特别是用于二分类问题&#xff0c;它的目标是预测一个实例属于两个类别中的一个的概率 文…

经验模态分解 (EMD) 及其在信号降噪中的应用

引言 在信号处理领域&#xff0c;处理非线性和非平稳信号是一个重要的挑战。传统的信号处理方法&#xff0c;如傅里叶变换和小波变换&#xff0c;虽然在处理线性和稳态信号方面表现出色&#xff0c;但在面对复杂信号时往往力不从心。经验模态分解 (Empirical Mode Decompositi…

【IO】 对于fread,fwrite,time_t,fseek等操作,写日志文件,完成文件的拷贝,对bmp图像进行读写操作;

目录 1>写一个日志文件&#xff0c;将程序启动后&#xff0c;每一秒的时间写入到文件中 ​编辑 2>使用fread、fwrite完成两个文件的拷贝 3>实现对bmp图像的读写操作 1>写一个日志文件&#xff0c;将程序启动后&#xff0c;每一秒的时间写入到文件中 1、2024-7…

mysql排查锁等待

排查锁等待步骤 最近线上碰到了几次mysql锁等待的问题&#xff0c;一个事务线程长期占用锁资源&#xff0c;导致其他事务无法获取到锁&#xff0c;为了快速解决问题&#xff0c;我们把线程kill掉了&#xff0c;但后面就定位不到具体的问题了&#xff0c;这里我总结整理一下我的…

【C语言】qsort详解——能给万物排序的神奇函数

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:https://blog.csdn.net/huangcancan666/category_12718530.html &#x1f381;代码托管:qsort的使用和模拟实现 a96cdd4 黄灿灿/C语言 - Gitee.com ⚙️操作环境:Visual Studio 2022 目录 一、引言 二、…

java编程练习(初学者)每日练题

一、前言&#xff08;目前java的就业环境&#xff09; Java仍然是IT行业中最受欢迎和广泛使用的编程语言之一&#xff0c;特别是在企业级应用、后端服务、金融系统、大型网站、游戏开发等领域。Java岗位的需求依然强劲&#xff0c;体现在多个方面&#xff1a;1.企业级应用&…

Linux通过Docker安装Microsoft Office+RDP远程控制

之前写过一篇使用KVM虚拟机安装Microsoft OfficeRDP远程控制的文章&#xff0c;根据B站的教程安装后&#xff0c;发现有远程控制延迟的问题&#xff0c;比如拖动Office窗口时会延迟&#xff0c;搜狗输入法扫一下就闪退&#xff0c;插入形状后无法调整大小&#xff0c;无法调整图…

十七、【人工智能】【机器学习】【非监督学习】- K-均值 (K-Means)

系列文章目录 第一章 【机器学习】初识机器学习 第二章 【机器学习】【监督学习】- 逻辑回归算法 (Logistic Regression) 第三章 【机器学习】【监督学习】- 支持向量机 (SVM) 第四章【机器学习】【监督学习】- K-近邻算法 (K-NN) 第五章【机器学习】【监督学习】- 决策树…

热门超声波清洗机有哪些?值得入手的超声波清洗机品牌推荐

相信大家对超声波清洗机并不陌生&#xff0c;每次眼镜脏了&#xff0c;去眼镜店清洗时&#xff0c;店员使用的就是超声波清洗机。这种机器通过超声波的原理&#xff0c;能深入清洁物品内部&#xff0c;清洁效果非常好。相比于手动清洗&#xff0c;超声波清洗机可以在清洁过程中…

PHP健身微信小程序系统源码

&#x1f3cb;️‍♀️健身新潮流&#xff01;解锁“健身微信小程序”的全方位塑形秘籍 &#x1f4f1;开篇&#xff1a;掌中健身房&#xff0c;随时随地动起来 你还在为找不到合适的健身场地或教练而烦恼吗&#xff1f;是时候告别这些束缚&#xff0c;拥抱“健身微信小程序”…

身在职场,要认清的3个真理,让你把同事远远甩在身后

总有人被一些表面的现象所以蒙蔽&#xff0c;还沾沾自喜以为自己掌握了“真理”。职场上从来不缺“好心人”&#xff0c;总是用所谓的经验来告诫别人&#xff0c;应该如何做事。 大家都在说的事情&#xff0c;就一定是对的&#xff1f;那么为什么大多数人还活不成自己想成为的…

基于“日志审计应用”的 DNS 日志洞察实践

作者&#xff1a;羿莉 (萧羿) 基础背景 DNS(Domain Name System) [ 1] 是任何网络活动的基础。它将易于记忆的域名转换为机器能够理解的 IP 地址。监控 DNS 服务可以帮助用户识别网络活动并保持系统安全。出于合规和安全性的考虑&#xff0c;公司通常要求对网络日志进行存储和…

手撕算法题3 (附源码和思路)

算法 1.有效的括号2.用队列实现栈3.用栈实现队列3.设计循环队列 1.有效的括号 有效的括号 思路 借助栈这样的数据结构&#xff0c;将所有左括号进行入栈&#xff0c;所有右括号与出栈的括号比较&#xff0c;相同循环继续&#xff0c;不同直接返回false。循环结束后检查栈是否为…

模拟队列--C++

用数组来表示队列&#xff0c;怎么表示呢&#xff1f;我们先假设hh为头&#xff0c;tt为尾,当弹出队头的时候我们只需要把hh加一下就连可以了&#xff0c;相反tt一样也可以 #include<iostream> using namespace std; const int N1e510; int a[N],tt-1,hh0;//hh头 tt尾 i…

2.Linux_vi编辑器

打开/创建文件 1、打开/创建文件 指令&#xff1a;vi 文件名 若文件不存在&#xff0c;则新建一个文件&#xff1b;若文件存在&#xff0c;则打开这个文件。 2、打开文件时&#xff0c;指定光标的位置 指令&#xff1a;vi 文件名 行号 注意&#xff1a;""和行号…

吴恩达机器学习-C1W3L1-逻辑回归分类

在本实验中&#xff0c;您将对比回归和分类。 import numpy as np %matplotlib widget import matplotlib.pyplot as plt from lab_utils_common import dlc, plot_data from plt_one_addpt_onclick import plt_one_addpt_onclick plt.style.use(./deeplearning.mplstyle)分类…

JDK 8 升级 17 及 springboot 2.x 升级 3.x 指南

JDK 8 升级 17 简介 从 JDK 8 升级到 JDK 17 的过程中&#xff0c;有几个主要的变化&#xff0c;特别是 Java Platform Module System (JPMS) 的引入&#xff0c;以及一些包路径的调整。以下是与 JDK 17 相关的一些重要变化&#xff1a; Java Platform Module System (JPMS) …

The First项目报告:解读Trading Bot黑马,交易狙击手Banana Gun

Meme币市场的特点是高度投机性和波动性&#xff0c;一个项目可能在短时间内实现巨大涨幅&#xff0c;为投资者带来巨额回报。然而&#xff0c;这种市场也充满了不确定性&#xff0c;许多项目可能只是短暂的炒作&#xff0c;缺乏实际价值或长期发展的潜力。因此&#xff0c;对于…