【C++之STL】013序列容器篇list特殊的更易型操作

c.unique()

在C++中,std::list是一个双向链表容器,它提供了许多成员函数来操作链表。其中,unique是std::list的一个成员函数,用于删除链表中的重复元素。

unique函数的用法很简单,它接受一个可选的二元谓词(binary predicate)作为参数,用于定义如何比较两个元素是否相等。如果未提供二元谓词,则默认使用std::equal_to来比较元素。

以下是unique函数的基本用法:

#include <iostream>
#include <list>
#include <algorithm>

int main() {
    std::list<int> mylist = {1, 2, 2, 3, 4, 4, 5};

    // 删除重复元素
    mylist.unique();

    // 输出链表中的元素
    for (int x : mylist) {
        std::cout << x << ' ';
    }

    return 0;
}

在这个例子中,mylist是一个包含重复元素的链表。调用mylist.unique()后,链表中的重复元素将被删除,只保留一个。最后,使用范围for循环遍历链表并输出其中的元素,输出结果为1 2 3 4 5。

如果需要自定义比较两个元素是否相等的方式,可以提供一个二元谓词作为unique函数的参数。例如,以下代码演示了如何删除链表中所有相邻且差值为1的重复元素:

#include <iostream>
#include <list>
#include <algorithm>

bool is_adjacent_duplicate(int a, int b) {
    return std::abs(a - b) == 1;
}

int main() {
    std::list<int> mylist = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    // 删除相邻且差值为1的重复元素
    mylist.unique(is_adjacent_duplicate);

    // 输出链表中的元素
    for (int x : mylist) {
        std::cout << x << ' ';
    }

    return 0;
}

在这个例子中,我们定义了一个名为is_adjacent_duplicate的二元谓词,它检查两个元素之间的差值是否为1。然后,我们将这个谓词作为参数传递给unique函数,以便删除所有相邻且差值为1的重复元素。最后,输出链表中的元素,结果为1 3 5 7 9。

c.splice()

std::list::splice 是 C++ 标准库中的 std::list 容器的一个成员函数,用于重新组织列表中的元素。它可以将一个列表中的元素移动到另一个列表的指定位置,或者合并两个列表。

以下是 std::list::splice 的函数声明:

void splice(const_iterator position, list& x);
void splice(const_iterator position, list&& x) noexcept; // (C++11 起)
void splice(const_iterator position, list& x, const_iterator i);
void splice(const_iterator position, list& x, const_iterator first, const_iterator last);

详细用法

splice(const_iterator position, list& x)

将列表 x 中的所有元素移动到调用列表的 position 位置之前。x 列表在移动后变为空。

std::list<int> list1 = {1, 2, 3};
std::list<int> list2 = {4, 5, 6};

list1.splice(list1.begin(), list2); // list2 中的元素移动到 list1 开头
// list1 现在包含 {4, 5, 6, 1, 2, 3}
// list2 现在为空

splice(const_iterator position, list&& x) noexcept (C++11)

将移动列表 x 中的所有元素到调用列表的 position 位置之前,并将 x 置于有效但未指定的状态(通常是空列表)。

std::list<int> list1 = {1, 2, 3};
std::list<int> list2 = {4, 5, 6};

list1.splice(list1.begin(), std::move(list2)); // list2 的元素移动到 list1 开头,list2 变为有效但未指定的状态
// list1 现在包含 {4, 5, 6, 1, 2, 3}
// list2 现在处于有效但未指定的状态

splice(const_iterator position, list& x, const_iterator i)

将列表 x 中由迭代器 i 指向的元素移动到调用列表的 position 位置之前。

std::list<int> list1 = {1, 2, 3};
std::list<int> list2 = {4, 5, 6};

auto it = list2.begin(); // 指向 list2 的第一个元素
list1.splice(list1.begin(), list2, ++it); // list2 的第二个元素(值为 5)移动到 list1 开头
// list1 现在包含 {5, 1, 2, 3}
// list2 现在包含 {4, 6}

splice(const_iterator position, list& x, const_iterator first, const_iterator last)

将列表 x 中由范围 [first, last) 指定的元素移动到调用列表的 position 位置之前。

std::list<int> list1 = {1, 2, 3};
std::list<int> list2 = {4, 5, 6, 7, 8};

