一、next_permutation()
next_permutation()
是 C++ 标准库中的一个函数,用于生成给定序列的下一个排列。如果当前排列是该序列的最后一个排列,next_permutation()
会将序列重新排列成第一个排列。
函数原型
#include <algorithm>
bool next_permutation(BidirectionalIterator first, BidirectionalIterator last);
bool next_permutation(BidirectionalIterator first, BidirectionalIterator last, Compare comp);
first
和last
是指向容器开始和结束的迭代器。comp
是一个可选的比较函数,用于自定义排列的顺序。如果未提供,默认使用<
操作符。
功能
- 返回值:如果生成了下一个排列,函数返回
true
;如果当前排列是最后一个排列,函数返回false
并将序列重置为第一个排列。
使用示例
假设我们有一个容器 std::vector<int>
,并希望使用 next_permutation
来生成所有可能的排列。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {1, 2, 3};
// 打印所有排列
do {
for (int x : vec) std::cout << x << ' ';
std::cout << '\n';
} while (std::next_permutation(vec.begin(), vec.end()));
return 0;
}
代码解释
- 初始化容器:我们定义了一个
std::vector<int>
并初始化为{1, 2, 3}
。 - 循环排列:使用
do-while
循环打印当前排列,并调用std::next_permutation
生成下一个排列。 - 结束条件:当
std::next_permutation
返回false
时,循环结束,表示已经生成了所有可能的排列。
使用自定义比较函数
next_permutation
也可以接受一个自定义比较函数,用于定义排列的顺序。例如,我们可以使用自定义的比较函数来生成降序排列。
#include <iostream>
#include <vector>
#include <algorithm>
// 自定义比较函数:大于
bool customCompare(int a, int b) {
return a > b;
}
int main() {
std::vector<int> vec = {1, 2, 3};
// 打印所有降序排列
do {
for (int x : vec) std::cout << x << ' ';
std::cout << '\n';
} while (std::next_permutation(vec.begin(), vec.end(), customCompare));
return 0;
}
代码解释
- 自定义比较函数:
customCompare
函数定义了一个大于比较(降序排列)。 - 使用自定义比较函数:
std::next_permutation
使用customCompare
来生成排列。
总结
next_permutation()
用于生成序列的下一个排列。- 默认行为:生成升序排列的下一个排列。
- 自定义行为:可以通过提供比较函数来生成不同顺序的排列。
- 返回值:
true
表示成功生成了下一个排列,false
表示当前排列已经是最后一个排列。
这个函数非常有用,可以用于需要生成所有排列或特定排列顺序的场景,例如排列组合、算法优化等。
二、lowbit(x) = x & -x
lowbit(x) = x & -x
是一种计算整数 x
的“最低有效位” (least significant bit) 的技巧,这个技巧在很多算法中非常有用,特别是在处理二进制表示和位操作时。下面详细解释这句代码的含义和用途。
1. 代码解释
lowbit(x) = x & -x
这个表达式利用了位运算来计算 x
的最低有效位。让我们逐步解释:
x
:一个整数,通常以二进制形式表示。-x
:是x
的负数。在计算机中,负数的二进制表示是其正数的二进制表示取反再加 1(即,二进制补码)。例如,如果x
的二进制表示是00001000
(即十进制的 8),那么-x
的二进制表示将是11111000
(即二进制的 -8)。
例子: 假设 x = 12
,其二进制表示为 00001100
。
-x
的二进制表示是11110100
(取反加 1)。x & -x
是00001100 & 11110100
,结果是00000100
,这是x
的最低有效位的值。
2. 用途
计算最低有效位的值
lowbit(x)
的结果是 x
二进制表示中最低的 1 所对应的值。例如:
- 对于
x = 12
(00001100
),lowbit(x) = 4
(00000100
),因为12
的最低有效位是 4。 - 对于
x = 10
(00001010
),lowbit(x) = 2
(00000010
),因为10
的最低有效位是 2。
在算法中的应用
-
位操作优化:
- 在一些算法中,特别是需要操作集合、更新数据结构或在树状结构中进行操作时,
lowbit
可以帮助快速找到和操作数据。例如,Binary Indexed Tree(树状数组)中,经常使用lowbit
来更新或查询数据。
- 在一些算法中,特别是需要操作集合、更新数据结构或在树状结构中进行操作时,
-
生成子集:
- 在生成所有子集的算法中,
lowbit
可以用来快速确定当前元素的最低有效位,从而生成所有可能的子集。
- 在生成所有子集的算法中,
3. 代码示例
以下是一个简单的 C++ 代码示例,展示了如何使用 lowbit
:
#include <iostream>
int lowbit(int x) {
return x & -x;
}
int main() {
int x = 12; // 二进制表示为 00001100
std::cout << "lowbit(" << x << ") = " << lowbit(x) << std::endl;
// 输出: lowbit(12) = 4
return 0;
}
4. 实际应用示例
例子 1:更新和查询 Binary Indexed Tree(树状数组)
假设你在实现一个 Binary Indexed Tree(BIT)来进行快速前缀和查询和更新操作:
#include <iostream>
#include <vector>
class BIT {
public:
BIT(int size) : tree(size + 1, 0), n(size) {}
void update(int index, int value) {
while (index <= n) {
tree[index] += value;
index += lowbit(index);
}
}
int query(int index) {
int sum = 0;
while (index > 0) {
sum += tree[index];
index -= lowbit(index);
}
return sum;
}
private:
std::vector<int> tree;
int n;
int lowbit(int x) {
return x & -x;
}
};
int main() {
BIT bit(10);
bit.update(1, 10);
bit.update(2, 20);
std::cout << "Sum of first 2 elements: " << bit.query(2) << std::endl;
return 0;
}
在这个例子中,lowbit
用于在更新和查询操作中计算索引的增量。
通过理解 lowbit
的定义和应用,你可以在各种需要二进制位操作的算法中利用这个技巧来优化你的代码。
三、unique(a.begin(), a.end())
unique(a.begin(), a.end())
函数返回的是已经去重后的序列的最后一个有效元素的下一个位置的迭代器。它的作用是将相邻的重复元素移到序列的末尾,并返回一个指向去重后序列末尾的迭代器。
具体来说:
-
去重过程:
unique
函数将相邻的重复元素移动到序列的末尾,并返回一个指向去重后的序列末尾的迭代器。注意,这个迭代器指向的是最后一个有效元素的下一个位置,即去重后序列的结束位置。 -
后续操作:你可以用这个迭代器来调整容器的大小,去掉不再需要的部分。例如,使用
container.erase(new_end, container.end())
来清除末尾的多余元素。
例如,考虑以下代码:
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> a = {1, 1, 2, 2, 3, 3, 3, 4};
auto new_end = std::unique(a.begin(), a.end());
a.erase(new_end, a.end());
for (int i : a) {
std::cout << i << " ";
}
return 0;
}
解释:
std::unique(a.begin(), a.end())
将序列变成{1, 2, 3, 4, 3, 3, 3, 4}
,并返回一个迭代器new_end
,它指向4
后的位置。a.erase(new_end, a.end())
删除了从new_end
到a.end()
的所有元素,最终容器a
只保留了{1, 2, 3, 4}
。
所以,unique
返回的迭代器是指向去重后序列最后一个有效元素的下一个位置。
四、a.erase(new_end, a.end());
erase
是 C++ STL 容器(如 std::vector
、std::list
)的成员函数,用于删除容器中的元素。它的用法有两个主要变体:
-
删除单个元素:
container.erase(iterator)
- 作用:删除指定位置的单个元素。
- 参数:一个指向要删除元素的迭代器。
- 返回值:指向被删除元素之后位置的迭代器。
-
删除范围内的元素:
container.erase(first, last)
- 作用:删除从
first
迭代器到last
迭代器之前的位置的所有元素(不包括last
)。 - 参数:两个迭代器,
first
是要开始删除的第一个元素的位置,last
是删除范围的末尾迭代器(不包括这个位置)。 - 返回值:指向
last
迭代器位置的元素之后的位置的迭代器。
- 作用:删除从
示例解释:
考虑如下代码:
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> a = {1, 1, 2, 2, 3, 3, 3, 4};
auto new_end = std::unique(a.begin(), a.end());
a.erase(new_end, a.end());
for (int i : a) {
std::cout << i << " ";
}
return 0;
}
-
std::unique
:将a
变成{1, 2, 3, 4, 3, 3, 3, 4}
,并返回指向序列中4
后的位置的迭代器new_end
。 -
a.erase(new_end, a.end())
:删除从new_end
到a.end()
之间的所有元素。new_end
是指向4
后的位置的迭代器。a.end()
是容器的末尾迭代器。
所以,这行代码删除了
{3, 3, 3, 4}
部分,最终容器a
变成{1, 2, 3, 4}
。
总结:erase(first, last)
通过指定范围来删除容器中的多个元素,非常有用来清理不再需要的部分。
五、count
在哈希表(如 C++ 中的 std::unordered_map
或 std::unordered_set
)中,count
方法用于确定某个元素的出现次数。哈希表的实现可以区分不同类型的数据结构,因此它们的行为在处理重复元素方面有所不同。
1. std::unordered_map
std::unordered_map
是一个基于哈希表的关联容器,存储键值对(key-value
)。在这种结构中,键(key
)是唯一的,但每个键对应的值(value
)可以是任意的。哈希表不会允许重复的键。
count
方法:- 对于
std::unordered_map
,count
方法通常用于检查某个键的出现次数。由于每个键在哈希表中是唯一的,count
方法的结果要么是0
(键不存在),要么是1
(键存在)。
- 对于
示例代码
#include <iostream>
#include <unordered_map>
int main() {
std::unordered_map<int, std::string> umap;
umap[1] = "one";
umap[2] = "two";
umap[3] = "three";
// 查询键 2 的出现次数
std::cout << "Count of key 2: " << umap.count(2) << std::endl; // 输出 1
// 查询不存在的键 4 的出现次数
std::cout << "Count of key 4: " << umap.count(4) << std::endl; // 输出 0
return 0;
}
2. std::unordered_set
std::unordered_set
是一个只存储键的哈希表容器,不存储与键关联的值。键在哈希表中必须唯一。
count
方法:- 对于
std::unordered_set
,count
方法用于检查某个元素是否存在于集合中。由于集合中的元素必须唯一,count
方法的结果要么是0
(元素不存在),要么是1
(元素存在)。
- 对于
示例代码
#include <iostream>
#include <unordered_set>
int main() {
std::unordered_set<int> uset = {1, 2, 3, 4, 5};
// 查询元素 3 的出现次数
std::cout << "Count of element 3: " << uset.count(3) << std::endl; // 输出 1
// 查询不存在的元素 6 的出现次数
std::cout << "Count of element 6: " << uset.count(6) << std::endl; // 输出 0
return 0;
}
3、std::vector
的 count
方法
std::vector
是一个动态数组,允许存储重复的元素。在 std::vector
中,元素的计数通常需要手动实现,因为标准库的 vector
没有直接提供 count
方法。你可以通过算法库中的函数来计算元素的出现次数,例如 std::count
。
示例代码
#include <iostream>
#include <vector>
#include <algorithm> // for std::count
int main() {
std::vector<int> vec = {1, 2, 2, 3, 4, 2, 5};
// 计算元素 2 的出现次数
int count = std::count(vec.begin(), vec.end(), 2);
std::cout << "Count of element 2: " << count << std::endl; // 输出 3
// 计算不存在的元素 6 的出现次数
count = std::count(vec.begin(), vec.end(), 6);
std::cout << "Count of element 6: " << count << std::endl; // 输出 0
return 0;
}
总结
std::unordered_map
中的count
方法检查的是键的存在性,每个键在哈希表中只能出现一次,因此count
的结果要么是0
(键不存在),要么是1
(键存在)。std::unordered_set
中的count
方法检查的是元素的存在性,每个元素在集合中只能出现一次,因此count
的结果要么是0
(元素不存在),要么是1
(元素存在)。- 在
std::vector
中,你需要使用算法函数(如std::count
)来统计元素的出现次数,因为vector
允许重复元素。
因此,这些容器的 count
方法用法和 std::vector
的处理方式不同,主要体现在它们是否允许重复元素以及如何查询这些元素的存在性。