简易STL实现 | PriorityQueue 的实现

1、priority_queue 的底层是堆,标准库中 直接使用 std::make_heap, std::push_heap, std::pop_heap 来实现 priority_queue

2、std::make_heap、std::push_heap 和 std::pop_heap 这三个函数 用于 处理堆数据结构(Heap)。堆 是一种特殊的完全二叉树,通常用于 实现优先队列,最常见的是 最大堆(每个节点的值 都大于或等于其子节点的值)或 最小堆(每个节点的值都小于或等于其子节点的值)

1)std::make_heap
功能:将一个范围内的元素转化为堆结构。
原型:template <class RandomIt> void make_heap(RandomIt first, RandomIt last);
解释:这个函数将一段范围 [first, last) 内的元素重新排列,使其符合堆的性质(默认情况下 是最大堆)

#include <iostream>
#include <algorithm>
#include <vector>int main() {std::vector<int> v = {3, 1, 4, 1, 5, 9, 2};std::make_heap(v.begin(), v.end());for (int n : v) {std::cout << n << ' '; // 输出最大堆}
}

2)std::push_heap
功能:将一个新元素 插入到 已经是堆的数据结构中,并调整 使其继续保持堆的性质
原型:template <class RandomIt> void push_heap(RandomIt first, RandomIt last);
解释:假设 [first, last-1) 范围已经是一个堆,push_heap 将范围内 最后一个元素(即 *(last-1))插入到正确的位置以维持堆的性质

#include <iostream>
#include <algorithm>
#include <vector>int main() {std::vector<int> v = {3, 1, 4, 1, 5, 9};std::make_heap(v.begin(), v.end()); // 先将其变为堆v.push_back(7);  // 添加新元素std::push_heap(v.begin(), v.end()); // 调整堆结构for (int n : v) {std::cout << n << ' '; // 输出调整后的堆}
}

3)std::pop_heap
功能:将堆的根元素(最大元素或最小元素)移到范围的末尾,同时 调整剩余元素,使它们仍保持堆的性质。
原型:template <class RandomIt> void pop_heap(RandomIt first, RandomIt last);
解释:pop_heap 将堆的第一个元素(根元素)与最后一个元素交换,然后调整 [first, last-1) 范围内的元素,使其依然满足堆的性质。此时,last-1 指向原来的堆顶元素

#include <iostream>
#include <algorithm>
#include <vector>int main() {std::vector<int> v = {3, 1, 4, 1, 5, 9, 2};std::make_heap(v.begin(), v.end()); // 先将其变为堆std::pop_heap(v.begin(), v.end());  // 移动最大元素到末尾v.pop_back(); // 移除最大元素for (int n : v) {std::cout << n << ' '; // 输出剩余的堆}
}

1、堆的插入和删除

插入操作: 插入新元素时,新元素 首先被放置在 树的最后一个位置,以保持 完全二叉树的结构。然后,该元素 会通过一个称为“上浮”(或“堆化”)的过程,与其父节点比较 并交换位置(如果在最大堆中 新元素比父节点大,或在最小堆中 新元素比父节点小)。这个过程重复进行,直到 新元素到达一个位置,它不再比父节点大(或小,取决于 是最大堆还是最小堆),或者 它已经到达了树的顶部

在这里插入图片描述

删除操作: 在堆中,删除操作 通常指的是删除根节点,即最大元素 或 最小元素。删除后,堆的结构性质 必须得到维护。这通常 通过 将最后一个元素移到根节点的位置来完成,接着执行“下沉”(或“堆化”)过程,该元素 会与其子节点比较 并根据需要 与较大(或较小)的子节点交换位置。这个过程持续进行,直到 该元素位于正确的位置,或者 它已经到达了树的底部

在这里插入图片描述

构建堆: 从无序数组 构建堆的过程 称为堆化(Heapify)。这可以通过 从 最后一个非叶子节点开始,对每个节点执行下沉操作 来完成。在数组中,给定索引为 i 的元素,其左子节点的索引为 2 * i + 1,右子节点的索引为 2 * i + 2,父节点的索引为 (i - 1) / 2

2、利用标准库堆实现