auto it_first = list2.begin();     // 指向 list2 的第一个元素
auto it_last = std::next(it_first, 3); // 指向 list2 的第四个元素
list1.splice(list1.begin(), list2, it_first, it_last); // list2 中的前三个元素移动到 list1 开头
// list1 现在包含 {4, 5, 6, 1, 2, 3}
// list2 现在包含 {7, 8}

注意事项:

  • 有效性: splice 操作后,原列表(即被移动元素所在的列表)的迭代器可能不再有效,因为元素已经被移动。

  • 异常安全性: splice 操作是异常安全的,这意味着如果在操作过程中抛出异常,列表的状态将保持不变。

  • 性能: 由于 splice 是在内部实现的,它通常比使用 insert 和 erase 来移动元素更高效,因为它不涉及复制或移动元素,而是直接重新链接节点。

  • 空列表: 如果将一个空列表的所有元素移动到另一个列表,或者移动一个空范围,则操作仍然有效,但不会有任何元素被移动。

  • 自移动: 尝试将一个列表的元素移动到它自身通常是不安全的,因为这可能导致迭代器失效和未定义的行为。尽管标准库不禁止这种操作,但通常应该避免它。

  • 移动语义: 在 C++11 及更高版本中,splice 提供了移动语义的重载版本,可以高效地将一个列表的所有元素移动到另一个列表。这减少了不必要的复制操作,提高了性能。

总的来说,std::list::splice 是一个强大而灵活的工具,用于在 std::list 容器之间重新组织元素。正确使用它可以提高代码的效率并简化逻辑。

c.sort()

std::list 容器在 C++ 标准库中提供了 sort 成员函数,用于对列表中的元素进行排序。sort 函数使用默认的比较运算符(<)对元素进行升序排序,但你也可以提供自定义的比较函数或 lambda 表达式来改变排序的行为。

以下是 std::list::sort 的函数声明:

void sort();
template< class Compare >
void sort( Compare comp );

详细用法

默认排序(sort())

当调用 sort() 时,列表中的元素会使用默认的比较运算符(<)进行排序。这意味着列表中的元素类型必须支持这个运算符。

std::list<int> numbers = {4, 2, 5, 1, 3};
numbers.sort(); // 对 numbers 进行升序排序

// 输出排序后的列表
for (int num : numbers) {
    std::cout << num << ' ';
}
// 输出: 1 2 3 4 5

自定义比较排序(sort(Compare comp))

如果你想要根据特定的比较逻辑对列表进行排序,你可以提供一个自定义的比较函数或 lambda 表达式。这个函数或表达式应该接受两个参数(即列表中的两个元素),并返回一个布尔值,指示第一个参数是否应该在排序后位于第二个参数之前。

#include <list>
#include <iostream>
#include <algorithm> // 为了使用 std::next_permutation

bool compareDescending(int a, int b) {
    return a > b; // 降序排序
}

int main() {
    std::list<int> numbers = {4, 2, 5, 1, 3};

    // 使用自定义比较函数进行降序排序
    numbers.sort(compareDescending);

    // 输出排序后的列表
    for (int num : numbers) {
        std::cout << num << ' ';
    }
    // 输出: 5 4 3 2 1

    return 0;
}

使用 lambda 表达式进行排序的例子:

std::list<std::string> words = {"apple", "banana", "cherry"};

// 使用 lambda 表达式进行字典序排序
words.sort([](const std::string& a, const std::string& b) {
    return a < b;
});

// 输出排序后的列表
for (const std::string& word : words) {
    std::cout << word << ' ';
}
// 输出: apple banana cherry

注意点

  • sort 函数会就地(in-place)对列表进行排序,这意味着列表本身会被修改,而不是创建一个新的排序列表。
  • 排序操作的复杂度通常为 O(N log N),其中 N 是列表中的元素数量。
  • 如果提供了自定义比较函数或 lambda 表达式,它应该定义一个严格的弱序(strict weak ordering),即如果 comp(a, b) 为真,那么 comp(b, a) 必须为假,并且对于任何 a,comp(a, a) 必须为假。
  • 在多线程环境中,如果列表被多个线程同时修改,排序操作可能是不安全的。需要确保在排序期间列表不会被其他线程修改。

c.merge()

