C++:使用Vector实现动态数组——算法中的常用数据结构

在算法中,动态数组是一种常见的数据结构,而C++的STL中的Vector容器提供了方便的动态数组功能,用来代替我们之前在c语言中使用malloc()函数申请的动态数组。本文将介绍Vector容器的定义和常用操作,并通过一个实例来说明其使用方法和注意事项。

Vector的定义

Vector是STL提供的动态数组容器,能够根据需要在运行时改变数组大小。它以数组形式存储元素,并具有连续的内存空间,因此可以在常数时间内完成索引操作。下面是Vector的几种定义示例:

1. 默认初始化

默认情况下直接声明vector类型的变量,该变量为空

vector<int> a;
2. 实现复制另一个vector类型

需要使得构造函数为被复制的vector类型的对象

vector<int> b(a);
3. 100个值为0的元素

当构造函数的参数只有一个的时候,数量为该参数,vector的变量内的值的默认值为0

vector<int> a(100);
4. 100个值为6的元素

通过向构造函数传入两个参数,届时,第一个参数表示vector类型变量存储的数量,第二个参数表示该vector类型变量内的值的默认值

vector<int> a(100, 6);
5. 10个值为"null"的元素

通过修改vector的模板类型string,相当于定义string型数组,且每个元素的值都为构造函数中的第二个参数

vector<string> a(10, "null");
6. 10个值为"hello"的元素

规则同上

vector<string> vec(10, "hello");
7. 声明b是a的拷贝

通过向构造函数中传入vector类型的对象的begin()函数以及end()函数,使得a中的内容传入b

vector<string> b(a.begin(), a.end());
8. 定义结构体,用于存储坐标

vector作为一个模板类,可以存储任何其他类的对象,记得需要修改模板类的类型为对应类型
例如,可以申请vector类型用于存储二叉树,便于解决很多问题

struct point{
    int x, y;
};
vector<point> a;

除了一维数组,还可以定义多维数组,例如二维数组用于实现图的邻接表存储。

华点

1.有关第2项和第7项的区别

区别主要体现在两个方面,创建的容器类型以及复制方式不同,这里主要说的是复制方式不同:

  1. 数据类型:vector b(a.begin(), a.end()); 创建的 b 是一个 vector 类型的容器,而 vector b(a); 创建的 b 是一个 vector 类型的容器。因此,它们可以存储不同类型的元素。
  2. 复制方式:vector b(a.begin(), a.end()); 使用迭代器范围初始化 b,从 a 的起始迭代器 begin() 到结束迭代器 end(),复制了 a 中的所有元素到 b 中。而 vector b(a); 通过拷贝构造函数直接将容器 a 中的元素复制到 b 中。
2.有关vector类型的用法
  1. 存储多个二叉树:vector 可以作为容器来存储多个二叉树对象。每个 BinaryTree 对象代表一个独立的二叉树结构,通过 vector 可以方便地管理和操作这些二叉树。
  2. 遍历和搜索操作:使用 vector 可以轻松进行对多个二叉树的遍历和搜索操作。可以使用循环结构迭代访问每个二叉树,执行先序、中序、后序或层序遍历等操作。同时,也可以在 vector 中进行搜索,查找符合条件的二叉树。
  3. 批量处理和扩展:通过 vector 可以方便地对多个二叉树进行批量处理和扩展。例如,可以批量插入、删除、修改二叉树中的节点。同时,可以利用 vector 的动态增长特性,方便地动态扩展存储的二叉树数量。
  4. 管理和组织二叉树集合:使用 vector 可以方便地管理和组织二叉树的集合。可以进行排序、过滤、分组等操作,基于不同的需求将二叉树进行分类和组织。
    总的来说,vector 提供了一种方便灵活的数据结构,用于存储和处理多个二叉树,使得对二叉树进行批量操作和管理变得更加方便和高效。

Vector的常用操作