#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::make_heap, std::push_heap, std::pop_heap
#include <stdexcept>
#include <sstream> // 为了使用 istringstreamtemplate <typename T, typename Container = std::vector<T>>
class PriorityQueue {Container data; // 始终保持堆的性质public:PriorityQueue() {}PriorityQueue(const Container& c) : data(c) {std::make_heap(data.begin(), data.end());}void push(const T& val) {data.push_back(val);std::push_heap(data.begin(), data.end());}void pop() {if (!data.empty()) {std::pop_heap(data.begin(), data.end());data.pop_back();}elsethrow std::runtime_error("PriorityQueue is empty. ");}T& top() {if (!data.empty()) {return data.front(); // 顶部在数据结构的最前面}elsethrow std::runtime_error("PriorityQueue is empty. ");}bool empty() const {return data.empty();}size_t size() const {return data.size();}
};int main() {// 使用 std::vector 作为底层容器PriorityQueue<int, std::vector<int>> myPriorityQueue;int N;std::cin >> N;getchar();std::string line;for (int i = 0; i < N; i++) {std::getline(std::cin, line);std::istringstream iss(line);std::string command;iss >> command;int element;if (command == "push") {iss >> element;myPriorityQueue.push(element);}if (command == "pop") {try {myPriorityQueue.pop();}catch (const std::runtime_error& e) {// 不做任何处理,无输出continue;}}if (command == "top") {try {std::cout << myPriorityQueue.top() << std::endl;}catch (const std::runtime_error& e) {std::cout << "null" << std::endl; //如果 priority_queue 为空,则输出 null}}if (command == "size") {std::cout << myPriorityQueue.size() << std::endl;}if (command == "empty") {std::cout << (myPriorityQueue.empty() ? "true" : "false") << std::endl;}}return 0;
}

3、利用自己实现堆实现

#include <iostream>
#include <vector>
#include <stdexcept> // std::runtime_error
#include <sstream> // 为了使用 istringstream
#include <utility>  // std::swap 所在的头文件template <typename T, typename Container = std::vector<T>>
class MaxHeap {Container data;// 下标int parent(int i) {return (i - 1) / 2;}int leftChild(int i) {return 2 * i + 1;}int rightChild(int i) {return 2 * i + 2;}void heapify_down(int i) { // 结点不断下沉,循环不是递归while (true) {int largest = i;int left = leftChild(i);int right = rightChild(i);// 得出三者的最大值if (left < data.size() && data[left] > data[largest]) {largest = left;}if (right < data.size() && data[right] > data[largest]) {largest = right;}if (largest != i) {std::swap(data[largest], data[i]);i = largest;}else {return; // 结束了}}}void heapify_up(int i) { // 结点不断往上升,同样是循环不是递归int par = parent(i);while (par >= 0 && data[par] < data[i]) {std::swap(data[par], data[i]);i = par;par = parent(i);}}public:MaxHeap() {}MaxHeap(const Container& c) : data(c) {int size = data.size();for (int i = (size / 2) - 1; i >= 0; i--) { // 叶子结点显然不需要下沉heapify_down(i);}}// 接受迭代器的版本MaxHeap(typename Container::iterator begin, typename Container::iterator end) {data = Container(begin, end);int size = data.size();for (int i = (size / 2) - 1; i >= 0; i--) { // 叶子结点显然不需要下沉heapify_down(i);}}void push(const T &val) {data.push_back(val);heapify_up(data.size() - 1);}void pop() {if (data.size() == 0)throw std::out_of_range("Heap is empty. ");data[0] = data.back(); // 将最后一个节点换到根位置,删除最后一个结点,再下降data.pop_back();heapify_down(0);}bool isEmpty() const {return data.size() == 0;}T& top() {return data[0];}size_t size() const {return data.size();}
};template <typename T, typename Container = std::vector<T>>
class PriorityQueue {// Container data;MaxHeap<T> mh;public:PriorityQueue() {}PriorityQueue(const Container& c) : mh(c) {}void push(const T& val) {mh.push(val);}void pop() {if (!mh.isEmpty()) {mh.pop();}elsethrow std::runtime_error("PriorityQueue is empty. ");}T& top() {if (!mh.isEmpty()) {return mh.top(); // 顶部在数据结构的最前面}elsethrow std::runtime_error("PriorityQueue is empty. ");}bool empty() const {return mh.isEmpty();}size_t size() const {return mh.size();}
};int main() {// 使用 std::vector 作为底层容器PriorityQueue<int, std::vector<int>> myPriorityQueue;int N;std::cin >> N;getchar();std::string line;for (int i = 0; i < N; i++) {std::getline(std::cin, line);std::istringstream iss(line);std::string command;iss >> command;int element;if (command == "push") {iss >> element;myPriorityQueue.push(element);}if (command == "pop") {try {myPriorityQueue.pop();}catch (const std::runtime_error& e) {// 不做任何处理,无输出continue;}}if (command == "top") {try {std::cout << myPriorityQueue.top() << std::endl;}catch (const std::runtime_error& e) {std::cout << "null" << std::endl; //如果 priority_queue 为空,则输出 null}}if (command == "size") {std::cout << myPriorityQueue.size() << std::endl;}if (command == "empty") {std::cout << (myPriorityQueue.empty() ? "true" : "false") << std::endl;}}return 0;
}

