C++:顺序容器

顺序容器

  • 向量(vector)

  • 双端队列(deque)

  • 列表(list)

  • 单向链表(forward_list) (以上四种在逻辑上可看作是一个长度可扩展的数组)

  • 数组(array)

  • 元素线性排列,可以随时在指定位置插入元素和删除元素。

  • 必须符合Assignable这一概念(即具有公有的拷贝构造函数并可以用“=”赋值)。

  • array对象的大小固定,forward_list有特殊的添加和删除操作。

顺序容器的接口(不包含单向链表(forward_list)和数组(array))

  • 构造函数

  • 赋值函数

    • assign
  • 插入函数

    • insert, pushfront(只对list和deque), pushback,emplace,emplace_front
  • 删除函数

    • erase,clear,popfront(只对list和deque) ,popback,emplace_back
  • 首尾元素的直接访问

    • front,back
  • 改变大小

    • resize

顺序容器的基本操作

#include 
#include 
#include 
using namespace std;

//输出指定的顺序容器的元素
template <class T>
void printContainer(const char* msg, const T& s) {
    cout << msg << ": ";
    copy(s.begin(), s.end(), ostream_iterator<int>(cout, " "));
    cout << endl;
}

int main() {
    //从标准输入读入10个整数,将它们分别从s的头部加入
    deque<int> s;
    for (int i = 0; i < 10; i++) {
        int x;
        cin >> x;
        s.push_front(x);
    }
    printContainer("deque at first", s);
    //用s容器的内容的逆序构造列表容器l
    list<int> l(s.rbegin(), s.rend());
    printContainer("list at first", l);

    //将列表容器l的每相邻两个元素顺序颠倒
    list<int>::iterator iter = l.begin();
    while (iter != l.end()) {
        int v = *iter;  
        iter = l.erase(iter);
        l.insert(++iter, v);
    }
    printContainer("list at last", l);
    //用列表容器l的内容给s赋值,将s输出
    s.assign(l.begin(), l.end());
    printContainer("deque at last", s);
    return 0;
}
/*  
运行结果如下: 
0 9 8 6 4 3 2 1 5 4
deque at first: 4 5 1 2 3 4 6 8 9 0
list at first: 0 9 8 6 4 3 2 1 5 4
list at last: 9 0 6 8 3 4 1 2 4 5
deque at last: 9 0 6 8 3 4 1 2 4 5
/*

顺序容器的特性

  • 顺序容器:向量、双端队列、列表、单向链表、数组

  • 向量(Vector)

    • 容量(capacity):实际分配空间的大小

    • s.capacity() :返回当前容量

    • s.reserve(n):若容量小于n,则对s进行扩展,使其容量至少为n

    • 一个可以扩展的动态数组

    • 随机访问、在尾部插入或删除元素快

    • 在中间或头部插入或删除元素慢

    • 特点

  • 向量的容量

    • 双端队列(deque)

    • 在两端插入或删除元素快

    • 在中间插入或删除元素慢

    • 随机访问较快,但比向量容器慢

    • 特点

奇偶排序

#include
#include
#include
#include
#include
using namespace std;

int main() {
    istream_iterator<int> i1(cin), i2;  //建立一对输入流迭代器
    vector<int> s1(i1, i2); //通过输入流迭代器从标准输入流中输入数据
    sort(s1.begin(), s1.end()); //将输入的整数排序
    deque<int> s2;
    //以下循环遍历s1
    for (vector<int>::iterator iter = s1.begin(); iter != s1.end(); ++iter) 
    {
         if (*iter % 2 == 0)    //偶数放到s2尾部
             s2.push_back(*iter);
         else       //奇数放到s2首部
             s2.push_front(*iter);
    }
    //将s2的结果输出
    copy(s2.begin(), s2.end(), ostream_iterator<int>(cout, " "));
    cout << endl;
    return 0;
}

列表(list)

  • 特点

    • 在任意位置插入和删除元素都很快

    • 不支持随机访问

  • 接合(splice)操作

    • s1.splice(p, s2, q1, q2):将s2中[q1, q2)移动到s1中p所指向元素之前
#include
#include
#include
#include
using namespace std;

int main() {
    string names1[] = { "Alice", "Helen", "Lucy", "Susan" };
    string names2[] = { "Bob", "David", "Levin", "Mike" };
    //用names1数组的内容构造列表s1
    list<string> s1(names1, names1 + 4); 
    //用names2数组的内容构造列表s2
    list<string> s2(names2, names2 + 4); 

    //将s1的第一个元素放到s2的最后
    s2.splice(s2.end(), s1, s1.begin());
    list<string>::iterator iter1 = s1.begin(); //iter1指向s1首
    advance(iter1, 2); //iter1前进2个元素,它将指向s1第3个元素
    list<string>::iterator iter2 = s2.begin();  //iter2指向s2首
    ++iter2; //iter2前进1个元素,它将指向s2第2个元素
    list<string>::iterator iter3 = iter2; //用iter2初始化iter3
    advance(iter3, 2); //iter3前进2个元素,它将指向s2第4个元素
    //将[iter2, iter3)范围内的结点接到s1中iter1指向的结点前
    s1.splice(iter1, s2, iter2, iter3); 

    //分别将s1和s2输出
    copy(s1.begin(), s1.end(), ostream_iterator<string>(cout, " "));
    cout << endl;
    copy(s2.begin(), s2.end(), ostream_iterator<string>(cout, " "));
    cout << endl;
    return 0;
}

单向链表(forward_list)

  • 单向链表每个结点只有指向下个结点的指针,没有简单的方法来获取一个结点的前驱;

  • 未定义insert、emplace和erase操作,而定义了insertafter、emplaceafter和erase_after操作,其参数与list的insert、emplace和erase相同,但并不是插入或删除迭代器p1所指的元素,而是对p1所指元素之后的结点进行操作;

  • 不支持size操作。

数组(array)

  • array是对内置数组的封装,提供了更安全,更方便的使用数组的方式

  • array的对象的大小是固定的,定义时除了需要指定元素类型,还需要指定容器大小。

  • 不能动态地改变容器大小

顺序容器的比较

  • STL所提供的顺序容器各有所长也各有所短,我们在编写程序时应当根据我们对容器所需要执行的操作来决定选择哪一种容器。

  • 如果需要执行大量的随机访问操作,而且当扩展容器时只需要向容器尾部加入新的元素,就应当选择向量容器vector;

  • 如果需要少量的随机访问操作,需要在容器两端插入或删除元素,则应当选择双端队列容器deque;

  • 如果不需要对容器进行随机访问,但是需要在中间位置插入或者删除元素,就应当选择列表容器list或forward_list;

  • 如果需要数组,array相对于内置数组类型而言,是一种更安全、更容易使用的数组类型。

顺序容器的插入迭代器与适配器

顺序容器的插入迭代器

  • 用于向容器头部、尾部或中间指定位置插入元素的迭代器

  • 包括前插迭代器(frontinserter)、后插迭代器(backinsrter)和任意位置插入迭代器(inserter)

list<int> s;
back_inserter iter(s);
*(iter++) = 5; //通过iter把5插入s末尾

顺序容器的适配器

以顺序容器为基础构建一些常用数据结构,是对顺序容器的封装

  • 栈(stack):最先压入的元素最后被弹出

  • 队列(queue):最先压入的元素最先被弹出

  • 优先级队列(priority_queue):最“大”的元素最先被弹出

栈和队列模板

  • 栈模板
template <class T, class Sequence = deque<T> > class stack;
  • 队列模板
template <class T, class FrontInsertionSequence = deque<T> > class queue;
  • 栈可以用任何一种顺序容器作为基础容器,而队列只允许用前插顺序容器(双端队列或列表)

栈和队列共同支持的操作

  • s1 op s2 op可以是==、!=、<、<=、>、>=之一,它会对两个容器适配器之间的元素按字典序进行比较

  • s.size() 返回s的元素个数

  • s.empty() 返回s是否为空

  • s.push(t) 将元素t压入到s中

  • s.pop() 将一个元素从s中弹出,对于栈来说,每次弹出的是最后被压入的元素,而对于队列,每次被弹出的是最先被压入的元素

  • 不支持迭代器,因为它们不允许对任意元素进行访问

栈和队列不同的操作

栈的操作

  • s.top() 返回栈顶元素的引用

队列操作

  • s.front() 获得队头元素的引用

  • s.back() 获得队尾元素的引用

利用栈反向输出单词

#include
#include
#include
using namespace std;

int main() {
    stack<char> s;
    string str;
    cin >> str; //从键盘输入一个字符串
    //将字符串的每个元素顺序压入栈中
    for (string::iterator iter = str.begin(); iter != str.end(); ++iter)
        s.push(*iter);
    //将栈中的元素顺序弹出并输出
    while (!s.empty()) {
        cout << s.top();
        s.pop();
    }
    cout << endl;
    return 0;
}
/*运行结果如下:
congratulations
snoitalutargnoc*/