Vector提供了一系列常用的操作函数,下面是一些常用操作示例:

  • a.push_back(100);在容器尾部添加元素100
  • int size = a.size();获取容器内元素的个数并储存到size中
  • bool isEmpty = a.empty();判断Vector是否为空,注意返回值为true则表示空
  • cout << a[0] << endl;使用普通数组的方式,打印第一个元素
  • a.insert(a.begin() + i, k);在第i个元素前插入k
  • a.pop_back();删除末尾元素
  • a.erase(a.begin() + i, a.begin() + j);删除区间[i, j-1]的元素⭐(注意是j-1
  • a.erase(a.begin() + 2);删除第3个元素,从第0号开始
  • a.resize(n);调整数组大小为n
  • a.clear();清空Vector
  • reverse(a.begin(), a.end());翻转数组
  • sort(a.begin(), a.end());对数组进行排序(从小到大)

华点(内容较多谨慎选择,有很多lonely的问题)

1.使用迭代器和 end() 函数删除vector中的最后一个元素时,需要注意以下几点:
  1. 空向量检查:在删除最后一个元素之前,要确保 vector 不为空。可以使用 empty() 函数检查 vector 是否为空,以避免在空向量上调用删除操作导致的错误。
  2. 迭代器有效性:使用迭代器和 end() 函数删除最后一个元素时,确保迭代器仍然有效。在进行删除操作之前,可以使用 begin() 向导取得迭代器,并确保迭代器不等于 end()
  3. 删除操作:可以使用 erase() 函数将最后一个元素从 vector 中删除。要删除最后一个元素,只需要传递指向最后一个元素的迭代器作为参数,而不能直接传递end()函数,即应是 erase(end()-1)。这将从 vector 中移除最后一个元素,同时减小 vector 的大小。⭐(注意是end()-1
  4. 迭代器失效:删除最后一个元素后,原先指向最后一个元素的迭代器将失效。如果需要继续使用迭代器,应该避免在删除元素后使用失效的迭代器,以免引发未定义的行为。
    综上所述,在使用迭代器和 end() 删除 vector 中的最后一个元素之前,要确保 vector 不为空,迭代器有效且不等于 end(),并使用 erase() 函数进行删除操作。同时要注意处理迭代器的失效问题。
2.end() 函数,用于返回指向容器中最后一个元素之后的位置的迭代器,需要注意:
  1. 功能:end() 函数用于返回指向容器中最后一个元素之后的位置的迭代器,一般是指向容器尾部的下一个位置。

  2. 适用范围:end() 函数适用于几乎所有 C++ 标准库中的容器,如 vectorlistdequearraystring 等。在这些容器中,end() 函数返回的迭代器表示容器的末尾的下一个位置。

  3. 注意事项:

    • end() 返回的迭代器指向容器中的虚拟位置,是一个不可解引用(dereference)的迭代器。因此,不能在访问该迭代器指向的元素,否则会导致未定义的行为。⭐(注意不要进行解引用操作!!)
    • end() 函数返回的迭代器是一个尾后(past-the-end)迭代器,标志着容器的结束位置。在使用迭代器遍历容器时,通常是以 begin() 迭代器作为起始位置,end() 迭代器作为结束位置。⭐(迭代器结束的位置并不指向任何元素!!)
  4. 例子:

    vector<int> numbers = {1, 2, 3, 4, 5};
    auto iter = numbers.begin();  // 迭代器指向容器开始位置
    while (iter != numbers.end()) {
        cout << *iter << " ";  // 输出元素
        ++iter;
    }
    cout << endl;
    

    在上述例子中,numbers.end() 表示 numbers 容器中的最后一个元素之后的位置,用于循环遍历输出容器中的所有元素。注意,end() 函数返回的迭代器不参与实际遍历。
    总的来说,end() 函数是用于返回容器中最后一个元素之后位置的迭代器。在使用时,需注意该迭代器不能解引用,通常用于循环遍历容器中的元素或作为结束位置标志。
    在该例中使用了auto,这里的类型实际为vector::iterator,使用 auto 关键字声明的迭代器 iter 的类型会被自动推导出来,根据迭代器的初始化值确定。在这里,numbers.begin() 返回的是 vector 类型的迭代器,因此推导结果会是 auto 的类型与 vector::iterator 类型相同。在这个情况下,iter 的类型被推导为 vector::iterator,它是一个指向 vector 中元素的迭代器类型。根据所使用的容器类型不同,迭代器的类型也会有所不同。这个我们之后再说。

3.在使用vector过程中,还有一些其他需要注意的内容,包括:
  1. 增加元素引起迭代器失效:当在插入或删除元素时,会导致迭代器失效,因为容器的内部结构可能会发生改变。因此,在使用迭代器遍历 vector 时,需要特别小心不要在遍历过程中进行元素的插入或删除操作。如果必须需要进行这些操作,可以使用插入迭代器(std::insert_iterator)或删除/替换迭代器(std::erase_iterator)来处理。
  2. 使用下标访问:vector 支持使用下标运算符([])来访问元素。但应注意,使用下标访问时需要确保索引的有效范围,避免越界访问。
  3. 内存重新分配:当 vector 的存储空间不足以容纳新的元素时,vector 会自动进行内存重新分配,以扩大存储空间。然而,这个过程可能涉及元素的复制或移动,导致迭代器、引用和指针失效。因此,在进行大量插入或删除操作时,可能会产生较大的性能开销。为了避免频繁的内存重新分配,可以使用 reserve() 函数在插入元素之前预留一定的存储空间。
  4. 避免悬垂指针:当从 vector 中删除元素时,该元素可能被销毁,如果有其他指向该元素的指针,就会引起悬垂指针问题(dangling pointers)。要避免悬垂指针,可以在删除元素之前更新指向该元素的指针或使用智能指针等管理资源的方式。
  5. 使用算法和迭代器:vector 是标准库中最常用的容器之一,配合使用算法和迭代器可以方便地进行元素的遍历、查找、排序等操作。了解标准库提供的各种算法和迭代器有助于提高效率和简化代码。
4.插入迭代器(std::insert_iterator)和删除/替换迭代器(std::erase_iterator):

他们是 C++ 标准库中提供的迭代器适配器,用于在容器中进行插入、删除或替换操作时保持迭代器的有效性。

  1. 插入迭代器(std::insert_iterator):
    • std::insert_iterator 是一个迭代器适配器,用于在容器中指定位置之前插入元素。需要注意的是std::insert_iterator是唯一可用于关联式容器的插入迭代器。

    • 使用 std::insert_iterator 需要包含 头文件。

    • 可以通过 std::inserter() 函数创建 std::insert_iterator 对象。该函数接受一个容器和一个迭代器,并返回一个用于插入的迭代器。

    • 例如:

      #include 
      #include 
      std::vector<int> numbers {1, 2, 3};
      std::vector<int> more_numbers {4, 5, 6};
      std::copy(more_numbers.begin(), more_numbers.end(),
                std::inserter(numbers, numbers.begin())
                );
      

      在这个例子中,std::inserter(numbers, numbers.begin()) 返回一个使用 numbers.begin() 作为插入位置的 std::insert_iterator 对象。std::copy() 算法将 more_numbers 容器中的元素插入到 numbers 容器中。

    • 这里不可以提供普通的迭代器,std::copy 函数的目标是将 more_numbers 容器中的元素复制到 numbers 容器中。尝试把 std::inserter 替换为普通的迭代器,将输出的元素插入到 numbers 容器中,会发生编译错误。
      std::inserter 创建的是插入迭代器,它会在目标容器的给定位置之前插入元素。然而,给 std::copy 函数提供普通的迭代器时,编译器会尝试将输出元素复制到迭代器指向的位置,而不会自动处理插入操作。⭐(注意是之前的位置)
      还可以直接使用 std::back_inserter 创建一个插入迭代器,将输出的元素插入到 numbers 容器的末尾,如下所示:

      #include 
      #include 
      std::vector<int> numbers {1, 2, 3};
      std::vector<int> more_numbers {4, 5, 6};
      std::copy(more_numbers.begin(), more_numbers.end(),
                std::back_inserter(numbers)
                );
      

      当然还可以使用std::front_inserter创建一个插入迭代器,将输出的元素插入到 numbers 容器的前端,注意最后插入的元素会在容器的最前方。

    • std::copy 函数的用法如下:

      template<class InputIterator, class OutputIterator>
      OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result);
      

      参数说明:

      • firstlast 是输入范围的起始和结束迭代器,它们指定了要复制的元素范围。⭐(注意是输入范围)
      • result 是输出范围的起始迭代器,它指定了复制的元素将被放置的位置。

      使用 std::copy 函数时,它会将输入范围 [first, last) 中的元素复制到输出范围,该范围从迭代器 result 开始。注意,输出范围必须有足够的空间来接收复制的元素。⭐(不包括last迭代器,因此可以使用end()函数)

    • 定义std::insert_iterator类型迭代器的语法格式如下:

      std::insert_iterator<int> insert_it (container,it);
      
    • 参数 container 表示目标容器,而 it 是一个普通的迭代器,表示新元素的插入位置。C++ STL标准库中还提供了inserter()函数,可以快速创建 insert_iterator 类型迭代器。

    • 例如:

      #include 
      #include 
      #include 
      int main() {
          std::vector<int> foo(2, 5);
          std::vector<int>::iterator it = ++foo.begin();
          // 创建一个 insert_iterator 迭代器
          std::insert_iterator<std::vector<int>> insert_it = std::inserter(foo, it);
          insert_it = 1;
          insert_it = 2;
          insert_it = 3;
          insert_it = 4;
          for (std::vector<int>::iterator it = foo.begin(); it != foo.end(); ++it) {
              std::cout << *it << ' ';
          }
          return 0;
      }
      

      程序执行结果为:5 1 2 3 4 5
      直接对插入迭代器赋值,也可以实现插入的操作。
      需要注意的是,如果 insert_iterator 迭代器的目标容器是关联式容器,由于该类型容器会自动对存储的元素进行排序,因此我们指定的插入位置仅起到一个提示的作用。这个提示会帮助关联式容器从指定位置开始搜索正确的插入位置。然而,如果我们给出的提示位置不正确,可能会导致插入操作的效率变得更差。
      C++ STL 官方手册中有std::insert_iterator类型的迭代器底层实现的代码,这里不再展开。

  2. 删除/替换迭代器(std::erase_iterator):
    • std::erase_iterator 是一个迭代器适配器,用于在容器中删除或替换指定位置的元素。
    • 使用 std::erase_iterator 需要包含 头文件。
    • 可以通过 std::remove_if() 算法结合 std::erase_iterator 来删除容器中特定的元素。
    • 例如:
      #include 
      #include 
      #include 
      std::vector<int> numbers {1, 2, 3, 4, 5, 6};
      auto iter = std::remove_if(numbers.begin(), numbers.end(), 
                                 [](int n){
                                     return n % 2 == 0;
                                 }
                                 );
      numbers.erase(iter, numbers.end());
      
      在这个例子中,std::remove_if() 算法结合了一个 lambda 表达式,用于删除偶数元素。std::remove_if() 算法会返回一个指向修改后的范围末尾之后位置的迭代器,通过将这个迭代器和容器的 end() 迭代器传递给 numbers.erase() 函数,可以删除指定范围内的元素。

插入迭代器和删除/替换迭代器在进行插入、删除和替换操作时,能够保持迭代器的有效性。这意味着在使用这些迭代器适配器进行操作后,可以继续使用原始的迭代器进行后续操作,而无需担心迭代器失效的问题。有关插入迭代器和删除/替换迭代器的内容较为复杂,应多加练习来掌握。

4.reserve() 函数用于预留一定的存储空间,以避免频繁的内存重新分配:

函数签名如下:

void reserve(size_type new_capacity);

注意事项:

  1. new_capacity 是一个无符号整数类型,表示所需的新容器容量。
  2. reserve() 函数通常在向 vector 容器插入大量元素之前调用,以避免在插入操作时可能发生的多次内存重新分配。通过提前分配足够的存储空间,可以改善性能并减少内存重新分配的次数。
  3. 调用 reserve() 函数并不会影响 vector 的大小或元素个数,仅仅是预留内存空间。
  4. 如果 new_capacity 小于当前容器的 capacity(),则 reserve() 不会进行任何操作。因此,建议在需要增加容器容量时才调用 reserve()
  5. 如果 new_capacity 大于当前容器的 capacity(),则 reserve() 会重新分配足够的内存空间,使容器的容量达到至少 new_capacity 大小。
  6. 注意,重新分配内存可能导致容器中的迭代器、引用和指针失效,因此在调用 reserve() 后,之前获取的迭代器、引用和指针可能无法继续使用。

下面是使用 reserve() 函数的一个例子:

#include 
int main() {
    std::vector<int> numbers;
    numbers.reserve(100); // 预留至少可以容纳 100 个元素的存储空间
    // 插入大量元素到 vector 中
    for (int i = 0; i < 100; ++i) {
        numbers.push_back(i);
    }
    return 0;
}

在这个例子中,使用 reserve(100) 事先预留了至少可以容纳 100 个元素的存储空间。这样,在插入100个元素时,就可以避免因为频繁的内存重新分配而产生额外的开销。
使用 reserve() 函数可以根据预期的容器大小提前分配合适的存储空间,从而提高性能和效率,并且减少内存重新分配的次数。

5.reserve() 函数和 resize() 的区别:
  1. 功能不同:
    • reserve() 函数用于预留一定的存储空间,但并不会改变容器的大小或元素个数。它只影响容器的容量,确保容器能够容纳指定大小的元素。
    • resize() 函数用于改变容器的大小,可以增加或减少容器的元素个数。它会根据需要自动插入或删除元素。
  2. 参数不同:
    • reserve() 函数接受一个无符号整数类型的参数 new_capacity,表示所需的新容器容量。
    • resize() 函数接受一个无符号整数类型的参数 new_size,表示所需的容器大小。
  3. 影响不同:
    • reserve() 函数只影响容器的容量,不会改变容器的大小。它通常在插入大量元素之前进行调用,以避免频繁的内存重新分配。
    • resize() 函数会根据指定的大小改变容器的元素个数。如果 new_size 大于当前容器的大小,则会插入新元素,如果 new_size 小于当前容器的大小,则会删除多余的元素。
  4. 内存分配不同:
    • reserve() 函数主要是为了提前分配足够的存储空间,防止频繁的内存重新分配。
    • resize() 函数在需要增大容器大小时,会分配额外的内存空间;在需要减小容器大小时,会释放多余的内存空间。

所以,reserve()resize() 在功能和使用方式上有明显的区别。reserve() 主要用于预留存储空间,而 resize() 主要用于改变容器的大小,并可以同时插入或删除元素。根据具体的需求,选择合适的函数来操作 vector 容器。

实例:hdu 4841 “圆桌问题”

  • 题目来源:hdu 4841 圆桌问题 Hangzhou Dianzi University ACM Team

  • 题目:

    • Problem Description
      圆桌上围坐着2n个人。其中n个人是好人,另外n个人是坏人。如果从第一个人开始数数,数到第m个人,则立即处死该人;然后从被处死的人之后开始数数,再将数到的第m个人处死……依此方法不断处死围坐在圆桌上的人。试问预先应如何安排这些好人与坏人的座位,能使得在处死n个人之后,圆桌上围坐的剩余的n个人全是好人。

    • Input
      多组数据,每组数据输入:好人和坏人的人数n(<=32767)、步长m(<=32767);

    • Output
      对于每一组数据,输出2n个大写字母,‘G’表示好人,‘B’表示坏人,50个字母为一行,不允许出现空白字符。相邻数据间留有一空行。

    • Sample Input
      2 3
      2 4

    • Sample Output
      GBBG

      BGGB

  • 解题思路:

    • 使用 Vector 来模拟圆桌,通过赶走人的操作实现题目要求。
      vector<int> table;
      
    • 首先从输入中读取整数 n 和 m,分别表示人数和赶走的步数。
      int n, m;
      cin >> n >> m;
      
      在这段代码中,使用了流提取运算符 >> 来从标准输入流 cin 中读取数据。而 cin 对象在读取失败时会返回一个具有特殊含义的值。在这种情况下,当输入流无法正确读取到 n 和 m 时,即无法将它们转换为适当的类型(整数),cin 会返回一个 false 的值,此时循环条件将为 false,从而结束循环。
    • 创建一个空的 Vector 表示圆桌,并使用循环将人的编号依次添加到圆桌中,总共添加 2 * n 个元素。
      vector<int> table;
      table.clear();
      for (int i = 0; i < 2 * n; i++)
           table.push_back(i);
      
      这里使用table.clear()是确保多次循环,每次都清空Vector,使得容器不含任何元素。
    • 初始化变量 pos 为 0,表示当前位置。
      int pos = 0;
      
    • 通过循环,每次赶走一个坏人,直到赶走 n 个人为止。
         for (int i = 0; i < n; i++) {
             pos = (pos + m - 1) % table.size();
             table.erase(table.begin() + pos);
         }
      
      这段代码使用了一个 for 循环来进行坏人赶走的操作。for (int i = 0; i < n; i++):初始化一个循环变量 i,并设置循环的起始条件为 i = 0,循环继续的条件为 i < n,每次循环完成后对 i 进行递增操作,因此一共会赶走n个人。
      • 在每一次循环中,pos 的值更新为 (pos + m - 1) % table.size(),这是因为圆桌是一个环形结构,需要使用取余运算实现循环。若步长为2,即我们需要赶走的就是第二个人,他所在的位置则是2-1。因此这里需要使用(pos + m -1)使得pos始终表示元素的下标。注意题目所说为(从第一个人开始数数,数到第m个人,而不是间隔m人)。
        pos = (pos + m - 1) % table.size();:将当前位置 pos 更新为 (pos + m - 1) % table.size()。这一行代码用于确定下一个将要赶走的坏人的位置。根据题目要求,每次赶走一个人后,下一个要赶走的人是当前位置加上步数 m 再减去1,并对 table.size() 取余,以实现环形移动。
      • 通过 table.erase(table.begin() + pos) 删除指定位置上的元素(即赶走坏人),此时 table 容器的人数减少了 1。
        table.erase(table.begin() + pos);:从 table 容器中删除指定位置的元素。在这里,table.begin() + pos 表示一个指向要删除的元素的迭代器,table.erase() 函数用于删除该位置上的元素。这一行代码实现了将坏人赶走的操作,即从 table 容器中删除指定位置的元素。要注意的是,table.erase() 操作会直接改变容器的大小,并删除指定位置或范围内的元素。因此会直接改变序列,使得容器内存储的元素不再和其下标相等。后续我们可以通过比较下标以及元素来判断是否进行过删除操作,即可以判断是好人还是坏人。
      • 继续循环下一个位置的赶走操作,直到赶走 n 个人。
    • 接下来,根据题目要求输出预先安排好的座位。使用变量 j 记录 table 容器中的人的索引。
      int j = 0;
      
    • 再次通过循环,依次遍历 table 的元素,并判断当前位置是否为好人的位置。
      for (int i = 0; i < 2 * n; i++) {
           if (!(i % 50) && i)
               cout << endl;
           if (j < table.size() && i == table[j]) {
               j++;
               cout << "G";
           } else {
               cout << "B";
           }
       }
      
      这段代码用于输出预先安排好的座位序列,其中 “G” 表示好人,“B” 表示坏人:
      • for (int i = 0; i < 2 * n; i++):初始化一个循环变量 i,并设置循环的起始条件为 i = 0,循环继续的条件为 i < 2 * n,每次循环完成后对 i 进行递增操作。
      • if (!(i % 50) && i):该条件判断用于判断是否需要在输出的行末添加换行符。当 i 是 50 的倍数(即 i % 50 的结果为 0)且 i 不等于 0 时,执行该条件判断为真,将输出一个换行符。这是为了让每行输出的字符数量限制在 50 个以内。这么操作是因为题目进行了输出格式的要求,即50个字母为一行。
      • if (j < table.size() && i == table[j]):该条件判断用于确定当前位置是否是好人的位置。j 是记录 table 容器中的人的索引,table[j] 表示当前位置上的人的编号。(在前面的操作中,我们已经使用循环让每一项的值都成为该项的编号了)如果 j < table.size()(即 j 小于 table 容器的大小)且 i 等于 table[j],表示当前位置是好人的位置,执行该条件判断为真。 也就是之前我们删除操作中没有删除的人。
      • j++⭐(循环内的参数很重要)
        当遇到 j++ 时,如果当前位置是好人的位置,在输出 “G” 后,将 j 递增,指向下一个好人的位置。
        当遇到 cout << "G" 时,输出字符 “G”,表示好人。
        当遇到 cout << "B" 时,输出字符 “B”,表示坏人。
        在判断下一个位置时,如果不是好人,容器内的元素和下标将不会相等。这是因为在之前操作中已经将坏人删除,导致不相等的情况发生。
        因此, 这时候我们就可以判断出该位置是坏人所在的位置。输出坏人。
        在这种情况下,不需要执行 j++ 操作,循环继续进行,i++,继续判断容器中下标为 j 的元素是否与 i 相等。
        由于之前使用 table.erase() 操作,移除了元素,导致下标与元素的值不一致。元素的值代表之前的坐标 j
        可以这样理解:之前删除的是坏人的下标。所以,当我们发现 j 在从0开始遍历时找不到对应的下标时,我们就可以判断出这个位置已经被删除,因此这里存放的应该是坏人。 因为我们操作之后容器内存放的全部为好人。 因此,我们在操作的时候只需要继续循环就可以,不用对标记的下标j进行操作。
      • 最后,输出两个换行符,以保留空行。
      • 重复上述步骤,直到读取到的输入为结束符。
  • 总代码:

#include 
using namespace std;

int main() {
    vector<int> table;
    int n, m;
    while (cin >> n >> m) {
        table.clear();
        for (int i = 0; i < 2 * n; i++)
            table.push_back(i);
        int pos = 0;
        for (int i = 0; i < n; i++) {
            pos = (pos + m - 1) % table.size();
            table.erase(table.begin() + pos);
        }
        int j = 0;
        for (int i = 0; i < 2 * n; i++) {
            if (!(i % 50) && i)
                cout << endl;
            if (j < table.size() && i == table[j]) {
                j++;
                cout << "G";
            } else {
                cout << "B";
            }
        }
        cout << endl << endl;
    }
    return 0;
}

这就是有关C++ STL库中vector的一些基础内容。

参考资料:

C++ STL插入迭代器适配器(insert_iterator) C语言中文网
算法竞赛入门到进阶 罗勇军
Problem - 4841 Hangzhou Dianzi University Online Judge 3.0

你可能感兴趣的:(算法,c++,算法,数据结构)