1、构造堆的过程:
为什么从 (size / 2) - 1 开始:表示 从最后一个非叶子节点开始,向上依次 对每个节点进行堆调整。这是因为 叶子节点本身已经是 符合堆的结构,无需调整

2、这里 默认构造函数 怎么自己构造 Container

template <typename T, typename Container = std::vector<T>>
class PriorityQueue {Container data;public:PriorityQueue() {}
};

Container data; 会自动调用 Container 的默认构造函数
C++ 会自动调用 Container(即 std::vector)的默认构造函数 来初始化 data,因此 data 也会被默认构造为一个空容器

3、什么情况下 默认构造函数会被自动调用
1)定义类对象时(不带初始化参数)
当定义 一个类对象 并且不提供 任何初始化参数时,默认构造函数 会自动被调用。如果 没有自定义构造函数,编译器 会生成 一个默认的空构造函数

class MyClass {
public:MyClass() { std::cout << "Default constructor called!" << std::endl;}
};int main() {MyClass obj; // 默认构造函数被调用return 0;
}

2)类成员是对象时
如果 一个类 包含其他类类型的成员,并且 这些成员也有默认构造函数,当包含这些成员的类的构造函数 被调用时,这些成员的默认构造函数 也会自动调用

class A {
public:A() { std::cout << "Class A's default constructor!" << std::endl;}
};class B {A a; // A 类的对象作为 B 类的成员
public:B() { std::cout << "Class B's default constructor!" << std::endl;}
};int main() {B obj; // 先调用 A 的默认构造函数,然后调用 B 的默认构造函数return 0;
}

输出:
先调用 A 的默认构造函数,然后调用 B 的默认构造函数

Class A's default constructor!
Class B's default constructor!

当没有提供 默认构造函数
如果 Member 类中 没有默认构造函数,编译器 就无法自动调用 它的默认构造函数,必须显式地调用 其其他构造函数。否则,编译器会报错

#include <iostream>class Member {
public:Member(int x) {  // 只有一个参数化构造函数,没有默认构造函数std::cout << "Member constructor called with value: " << x << std::endl;}
};class MyClass {Member m;  // Member 类的成员
public:MyClass(int x) : m(x) {  // 必须使用初始化列表调用 Member 的构造函数std::cout << "MyClass constructor called" << std::endl;}
};int main() {MyClass obj(42);return 0;
}

输出:

Member constructor called with value: 42
MyClass constructor called

没有使用初始化列表(不推荐)
如果 不使用初始化列表,成员变量 仍然会先通过 它们的默认构造函数初始化,然后在构造函数体中 再对它们进行赋值操作。这种方式效率较低,因为对象 会先被默认初始化,然后被赋予新的值

#include <iostream>class Member {
public:Member(int x = 0) {  // 提供默认参数std::cout << "Member constructor called with value: " << x << std::endl;}
};class MyClass {Member m;  // Member 类的成员
public:MyClass(int x) {// 构造函数体内赋值,而不是使用初始化列表m = Member(x);std::cout << "MyClass constructor called" << std::endl;}
};int main() {MyClass obj(42);return 0;
}

输出:

Member constructor called with value: 0  // 先调用默认构造函数
Member constructor called with value: 42 // 然后在构造函数体内赋值
MyClass constructor called

3)数组初始化时
当创建对象数组时,每个数组元素的默认构造函数 会被自动调用。如果数组中元素的类型是 自定义类,并且 没有提供初始值,那么每个元素 都会调用默认构造函数

class MyClass {
public:MyClass() {std::cout << "Default constructor called!" << std::endl;}
};int main() {MyClass arr[3];  // 创建包含 3 个元素的数组,每个元素调用默认构造函数return 0;
}

输出:

Default constructor called!
Default constructor called!
Default constructor called!

4)动态分配对象时
当使用 new 操作符动态 分配一个类对象,并且 不提供构造函数的参数时,默认构造函数 会自动调用