std::list::merge 是 C++ 标准库中 std::list 容器的一个成员函数,它用于合并两个已排序的列表。该函数将两个列表合并成一个单一的有序列表,并且原始列表在合并操作后不再存在。

以下是 std::list::merge 的函数声明:

void merge(list& x);
template< class Compare >
void merge(list& x, Compare comp);

详细用法

默认合并(merge(list& x))

当调用 merge 而没有提供比较函数时,它假设两个列表都已经按照默认的比较运算符(<)排序。它会将两个列表合并成一个单一的有序列表,保持原有的排序顺序。

#include <iostream>
#include <list>

int main() {
    std::list<int> list1 = {1, 3, 5};
    std::list<int> list2 = {2, 4, 6};

    // 合并两个已排序的列表
    list1.merge(list2);

    // 输出合并后的列表
    for (int num : list1) {
        std::cout << num << ' ';
    }
    // 输出: 1 2 3 4 5 6

    return 0;
}

自定义比较合并(merge(list& x, Compare comp))

如果你想要根据自定义的比较逻辑来合并两个列表,你可以提供一个比较函数或 lambda 表达式。这个函数应该接受两个参数(即来自两个列表的元素),并返回一个布尔值,指示第一个参数是否应该在排序后位于第二个参数之前。

#include <iostream>
#include <list>

struct Compare {
    bool operator()(const int& a, const int& b) {
        // 自定义比较逻辑,例如降序排序
        return a > b;
    }
};

int main() {
    std::list<int> list1 = {1, 3, 5};
    std::list<int> list2 = {2, 4, 6};

    // 使用自定义比较函数进行合并(降序)
    list1.merge(list2, Compare());

    // 输出合并后的列表
    for (int num : list1) {
        std::cout << num << ' ';
    }
    // 输出: 6 5 4 3 2 1

    return 0;
}

注意事项

  • merge 操作会改变调用它的列表,并且会销毁传入的列表 x。合并后的元素将存储在调用 merge 的列表中。
  • 如果两个列表中有重复的元素,merge 操作会保留它们,并按照它们在各自列表中的相对顺序进行排序。
  • 自定义比较函数应该定义严格的弱序(strict weak ordering),以保证合并操作的正确性。
  • 在多线程环境中,如果列表被多个线程同时修改,合并操作可能是不安全的。需要确保在合并期间列表不会被其他线程修改。
  • merge 操作的复杂度通常为线性,即 O(N + M),其中 N 和 M 分别是两个列表中的元素数量。

c.reverse()

std::list 容器在 C++ 标准库中提供了 reverse 成员函数,用于反转列表中的元素顺序。这是一个就地(in-place)操作,意味着它直接修改列表本身,而不是创建一个新的反转后的列表。

以下是 std::list::reverse 的函数声明:

void reverse();

详细用法

reverse 函数非常简单,不需要任何参数。调用它之后,列表中的元素顺序将被反转。

#include <iostream>
#include <list>

int main() {
    std::list<int> numbers = {1, 2, 3, 4, 5};

    // 输出原始列表
    std::cout << "Original list: ";
    for (int num : numbers) {
        std::cout << num << ' ';
    }
    std::cout << std::endl;

    // 反转列表
    numbers.reverse();

    // 输出反转后的列表
    std::cout << "Reversed list: ";
    for (int num : numbers) {
        std::cout << num << ' ';
    }
    std::cout << std::endl;

    return 0;
}

输出

Original list: 1 2 3 4 5 
Reversed list: 5 4 3 2 1

注意事项

  • reverse 操作的时间复杂度通常是 O(N),其中 N 是列表中的元素数量。这是因为需要遍历列表中的每个元素一次以完成反转。
  • reverse 是一个就地操作,它会直接修改调用它的列表,而不是创建一个新的反转列表。
  • 由于 reverse 仅仅交换了元素的位置,而不是它们的值,所以它不需要元素的类型支持任何特定的操作或函数。
  • 在多线程环境中,如果列表被多个线程同时修改,反转操作可能是不安全的。需要确保在反转期间列表不会被其他线程修改。
  • reverse 函数不接受任何参数,因此无法根据自定义的比较函数来反转列表。它总是按照元素在列表中的当前顺序进行反转。
  • 30
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熊猫Devin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值