容器是什么?
几乎可以说,任何特定的数据结构都是为了实现某种特定的算法。STL容器就是将运用最广泛的一些数据结构实现出来。
常用的数据结构:数组(array) , 链表(list), tree(树),栈(stack),** 队列(queue)**, 集合(set),映射表(map), 根据数据在容器中的排列特性,这些数据分为序列式容器和关联式容器两种。
- 序列容器强调值的排序,每个元素有着固定的位置,比如vector,deque,list
- 关联容器一般是树状结构,方便查找,强调键值对。键起到索引作用,比如map,set。
string容器
c语言中char风格字符串(以空字符为结尾的字符组难以掌握),不适合大型程序的开发,所以在头文件<string>
内定义了一种string类。与char相比,string是个类而char是个指针,封装了更多的方法,不用考虑内存的释放和越界。
构造函数
string();
string(const string& str);
string(const char* s);
string(int n, char c);
string s(int beg,int end)
常见函数
大小容量函数 | 作用 |
---|
size(),length() | 返回字符串长度 |
max_size() | 返回当前字符串最多能包含的字符数 |
capacity() | 重新分配内存之前 string 所能包含的最大字符数 |
reserve() | 为当前的string 重新分配内存,参数为分配内存的大小 |
元素存取函数 | 作用 |
---|
at(),[] | 索引不会检查长度在str[str.length()] 依然有效,返回值为’\0’ |
比较函数 | 作用 |
---|
>,>=,<,<=,==,!= | 按照字典顺序比较 |
compare() | 返回0则相等,支持多参数 |
更改内容函数 | 作用 |
---|
= | 赋值 |
compare() | 返回0则相等,支持多参数 |
assign(str,begin,end) | 返回str[begin:end] 的值 |
append() | 往后面加字符 |
push_back() | 加单个字符太常见了吧 |
insert(index,str) | 索引index 的位置加一个str |
replace(1,2,str) | 从索引为1开始后两个换成str |
erase(7,5) | 索引7 的位置删除5个,如果形参2没有,默认从参数1索引后面删光 |
substr(5,6) | 提取5到6的子串 |
find | 查找 |
find_first_of | 反 查找包含子串中的任何字符,返回第一个位置 |
find_first_not_of | 查找不包含子串中的任何字符,返回第一个位置 |
find_last_of | 查找包含子串中的任何字符,返回最后一个位置 |
find_last_not_of | 查找不包含子串中的任何字符,返回最后一个位置 |
vector容器
与array的区别
vector的数据安排以及操作方式,与array非常相似,两者的唯一差别在于空间的运用的灵活性。
与array的区别:使用的 是动态空间,就算内容变动也会自动重新分配内存。两者在内存空间都是连续的内存空间。
动态增长的原理:如果空间不足会申请更大的内存空间,会拷贝到新空间,释放旧空间。
构造函数
vector<T> v;
vector(v.begin(), v.end());
vector(n, elem);
vector(const vector &vec);
vector<int> nVec(5,-1);
vector<string>str_vec{'a','b'}
std::vector<int>nVec;
for(int i = 0; i<5;++i)
nVec.push_back(i);
常见函数
函数 | 作用 |
---|
insert() | 添加元素(函数重载过多次,可以看下面的应用) |
push_back() | 从后面加一个,效率高,不需要换地址的 |
pop_back() | 删除 |
size() | 大小操作是否为空 |
resize() | 默认值填充,多的直接删除 |
capacity() | 观察容量 |
vector<int>list{10,9,8,7};
list.insert(pos,num);
list.insert(pos,n,num);
list.insert(pos,beg,end);
c.erase(p);
c.erase(begin,end);
c.clear();
vector添加元素时会内存自动增长,删除的时候会自动回收吗?
不会!下面分享一个用于节约内存的方法,利用交换swap
函数重新分配内存。
vector<int>v;
for (int i = 0;i <100000;++i)
{
v.push_back(i)
}
v.resize(10);
vector<int>(v).swap(v);
迭代器
迭代器是什么?
迭代器是一种抽象的设计理念,通过迭代器可以在不了解容器内部原理的情况下遍历容器。除此之外,STL 中迭代器一个最重要的作用就是作为容器与 STL 算法的粘结剂,只要容器提供迭代器的接口,同一套算法代码可以利用在完全不同的容器中,这是抽象思想的经典应用。
迭代器和C++的指针非常类似,它指向容器中的某个元素,如果需要也可以对容器的值读/写操作。
- Input itertor (输入迭代器)只读;只支持自增运算
- Output itertor(输出迭代器)只写;只支持自增运算
- Forward itertor(前向迭代器)读写;只支持自增运算
forward_list,unordered_map / unordered_multimap,unordered_set / unordered_multiset
- Bidirectional itertor(双向迭代器)读写;支持自增和自减
list set/multiset,map/multimap
- Random access itertor(随机访问迭代器)读写;支持完整迭代器算数运算
array,vecor,deque
stack
和queue
不支持迭代器
vector迭代代码实现:
#include <iostream>
#include<vector>
using namespace std;
int main(){
vector<int> v{1,2,3,4,5,6};
vector<int>::iterator it = v.begin()
for(;*it !=v.end();it++){
cout << *it <<'';
}
return 0;
}
deque容器
vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间。所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,需要开辟一块新的内存,无法被接受。
deque是 一种 动态分段连续空间 组成的,
实现原理
deque容器是连续的空间,至少逻辑上看来如此,连续现行空间总是令我们联想到array和vector,array无法成长,vector虽可成长,却只能向尾端成长,而且其成长其实是一个假象,事实上(1) 申请更大空间 (2)原数据复制新空间 (3)释放原空间 三步骤,如果不是vector每次配置新的空间时都留有余裕,其成长假象所带来的代价是非常昂贵的。
deque是由一段一段的定量的连续空间构成。一旦有必要在deque前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在deque的头端或者尾端。Deque最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。
既然deque是分段连续内存空间,那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。Deque代码的实现远比vector或list都多得多。
class Linknode
{
public:
linknode();
int data;
Linknode* left;
Linknode* right;
};
Linknode::linknode()
{
left =NULL;
right =Null;
}
class Tree
{
Linknode* root;
}
常见函数
构造函数 | 作用 |
---|
dequie<T> deq | 默认构造函数 |
deque(const deque &deq) | 拷贝构造函数 |
deque(beg, end) | 拷贝函数,构造函数将[beg, end)区间中的元素拷贝给本身 |
deque(n, elem) | 构造函数将n个elem拷贝给本身 |
deque赋值操作 | 作用 |
---|
assign(beg, end) | 将[beg, end)区间中的数据拷贝赋值给本身 |
assign(n, elem) | 将n个elem的值赋给本身 |
deque& operator=(const deque &deq) | 重载=操作符 |
swap(deq) | 交换元素 |
deque大小操作 | 作用 |
---|
deque.size() | 返回deque中元素的个数 |
deque.empty() | 返回容器中为空否为真 |
deque.resize(num) | 重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾 超出容器长度的元素被删除。 |
deque.resize(num, elem) | 若分配的内存大于原先的,则用elem 填充 |
deque双端操作 | 作用 |
---|
push_back(elem) | 在容器尾部添加elem |
push_front(elem) | 在容器头部添加elem |
pop_back(elem) | 在容器尾部删除一个元素 |
pop_front(elem) | 在容器头部删除一个元素 |
deque数据存取 | 作用 |
---|
deq(idx) | 返回索引idx所指的数据,如果idx越界,抛出out_of_range。 |
front() | 返回容器第一个值 |
back() | 返回容器最后一个值 |
deque插入 | 作用 |
---|
insert(pos,elem) | 在pos位置插入一个elem元素的拷贝,返回新数据的位置 |
insert(pos,n,elem) | 在pos位置插入n个elem数据,无返回值 |
insert(pos,beg,end) | 在pos位置插入[beg,end)区间的数据,无返回值 |
deque删除操作 | 作用 |
---|
clear() | ` 移除容器的所有数据 |
erase(beg,end) | 删除[beg,end)区间的数据,返回下一个数据的位置 |
erase(pos) | 删除pos位置的数据,返回下一个数据的位置 |
stack容器 栈
先进后出的容器,只有一个入口和出口,允许新增元素移除元素,取得最顶端元素,但是没有办法获取除了最顶端元素之外的元素,不允许有 遍历 行为和 迭代 行为!
常见函数
API | 作用 |
---|
push(elem) | 向容器添加elem |
pop() | 移除容器第一个元素(栈顶) |
top() | 返回栈顶元素 |
empty() | 判断是否为孔 |
size() | 返回大小 |
queue容器
queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口,queue容器允许从一端新增元素,从另一端移除元素。与栈·一样没有迭代器
常见函数
API | 作用 |
---|
push(elem) | 向容器添加elem |
pop() | 移除容器第一个元素(栈顶) |
back() | 返回最后一个元素 |
front() | 返回第一个元素 |
empty() | 判断是否为孔 |
size() | 返回大小 |
list容器 链表
链表的概念
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
list和vector是最常见的被使用的容器:
- 采用动态存储分配,不会造成内存浪费和溢出
- 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
- 链表灵活,但是空间和时间额外耗费较大
写成代码(随便写写,意思到了就行)就是:
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
struct ListNode
{
int data;
ListNode* next;
};
void Listprintf(ListNode* phead)
{
ListNode* cur=phead;
while (cur != NULL)
{
cout << cur->data << "->";
cur = cur->next;
}
}
void Listpushback(ListNode** pphead, int x)
{
ListNode* newnode = new ListNode{ x,NULL };
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
ListNode* tail= *pphead;
while(tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void test_1()
{
ListNode* phead = NULL;
Listpushback(&phead, 1);
Listpushback(&phead, 2);
Listpushback(&phead, 3);
Listprintf(phead);
}
int main()
{
test_1();
return 0;
}
list容器的迭代器
list的迭代器不能像一些连续内存的容器中那样是指针,它需要的是利用递增递减可以前后捕捉每个节点的next和pre的能力。可以看上面代码如果指针没有指向null则会一直指向下一个Node的地址。
常见函数
构造函数 | 作用 |
---|
list<T> lstT | list采用采用模板类实现,对象的默认构造形式: |
list(beg,end) | 构造函数将[beg, end)区间中的元素拷贝给本身。 |
list(n,elem) | 构造函数将n个elem拷贝给本身。 |
list(const list &lst) | 拷贝构造函数 |
赋值函数 | 作用 |
---|
push_back(elem) | 在容器尾部加入一个元素 |
pop_back() | 删除容器中最后一个元素 |
push_front(elem) | 在容器开头插入一个元素 |
pop_front() | 从容器开头移除第一个元素 |
insert(pos,elem) | 在pos位置插elem元素的拷贝,返回新数据的位置。 |
insert(pos,n,elem) | 在pos位置插入n个elem数据,无返回值。 |
insert(pos,beg,end) | 在pos位置插入[beg,end)区间的数据,无返回值。 |
clear() | 移除容器的所有数据 |
erase(beg,end) | 删除[beg,end)区间的数据,返回下一个数据的位置。 |
erase(pos) | 删除pos位置的数据,返回下一个数据的位置。 |
remove(elem) | 删除容器中所有与elem值匹配的元素。 |
大小操作 | 作用 |
---|
size() | 返回容器中元素的个数 |
empty() | 判断容器是否为空 |
resize(num) | 重新指定容器的长度为num |
resize(num, elem) | 重新指定容器的长度为num |
赋值操作 | 作用 |
---|
assign(beg, end) | 将[beg, end)区间中的数据拷贝赋值给本身。 |
assign(n, elem) | 将n个elem拷贝赋值给本身。 |
list& operator=(const list &lst) | 重载等号操作符 |
swap(lst) | 将lst与本身的元素互换。 |
reverse() | 反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。 |
sort() | list排序 |
set/multiset容器
set/multiset属于关联式容器,底层结构使用二叉树 实现的。它们的特点是:所有的元素的键值在插入时会自动被排序。不可以用迭代器改变set的值,因为如果动了其中一个值会影响整个的树的结构。而set与multiset容器的区别就是:set容器中不允许有重复的元素,而multiset允许容器中有重复的元素。
二叉树
树状图 是一种数据结构,它是由 n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树。由于线性查找需要遍历全部数据,在数据量大的时候效率就非常低下,到底有多低,在数据库有几百万几千万以上数据量写过查询语句的就知道有索引和没索引的区别。
代码实现:
class Linknode
{
public:
linknode();
int data;
Linknode* left;
Linknode* right;
};
Linknode::linknode()
{
left =NULL;
right =Null;
}
class Tree
{
Linknode* root;
}
常见函数
构造函数 | 作用 |
---|
set<T> st | set默认构造函数: |
mulitset<T> mst | multiset默认构造函数: |
set(const set &st) | 拷贝构造函数 |
赋值函数 | 作用 |
---|
set& operator=(const set &st) | 重载等号操作符 |
swap(st) | 交换两个集合容器 |
大小操作 | 作用 |
---|
size() | 返回容器中元素的数目 |
empty() | 判断容器是否为空 |
插入和删除操作 | 作用 |
---|
insert(elem) | 在容器中插入元素。 |
clear() | 清除所有元素 |
erase(pos) | 删除pos迭代器所指的元素,返回下一个元素的迭代器。 |
erase(beg, end) | 删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。 |
erase(elem) | 删除容器中值为elem的元素。 |
查找操作 | 作用 |
---|
find(key) | 查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end(); |
count(key) | 查找键key的元素个数 |
lower_bound(keyElem) | 返回第一个key>=keyElem元素的迭代器。 |
upper_bound(keyElem) | 返回第一个key>keyElem元素的迭代器。 |
equal_range(keyElem) | 返回容器中key与keyElem相等的上下限的两个迭代器。 |
map/multimap容器
map的特性是,所有元素都会根据元素的键值自动排序。
map所有的元素都是关联的,同时拥有实值和键值(key,value),第一元素被视为键值,第二元素被视为实值,map不允许两个元素有相同的键值.
- map中的key是唯一的。集合中元素按照一定顺序排列。元素的插入按照规则插入我,所以不能指定插入位置。
- map的底层是红黑树的变体,平衡二叉树。在插入操作、删除和检索上要比vector快很多。
- map可以直接存取key所对应的value,支持
[]
操作符,如map[key]=value
. #include<map>
- map支持唯一键值,每个键只能出现一次;而multimap中相同键可以出现多次。multimap不支持[]操作符
map和set的区别是:
Map对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。构造函数Map可以接受一个数组作为参数。
Set对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。
#include<map>
#include<iostream>
using namespace std;
int main(void)
{
multimap<int ,string> james;
james.insert(pair<int,string>(3,'dog'));
james.insert(pair<int,string>(4,'cat'));
for (multimap<int,string>::iterator it = james.begin();it !=james.end();it++)
{
cout <<(*it).first <<endl;
}
}