前言
这一篇博客主要是针对C++语言中的几个概念进行分析
- 容器
- 队列
- 栈
- 链表
- set
- map
STL(Standerd Template Library)是C++的标准模板库,一些常用的数据结构,算法在STL中都有,熟练的掌握它们可以很方便的帮助我们编写代码。
STL包含容器(container)、迭代器(iterator)、空间配置器(allocator)、配接器(adapter)、算法(algorithm)、仿函数(functor)6个部分。这篇博客主要介绍容器和两个常用的算法。
容器
STL容器包括顺序式容器和关联式容器。
顺序式容器
顺序式容器包括vector、list、deque、queue、priority_queue、stack等,他们的特点如下。
- vector:动态数组,从末尾能快速插入与删除,直接访问任何元素
- list:双链表。从任何地方快速插入与删除。
- deque:双向列表,从前面或后面快速插入与删除,直接访问任何元素。
- queue:队列,先进先出。
- priority_queue:优先队列,最高优先级元素总是第一个出列
- stack:栈,后进先出。
关联式容器
- set:集合,快速查找,不允许重复值、
- multiset:快速查找,允许重复值。
- map:一对多映射,基于关键字快速查找,不允许重复值
- multimap:一对多映射,基于关键字快速查找,允许重复值。
Vector
数组是基本的数据结构,有静态数组和动态数组两种类型。一般在算法竞赛中,编码的惯例是:如果空间充足,能够用静态数组就用静态数组,而不用指针管理动态数组,这样编码比较简单并且不会出错;如果空间紧张,可以使用STL的vector建立动态数组,不仅节约空间,而且不易出错。
vector是STL的动态数组,在运行时能根据需要改变数组大小。由于它以数组形式存储,也就是说它的内存空间是连续的,所以索引可以在常数时间内完成。但是在中间进行插入和删除会造成内存块的复制。另外,如果数组后面的内存空间不够,需要重新申请一块足够大的内存。这些都会影响vector的效率.
vector容器是一个模板类,能存放任何类型的对象。
定义
如下表:
用户还可以定义多维数组,例如定义一个二维数组:
vectora[MAXN];
它的一维大小是固定的MAXN,第二维是动态的。用这个方式可以实现图的邻接表存储。
常见操作
vector的常用操作如下表
案例:圆桌问题
圆桌边围坐着2n个人。其中n个是好人,另外n个是坏人。从第一个
人开始数,数到第m个人,立即赶走该人;然后从被赶走的人之后
开数,再数到的第m个人赶走,依此方法不断赶走围坐在圆桌边的
人。预先应如何安排这些好人与坏人的座位,才能使得在赶走n个人
之后圆桌边围坐的剩余的n个人都是好人?
输入:多组数据,每组数据输入:n,m<=32767
输出:对于每一组数据,输出2n个大写字母,"G"表示好人,
"B"表示坏人,50个字母为一行,不允许出现空白字符。
相邻数据间留有一个空行。
输入样例:
2 3
2 4
输出样例:
GBBG
BGGB
这个题目是约瑟夫问题。用vector模拟动态数组变化的圆桌,赶走n个人之后留下的都是好人
源码如下
#include<iostream>
#include<vector>
using namespace std;
using std::vector;
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); //初始化数组,在数组table后依次插入数值为i的元素
}
int pos = 0;//记录当前位置
for (int i = 0; i < n; i++) {//开始数数,赶走n个人
pos = (pos + m - 1) % table.size();//根据索引直接找到第m个人,
//而圆桌是个环所以进行取余处理
table.erase(table.begin() + pos);//赶走第pos个人,table人数减一
}
int j = 0;
for (int i = 0; i < 2 * n; i++) {//打印预先安排座位
if (!(i % 50) && i) {
cout << endl; //50个字母为1行
}
if (j < table.size() && i == table[j]) {//table留下的都是好人
j++;
cout << "G";
//i==table[j]表示的意思就是,table[j]就是没有数到的好人,
//等于i就是i这个位置表示好人输出"G",
//而不是table[j]这个值的i自然就是坏人
}
else {
cout << "B";
}
}
cout << endl << endl; //留一个空行
}
return 0;
}
运行结果:
为了方便大家阅读我就放一个图片
有点小 ,但是也无伤大雅。
注意:前面提到的,vector插入或删除中间某一项时需要线性时间,即需要把这个元素后面的所有元素往后移或前移,复杂度是O(n)。如果频繁移动,则效率很低。上面这个代码用了erase()来删除中间元素就有这个问题。
栈和stack
栈是基本的数据结构之一,特点是"先进后出"。例如乘坐电梯时,先进电梯的最后出电梯;一杯水,先倒进去的水是最后才出来的。
在C++中使用栈需要引入头文件
#include
栈相关的操作
- stacks; //定义栈,Type表示数据类型,例如int ,float,char等,s表示栈的名字
- s.push(item); //把item放到栈顶
- s.top(); //返回栈顶元素,但不会删除
- s.pop(); //删除栈顶元素,但不会返回。在出栈时需要进行两步操作,即先top()一哈,获得栈顶元素,再pop()删除栈顶元素
- s.size(); //返回栈中元素个数
- s.empty(); //返回栈是否为空,如果为空,返回ture,否则返回false
案例:字符串翻转
翻转字符串。例如输入"olleh! dlrow",输出"hello world!"。
源码
#include<iostream>
#include<stack>
using namespace std;
int main() {
int n;
char ch;
scanf_s("%d", &n);
ch=getchar();//吸收换行符
while (n--) {
stack<char>s; //定义栈
while (true) {
ch = getchar();//一次读取一个字符
if (ch == ' ' || ch == '\n' || ch == EOF) {
while (!s.empty()) {
printf_s("%c", s.top());//输出栈顶
s.pop();//清除栈顶
}
if (ch == '\n' || ch == EOF) {
break;
}
printf_s(" ");
}
else {
s.push(ch);//入栈
}
}
printf_s("\n");
}
return 0;
}
运行结果
分析:
特别注意
爆栈问题。栈需要用空间存储,如果深度太大,或者存进栈的数组太大,那么总数会超过系统为栈分配的空间。这样就会爆栈,即栈溢出。其解决办法有以下两种:
- 在程序中调大系统的栈,这种方法依赖于系统和编译器。
- 手工写一个栈,自己设定。这部分后面的博客介绍
总结
相信通过上面的讲述和例子,你对C++的标准模板库中的vector容器和栈的操作和使用都有了清晰的认识。
对于两者记住,vector容器主要是针对动态数组问题的解决。而栈主要是对于有先进后出这个味道的问题适用。
下一篇博客分析队列和queue,优先队列和priority_queue以及链表和list
非常感谢大家的支持和点赞,这份认可是莫大的鼓励,也非常希望这些博客能对你学习计算机有帮助。