class MyClass {
public:MyClass() { std::cout << "Default constructor called!" << std::endl;}
};int main() {MyClass* ptr = new MyClass(); // 默认构造函数调用delete ptr; // 删除动态分配的对象return 0;
}

5)继承类时
当派生类的默认构造函数 被调用时,基类的默认构造函数 也会被自动调用。如果 派生类没有显式指定 基类构造函数,编译器会自动调用 基类的默认构造函数

class Base {
public:Base() { std::cout << "Base class default constructor called!" << std::endl;}
};class Derived : public Base {
public:Derived() {std::cout << "Derived class default constructor called!" << std::endl;}
};int main() {Derived obj; // 首先调用 Base 的默认构造函数,然后调用 Derived 的默认构造函数return 0;
}

总结:
创建对象时 不传递参数
动态分配对象时 不传递参数
类的成员是 对象类型时,其成员的默认构造函数 自动调用
创建对象数组时
继承关系中 派生类调用时,基类的默认构造函数 会被自动调用

4、参数是 data.begin(), data.end() 函数参数 应该怎么写

#include <iostream>
#include <vector>
#include <algorithm> // for std::for_each// 定义一个模板函数,接受两个迭代器参数
template <typename InputIt>
void printRange(InputIt begin, InputIt end) {for (InputIt it = begin; it != end; ++it) {std::cout << *it << " ";}std::cout << std::endl;
}int main() {std::vector<int> data = {1, 2, 3, 4, 5};// 调用函数,传递 data.begin() 和 data.end() 作为参数printRange(data.begin(), data.end());return 0;
}

让函数 可以对容器的任意范围进行操作,适应不同类型的容器,如 std::vector、std::list、std::set 等,且不需要 具体知道容器的类型,只要 容器支持迭代器就可以

std::for_each,可以直接 利用这些算法 而不需要手动遍历迭代器

std::vector<int> data = {1, 2, 3, 4, 5};// 使用 std::for_each 打印范围内的元素
std::for_each(data.begin(), data.end(), [](int value) {std::cout << value << " ";
});

可以 通过指定迭代器类型,例如只接受 std::vector<int>::iterator 或者 std::list<int>::iterator,这样函数 只允许该特定类型的迭代器 作为参数

// 限制函数只接受 std::vector<int>::iterator 迭代器
void printVectorRange(std::vector<int>::iterator begin, std::vector<int>::iterator end) {while (begin != end) {std::cout << *begin << " ";++begin;}std::cout << std::endl;
}

5、为什么报错

template <typename T, typename Container = std::vector<T>>
MaxHeap(Container::iterator begin, Container::iterator end)

由于 在模板类中 尝试直接访问 Container::iterator,但在模板参数中,Container 还不是一个具体的类型,因此 无法直接使用 Container::iterator
Container::iterator 需要通过 typename 来显式声明,因为在模板中,Container 是一个依赖类型,编译器 不能自动推导出 它包含的类型成员
改成

MaxHeap(typename Container::iterator begin, typename Container::iterator end) 

6、依赖类型,编译器 不能自动推导出它包含的类型成员:
依赖类型 是指 那些依赖于模板参数的类型。因为 这些类型在模板实例化之前 是未知的