优先级队列

  • 优先级队列也像栈和队列一样支持元素的压入和弹出,但元素弹出的顺序与元素的大小有关,每次弹出的总是容器中最“大”的一个元素。
template <class T, class Sequence = vector<T> > class priority_queue;
  • 优先级队列的基础容器必须是支持随机访问的顺序容器。

  • 支持栈和队列的size、empty、push、pop几个成员函数,用法与栈和队列相同。

  • 优先级队列并不支持比较操作。

  • 与栈类似,优先级队列提供一个top函数,可以获得下一个即将被弹出元素(即最“大”的元素)的引用。

细胞分裂模拟
一种细胞在诞生(即上次分裂)后会在500到2000秒内分裂为两个细胞,每个细胞又按照同样的规律继续分裂。

#include
#include
using namespace std;
const int SPLIT_TIME_MIN = 500;    //细胞分裂最短时间
const int SPLIT_TIME_MAX = 2000;  //细胞分裂最长时间

class Cell;
priority_queue<Cell> cellQueue;

class Cell {    //细胞类
private:
    static int count;   //细胞总数
    int id;     //当前细胞编号
    int time;   //细胞分裂时间
public:
    Cell(int birth) : id(count++) { //birth为细胞诞生时间
        //初始化,确定细胞分裂时间
        time = birth + (rand() % (SPLIT_TIME_MAX - SPLIT_TIME_MIN))+ SPLIT_TIME_MIN;
    }
    int getId() const { return id; }        //得到细胞编号
    int getSplitTime() const { return time; }   //得到细胞分裂时间
    bool operator < (const Cell& s) const      //定义“<”
    { return time > s.time; }
    void split() {  //细胞分裂
        Cell child1(time), child2(time);    //建立两个子细胞
        cout << time << "s: Cell #" << id << " splits to #" << child1.getId() << " and #" << child2.getId() << endl;
        cellQueue.push(child1); //将第一个子细胞压入优先级队列
        cellQueue.push(child2); //将第二个子细胞压入优先级队列
    }
};
int Cell::count = 0;

int main() {
    srand(static_cast<unsigned>(time(0)));
    int t;  //模拟时间长度
    cout << "Simulation time: ";
    cin >> t;
    cellQueue.push(Cell(0));    //将第一个细胞压入优先级队列
    while (cellQueue.top().getSplitTime() <= t) {
        cellQueue.top().split();    //模拟下一个细胞的分裂
        cellQueue.pop();    //将刚刚分裂的细胞弹出
    }
    return 0;
}
/*
运行结果如下:
Simulation time: 5000
971s: Cell #0 splits to #1 and #2
1719s: Cell #1 splits to #3 and #4
1956s: Cell #2 splits to #5 and #6
2845s: Cell #6 splits to #7 and #8
3551s: Cell #3 splits to #9 and #10
3640s: Cell #4 splits to #11 and #12
3919s: Cell #5 splits to #13 and #14
4162s: Cell #10 splits to #15 and #16
4197s: Cell #8 splits to #17 and #18
4317s: Cell #7 splits to #19 and #20
4686s: Cell #13 splits to #21 and #22
4809s: Cell #12 splits to #23 and #24
4818s: Cell #17 splits to #25 and #26
*/

你可能感兴趣的:(C++,顺序容器)