C++编译器 在第一次看到模板时,并不会 对模板进行实例化,它只是 做基本的语法检查。因此,对于依赖于模板参数的名称,编译器 可能不知道 这个名称到底是类型 还是变量。为了明确表示这些依赖类型,C++标准引入了 typename 关键字
使用 typename 来明确告诉编译器:T::value_type 是一个类型

template <typename T>
class MyContainer {
public:typename T::value_type data;
};template <typename T>
class MyContainer {
public:T::value_type data;  // 错误:没有 typename,编译器不知道 T::value_type 是什么
};

解析模板时的两个阶段:
实例化解析(模板实例化阶段):当模板 被具体类型实例化时,编译器 才会知道模板参数的具体类型,并解析 依赖于模板参数的类型

7、正在一个 const 成员函数中 返回一个非常量的引用。具体来说,top() 函数被声明为 const,但它试图返回 data[0] 的引用,而 const 成员函数 保证不会修改类的任何成员。因为 试图返回 data[0] 的非常量引用(就可以被修改),而 const 函数要求返回的值也是常量的(只有引用指针需要)

T& top() const {return data[0];  // data[0] 是一个非常量引用,但函数是 const
}

当一个成员函数被声明为 const 时,它意味着 该函数承诺 不会修改类的任何成员变量(除非 被声明为 mutable)
mutable 成员变量 可以在 const 成员函数中被修改,这是 唯一的例外

class MyClass {
private:int value;mutable int accessCount;  // 可以在 const 成员函数中修改public:MyClass(int v) : value(v), accessCount(0) {}// 常量成员函数,不能修改 value,但可以修改 accessCountint getValue() const {accessCount++;  // 允许修改 mutable 变量return value;}int getAccessCount() const {return accessCount;}
};int main() {MyClass obj(10);obj.getValue();  // 调用 getValue 后,accessCount 会被修改obj.getValue();std::cout << "Access count: " << obj.getAccessCount() << std::endl;  // 输出 2
}
  1. 返回值为非引用或非指针的情况
    如果 返回值是 值类型(如整数、浮点数 或 对象的副本),const 成员函数 并不要求 返回的值是常量的,因为返回的是 一个临时对象或拷贝,修改它 不会影响类的状态
class Example {int value;public:Example(int v) : value(v) {}// const 成员函数:可以返回一个拷贝(值类型)int getValue() const {return value;  // 返回一个拷贝,不会影响对象状态}
};
  1. 返回引用或指针的情况
    如果函数返回的是 类成员的引用或指针,为了遵守 const 成员函数的约定(不修改对象状态),必须返回 常量引用 或 常量指针,这样调用者 就无法通过 返回值修改类的成员
class Example {int value;public:Example(int v) : value(v) {}// const 成员函数:返回 const 引用const int& getValue() const {return value;  // 返回常量引用,调用者不能修改 value}// 非 const 成员函数:可以返回非常量引用int& getValue() {return value;  // 返回非常量引用,允许修改 value}
};
  1. 访问 const 对象的情况
    当有一个 const 对象时,只能调用 const 成员函数。这意味着 只有返回常量引用 或 常量指针的函数 才可以访问

4、高频面试题

1、如何从给定的无序数组中找到第 K 大的元素?

可以利用最小堆来解决这个问题。步骤如下:
创建一个大小为 K 的最小堆:
将数组的前 K 个元素添加到最小堆中
这可以使用 heapq.heapify() 函数在 O(k) 的时间内完成

遍历数组的剩余元素:
对于每个元素,如果其值 大于 最小堆的堆顶元素(即当前最小的元素),则:
用该元素替换堆顶元素
调整最小堆 以保持堆的性质,这个操作的时间复杂度是 O(log K)

获取结果:
遍历完成后,最小堆的堆顶元素即为第 K 大的元素

时间复杂度:O(n log k)

  1. 最小堆中 存储了 数组中最大的 K 个元素
    初始阶段:将数组的前 K 个元素放入最小堆,堆的大小为 K
    遍历阶段:对于数组中剩余的元素,如果 当前元素 大于 最小堆的堆顶(即最小的那个元素),就用 当前元素替换堆顶,然后重新调整堆(相当于把最小的那个元素删除了)
    通过这个过程,最小堆始终保留着 当前已知的最大的 K 个元素(重要)。因为:
    保持堆的大小为 K:任何时候堆中 都只存储 K 个元素
    堆的性质:在最小堆中,堆顶元素是堆中最小的

  2. 最小堆的堆顶是第 K 大的元素
    堆中元素的关系:最小堆中的所有元素 都是数组中最大的元素之一,但堆顶是 这些元素中最小的
    第 K 大的定义:第 K 大的元素 是在数组中从大到小排序后 位于第 K 位的元素
    因此,最小堆的堆顶元素是 当前最大的 K 个元素中最小的那个,也就是 整个数组中第 K 大的元素

2、插入和删除操作的 时间复杂度 O(log n)

3、堆排序是 一种利用堆结构的排序方法
将无序数组 构建成 一个最大堆
重复 从堆中删除最大元素,并将其 放到数组的尾部
时间复杂度:O(n log n)
空间复杂度:O(1)(就地排序)

4、二叉堆和斐波那契堆 有什么区别
二叉堆: 插入和删除操作的时间复杂度为 O(log n)。
斐波那契堆: 插入操作和减小关键字的时间复杂度为 O(1),删除最小元素的时间复杂度为 O(log n),但是均摊时间复杂度

减少键值和合并操作:斐波那契堆在这些操作上具有优势,尤其适用于需要频繁执行这些操作的算法

在这里插入图片描述

5、解释堆排序算法 以及 它的时间复杂度

堆排序算法的步骤如下:
构建最大堆
交换堆顶元素 与 最后一个元素,然后 减少堆的大小
重新堆化 新的堆顶元素
重复 上述步骤,直至堆的大小为 1
时间复杂度:O(n log n)

6、给定一个 k 有序数组,如何在最优时间复杂度内对它进行排序

k 有序数组是指 数组中的每个元素 最多只错位 k 个位置,也就是说,元素在排序后的位置 与原始位置的距离不超过 k。针对这种特殊的数组,可以在 O(n log k) 的时间复杂度内 对其进行排序,这比一般的排序算法(如 快速排序的 O(n log n))在 k 较小的情况下更高效

1)算法思路:
创建一个大小为 k+1 的最小堆(时间复杂度是 O(k)):
因为元素最多错位 k 个位置,所以前 k+1 个元素 包含了 数组开头需要的最小元素
将数组的前 k+1 个元素插入最小堆中

2)遍历数组并重建排序:
从数组的第 k+2 个元素开始,逐个遍历剩余的元素
对于每个元素:
从最小堆中取出堆顶元素(当前最小的元素,时间复杂度是 O(log k)),放到结果数组中
将当前元素插入最小堆(时间复杂度也是 O(log k))
当遍历完所有元素后,最小堆中还会剩下 k 个元素,将它们依次取出,放到结果数组中

3)总时间复杂度分析
初始化最小堆:O(k)
遍历和处理元素:
总共 n - (k+1) + k = n - 1 次 取出和插入操作
每次操作的时间复杂度为 O(log k)
总时间复杂度:O(k) + O((n - 1) * log k) ≈ O(n log k)

#include <iostream>
#include <vector>
#include <queue>using namespace std;/*** 对 k 有序数组进行排序* @param arr 原始数组* @param k 元素错位的最大距离* @return 排序后的数组*/
vector<int> sortKSortedArray(vector<int>& arr, int k) {int n = arr.size();vector<int> result;result.reserve(n);  // 预留空间,提升效率// 使用优先队列(最小堆)来管理当前的 k+1 个元素priority_queue<int, vector<int>, greater<int>> minHeap;// 将前 k+1 个元素插入最小堆for (int i = 0; i <= k && i < n; ++i) {minHeap.push(arr[i]);}// 从第 k+1 个元素开始遍历数组for (int i = k + 1; i < n; ++i) {// 取出堆顶元素(最小值),放入结果数组result.push_back(minHeap.top());minHeap.pop();// 将当前元素插入堆中minHeap.push(arr[i]);}// 将剩余的元素取出并加入结果数组while (!minHeap.empty()) {result.push_back(minHeap.top());minHeap.pop();}return result;
}int main() {vector<int> arr = {2, 6, 3, 12, 56, 8};int k = 3;vector<int> sortedArr = sortKSortedArray(arr, k);cout << "排序后的数组: ";for (int num : sortedArr) {cout << num << " ";}cout << endl;return 0;
}

https://kamacoder.com/ 手写简单版本STL,内容在此基础上整理补充

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

4、.Net 快速开发框架:DncZeus - 开源项目研究文章

DncZeus 是一个基于 ASP.NET Core 和 Vue.js 的前后端分离的通用后台管理系统框架&#xff0c;其愿景是成为一个易于使用且功能丰富的 .NET Core 通用后台权限管理模板系统基础框架。项目名称 "DncZeus" 由 "Dnc"(.NET Core 的缩写)和 "Zeus"(古…

JavaWeb环境下的Spring Boot在线考试系统开发

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理基于JavaWeb技术的在线考试系统设计与实现…

【学习】word保存图片

word中有想保存的照片 直接右键另存为的话&#xff0c;文件总是不清晰&#xff0c;截屏的话&#xff0c;好像也欠妥。 怎么办? 可以另存为 网页 .html 可以得到&#xff1a; 原图就放到了文件夹里面

Java学习Day47:戏耍黑手道人(项目记录)

1.项目背景 2.技术选择 3.环境搭建 1.创建空项目 创建health_parent父文件用来控制依赖&#xff0c;类型为quickStart 打包方式为&#xff0c;pom&#xff1a;用在父级工程或聚合工程中&#xff0c;用来做jar包的版本控制&#xff0c;必须指明这个聚合工程的打包方式为pom。…

计算机网络-RSTP工作过程与原理

前面我们已经学习了RSTP的一些基础概念以及对于STP的改进之处&#xff0c;因为RSTP兼容STP&#xff0c;所以实际上两者工作原理是一致的&#xff0c;这里只简单过一遍&#xff0c;然后进行一些基础实验即可&#xff0c;大致还是遵循选举根桥、确定端口角色与状态、全网收敛的思…

ROS理论与实践学习笔记——6 ROS机器人导航(仿真)之导航实现

准备工作&#xff1a;请先安装相关的ROS功能包 安装 gmapping 包(用于构建地图):sudo apt install ros-<ROS版本>-gmapping 安装地图服务包(用于保存与读取地图):sudo apt install ros-<ROS版本>-map-server 安装 navigation 包(用于定位以及路径规划):sudo apt in…

一文详解Ntlm Relay

Ntlm Rleay简介 Ntlm Rleay翻译过来就是Ntlm 中继的意思&#xff0c;也肯定是跟Ntlm协议是相关的&#xff0c;既然要中继&#xff0c;那么攻击者扮演的就是一个中间人的角色&#xff0c;类似于ARP欺骗&#xff0c;ARP欺骗就是在一个广播域中发送一些广播&#xff0c;然后大声问…

解锁C++多态的魔力:灵活与高效的编码艺术(上)

文章目录 前言&#x1f338;一、多态的定义与概念&#x1f33b;1.1 多态的核心思想&#xff1a;&#x1f33b;1.2 多态的两种主要形式&#xff1a; &#x1f338;二、多态的使用条件&#x1f33b;2.1 基类指针或引用2.1.1 为什么需要基类指针或引用 &#x1f33b;2.2 虚函数&am…

UE5 猎户座漂浮小岛 04 声音 材质

UE5 猎户座漂浮小岛 04 声音 材质 1.声音 1.1 导入 wav格式 1.2 循环播放 1.3 mp3转wav 1.4 新手包素材&#xff08;火焰 &#xff09; particle&#xff1a;颗粒 2.材质 2.1 基本颜色 M_Yellow 2.2 混合模式与双面材质 2.3 金属感、高光、粗糙度 M_AluminumAlloy 2.4 自…

视频网站开发:Spring Boot框架的高效实现

5 系统实现 5.1用户信息管理 管理员管理用户信息&#xff0c;可以添加&#xff0c;修改&#xff0c;删除用户信息信息。下图就是用户信息管理页面。 图5.1 用户信息管理页面 5.2 视频分享管理 管理员管理视频分享&#xff0c;可以添加&#xff0c;修改&#xff0c;删除视频分…

Codeforces Round 770 (Div. 2)

比赛链接&#xff1a;Dashboard - Codeforces Round 770 (Div. 2) - Codeforces A. Reverse and Concatenate 题意&#xff1a; 思路&#xff1a; 假设 s "abba" 经过1次操作后 -> "abbaabba" s "abcd" 经过一次操作后 -> "abcd…

JavaWeb合集12-Redis

十二、Redis 1、Redis 入门 Redis是一个基于内存的key-valule 结构数据库。 特点&#xff1a;基于内存存储&#xff0c;读写性能高 场景&#xff1a;适合存储热点数据(热点商品、资讯、新闻) Redis安装包分为Windows版和Linux版&#xff1a; Windows版 下载地址: https://gith…

unity 屏幕波动反馈打击效果(附资源下载)

unity 屏幕波动反馈打击效果 一枪打出去整个屏幕都回波动的效果反馈。 知识点&#xff1a; 1、动画事件 2、屏幕后处理 效果如图&#xff1a;&#xff08;波动速度浮动都可调整&#xff09; 附件下载

Java 枚举类

枚举类型 在Java编程语言中&#xff0c;枚举类&#xff08;Enum Class&#xff09;是一种特殊的类&#xff0c;它用于表示一组固定的常量。这些常量通常用于定义变量的合法取值&#xff0c;比如一周的天数、交通信号灯的颜色等。枚举类提供了一种类型安全的方式来使用这些常量&…

CasADi库C++用法整理学习---以NMPC代码为例

参考几个使用方法博客 1 官方文档写的很清楚 对SM&#xff0c;DM&#xff0c;XM数据类型疑惑。什么时候使用什么样的类型&#xff0c;还是都可以&#xff1f; x MX.sym(“x”) 这将创建一个 11 矩阵&#xff0c;即一个包含名为 x 的符号基元的标量。这只是显示名称&#xff…

基于Java的免税商品优选购物商城设计与实现代码(论文+源码)_kaic

目 录 摘 要 Abstract 第一章 绪论 1.1 课题开发的背景 1.2 课题研究的意义 1.3 研究内容 第二章 系统开发关键技术 2.1 JAVA技术 2.2 MyEclipse开发环境 2.3 Tomcat服务器 2.4 Spring Boot框架 2.5 MySQL数据库 第三章 系统分析 3.1 系统可行性研究…

ClickHouse的原理及使用,

1、前言 一款MPP查询分析型数据库——ClickHouse。它是一个开源的&#xff0c;面向列的分析数据库&#xff0c;由Yandex为OLAP和大数据用例创建。ClickHouse对实时查询处理的支持使其适用于需要亚秒级分析结果的应用程序。ClickHouse的查询语言是SQL的一种方言&#xff0c;它支…

01数组算法/代码随想录

一、数组 好久没写算法题&#xff0c;之前喜欢按着习惯选择刷题&#xff0c;很早以前就听说代码随想录&#xff0c;今天跟着代码随想录再过一遍算法 1.1二分查找 常见疑问 middle一定是在[left, right]这个范围内标准代码不会越界&#xff0c;因为在else if中出现越界后&…

【windows个性化】在 Windows 10/11 上使 Windows 任务栏半透明/透明

实现目的&#xff1a;在 Windows 10/11 上使 Windows 任务栏半透明/透明. TranslucentTB plugin 描述 只需几 MB 内存&#xff0c;几乎不占用 CPU&#xff0c;便可以在 Windows 10/11 上使 Windows 任务栏半透明/透明的轻量小工具 功能 高级颜色选择器(color picker)&…

X(twitter)推特广告类型有哪些?如何选择?

X&#xff08;twitter&#xff09;推特是全球最热门的几大社交媒体平台之一&#xff0c;也是很多电商卖家进行宣传推广工作的阵地之一。在营销过程中不可避免地需要借助平台广告&#xff0c;因此了解其广告类型和适配场景也十分重要。 一、广告类型及选择 1.轮播广告 可滑动的…