【algorithm】算法学习----C++数据结构中的STL

#include <vector>

size()返回容器内元素的多少
empty()返回一个bool类型,表明vector是否为空。二者的时间复杂度都是O(1)。
所有的STL容器都支持这两个方法,含义也相同,之后我们就不再重复给出。

clear函数把vector清空除了队列queue、priority_queue、stack其他STL容器都有clear函数

迭代器

​ 迭代器就像STL容器的“指针”,可以用星号“*”操作符解除引用。

​ 一个保存int的vector的迭代器声明方法为:

vector<int>::iterator it;

vector的迭代器是“随机访问迭代器”,可以把vector的迭代器与一个整数相加减,其行为和指针的移动类似。可以把vector的两个迭代器相减,其结果也和指针相减类似,得到两个迭代器对应下标之间的距离。

begin/end(反向迭代器rbegin()指向最后一个元素,rend()指向第一个元素,和begin()相等)

begin函数返回指向vector中第一个元素的迭代器。例如a是一个非空的vector,则*a.begin()a[0]的作用相同。

所有的容器都可以视作一个“前闭后开”([begin(),end()))的结构,end函数返回vector的尾部,即第n个元素再往后的“边界”。*a.end()与a[n]都是越界访问,其中n=a.size()。

下面两份代码都遍历了vector<int>a,并输出它的所有元素。

for (int i = 0; i < a.size(); i ++) cout << a[i] << endl;

//绝大部分都不会使用迭代器来访问vector
for (vector<int>::iterator it = a.begin(); it != a.end(); it ++) cout << *it << endl;

for (auto it = a.begin(); it != a.end(); it ++) cout << *it << endl;

for (int x : a) cout << x <<" ";

front/back 都是O(1)的时间复杂度

​ front函数返回vector的第一个元素,等价于*a.begin() a[0]

​ back函数返回vector的最后一个元素,等价于*==a.end() a[a.size() – 1]

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

int main(){
    vector<int> a({1,2,3});

    a.push_back(4);

    for(auto x : a) cout << x <<" ";
    cout<<endl;

    a.pop_back();
    for( auto x : a) cout << x << " ";
    cout<<endl;  
    return 0;
}

//运行结果:
1 2 3 4 
1 2 3 
//在指定位置插入元素:insert(lterator it,int a)
vector <int> s;
s.insert(i,n);//在s的第i个元素后面插入a
//代码参考:
#include <iostream>
#include <stdlib.h>
#include <vector>
using namespace std;

int main()
{
    vector <int> s({6,2,3});
    s.insert(s.begin()+0,4);
    for(int i=0;i<s.size();i++)
    cout<<s[i]<<" ";
    system("pause");
    return 0;
}

//运行结果:
4 6 2 3

s.insert(s.begin()+1,4);//运行结果就变成了6 4 2 3
//重新分配元素assign(iterator first,iterator last)或者assgin(size_type n,size_type u)
//第一种是使用迭代器,这种方式一般用不到
//第二种方式是重新分配为n个元素,每个元素都是u
int main()
{
    vector <int> s({6,2,3});
    s.assign(3,4);
    for(int i=0;i<s.size();i++)
    cout<<s[i]<<" ";
    system("pause");
    return 0;
}
//运行结果:
4 4 4

vector是基于倍增的思想

//比如说我这里定义了一个vector
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

int main(){
    vector<int> a({1,2,3});

    cout << a.capacity() << endl;

    cout << a.size() << endl; 

    return 0;
}

//运行结果
3
3

//我们发现vector的容器的容量和元素的个数大小一样
//那么,如果我插入一个元素这两个数字会发生变化吗?
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

int main(){
    vector<int> a({1,2,3});

    a.push_back(4);

    cout << a.capacity() << endl;

    cout << a.size() << endl; 

    return 0;
}

//运行结果
6
4

//我们就很容易发现vector的容器的大小变成了6,但此时元素的个数变成4
//容器的大小为什么会变成6呢?因为原来的容器大小是3,利用容器大小的倍增思想,每次发现插入的元素大于容器的大小,那么容器就会倍增2,然后把原来的元素复制到新扩大的vector容器里,来达到扩大容器的目的,这样就可以放入更多的元素;如此以往,如果再加的话会变成12……

我们如何分析vector的效率呢?(vector的原理)

我们发现我们新开的vector初始的容量大小就是元素的大小,但是随着插入元素的增多,容量会倍增,并且将原有的小的容器的元素复制到扩大的新的数组上来;复制元素效率不就下来了吗?

假如说我们插入了n个元素,那么vector会复制多少次呢?(复制一个元素算复制一次)

在插入的数量达到 n 2 \frac{n}{2} 2n,会复制 n 2 \frac{n}{2} 2n,那么在此之前就是 n 4 \frac{n}{4} 4n,(复制 n 4 \frac{n}{4} 4n次),然后一次类推vector复制了n( 1 2 \frac{1}{2} 21+ 1 4 \frac{1}{4} 41+ 1 8 \frac{1}{8} 81+……)

1 2 \frac{1}{2} 21+ 1 4 \frac{1}{4} 41+ 1 8 \frac{1}{8} 81+……<1(可以用数学证明,这里时间紧迫就不去证明了)

最后复制的次数会小于n

vector还可以进行比较运算,按照字典序进行排序和长度无关,从第一个元素按照ASCII值进行比较

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<int> a(3,4),b(4,3);
    if(a>b)
    cout<<"yes"<<endl;
    return 0;
}


//yes

#include <string>

substr(int pos,int len):从下标pos开始截取长度为len的字串。如果没有这两个参数,那么就不截取字串;如果pos的位置大于string的长度,那么就会报错;如果从pos+len>string的长度,那么只会截取到string结束的部位。

c_str():将string类型的字符串转换成C语言的char数组

+:string可以利用+对字符串进行连接;(在字符串后进行连接)

#include <queue>

主要包括循环队列queue和优先队列priority_queue一个堆,可以实时返回所有数的最大值两个容器

#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

int main(){
    queue<int> q;//队列
    priority_queue<int> a;//大根堆。每次弹出最大的值
    priority_queue<int,vector<int>, greater <int>> b;//小根堆,每次弹出最小的值

    //如果对结构体创建大根堆的话要重载小于号
    struct Rec{
      int a,b;
    //   //定义Rec结构体的大根堆要重载小于号
    //   bool operator < (const Rec& t) const{
    //       return a<t.a;
    //   }
        //定义Rec结构体的小根堆要重载大于号
         bool operator > (const Rec& t) const{
          return a>t.a;
      }
    };

    //priority_queue<Rec > d;//以Rec结构体为对象创建的d大根堆
    priority_queue<Rec ,vector<Rec>,greater<Rec>> d;//以Rec结构体为对象创建的d小根堆
    return 0;
}

普通队列用法:

#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

int main(){
    queue<int> q;//队列
    //只能在队尾插入,在队头弹出
    q.push(1);//在队尾插入元素
    q.push(2);
    q.push(3);
    //插入的队列的顺序为(队头)1、2、3(队尾)
    q.pop();//弹出队头元素
    cout<<q.front()<<endl;//返回队头元素
    cout << q.back() <<endl;//返回队尾元素
    return 0;
}

//运行结果
2
3

优先队列用法(以大根堆为例):

#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

int main(){    
    priority_queue<int > a;//大根堆

    a.push(1);
    a.push(3);
    a.push(2);    

    cout<< a.top()<<endl;//取最大值
    a.pop();//弹出堆顶

    cout<< a.top()<<endl;
    return 0;
}

//运行结果
3
2

queue和priority_queue都不能使用clear()函数,那么清空队列?

q=queue<int>();//重新定义一个队列覆盖原有的队列达到清空的目的

#include <stack>

先进后出的原则—这样的一个数据结构

#include <iostream>
#include <algorithm>
#include <stack>

using namespace std;

int main(){
    stack<int > stk;
    stk.push(1);//插入一个元素
    stk.push(2);
    stk.push(3);
    cout<< stk.top()<<endl;;//返回栈顶元素
    stk.pop();//弹出栈顶元素
    cout<< stk.top()<<endl;
    return 0;
}

//运行结果

3
2

#include <deque>

双端队列deque是一个支持在两端高效插入或者删除的连续线性存储空间。它就像是vector和queue的结合。与vector相比,deque在头部增添元素只需要O(1)的时间;但是vector在尾部是O(1),但是在头部就是O(n);与queue相比,deque就像数组一样可以随机访问

#include <iostream>
#include <algorithm>
#include <deque>

using namespace std;

int main(){
    deque<int> a;
    a.push_front(1);//在队头插入元素
    a.push_front(3);
    a.push_front(5);
    a.push_back(2);//在队尾插入元素
    a.push_back(4);//当然也可以使用a.push_front()向队头插入元素
    a.push_back(6);
    //这个时候的deque存取的是:5 3 1 2 4 6
//    cout<< a.begin()<<"     "<<a.end()<<endl;//迭代器的begin()和end()

    cout<<a.front()<<"     "<<a.back()<<endl;//返回deque的队头和队尾元素
    for( int i=0;i<a.size();i++) cout<<a[i]<<" ";//可以使用[]访问deque的元素
    cout<<endl;

    a.pop_back();//弹出队尾元素
    a.pop_front();//弹出队头元素

    for( int i=0;i<a.size();i++) cout<<a[i]<<" ";
    cout<<endl;

    a.clear();//清空deque的元素
    cout<< a.size() <<endl;
    return 0;
}

//运行结果
5     6
5 3 1 2 4 6 
3 1 2 4 
0

#include <set>

头文件set主要包括setmultiset两个容器,分别是**“有序集合”“有序多重集合”,(注意是有序的)即前者的元素不能重复,而后者可以包含若干个相等的元素**。set和multiset的内部实现是一棵红黑树,它们支持的函数基本相同。都是基于平衡二叉树

红黑树不会QAQ,心态emo了

红黑树是一个有如下规则的二叉排序树

1.结点是红色或黑色。

2.根结点是黑色。

3.每个叶子结点都是黑色的空结点(NIL结点)。

4 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)

5.从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。

具体红黑树的内容可以看漫画:什么是红黑树? - 掘金 (juejin.cn)

#include <iostream>
#include <algorithm>
#include <set>

using namespace std;

int main(){
    set<int> a;//元素不能重复
    multiset<int> b;//元素可以重复

    //也可以定义struct
    struct Rec{
      int x,y;
      //不过使用基于结构体的set集合的时候结构体内要重载小于号(因为set内部是有比较的)
      bool operator < (const Rec& t) const{
          return x<t.x;
      }
    };
    //重载<或者>等我们就可以定义一个Rec类型的set集合
    set<Rec> c;
    return 0;
}
//size()、empty()、clear()这三个函数的使用和效果和vector一样

迭代器:

set和multiset的迭代器称为“双向访问迭代器”,不支持“随机访问”,支持星号(*)解除引用,仅支持”++”和–“两个与算术相关的操作。

设it是一个迭代器,例如set<int>::iterator it;

若把it++,则it会指向“下一个”元素。这里的“下一个”元素是指在元素从小到大排序的结果中,排在it下一名的元素。同理,若把it–,则it将会指向排在“上一个”的元素。(有序集合);时间复杂度是O(logn)

begin/end

返回集合的首、尾迭代器,时间复杂度均为O(1)。

s.begin() 是指向集合中最小元素的迭代器。

s.end() 是指向集合中最大元素的下一个位置的迭代器。换言之,就像vector一样,是一个“前闭后开”的形式。因此–s.end()是指向集合中最大元素的迭代器。

#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
int main(){
    set<int> s;
    s.insert(1);//s.insert(x)把一个元素x插入到集合s中,时间复杂度为O(logn)。在set中,若元素已存在,则不会重复插入该元素,对集合的状态无影响。时间复杂度为O(logn)
    s.insert(2);
    s.insert(3);
    cout<< *s.find(1) <<endl;//s.find(x) 在集合s中查找等于x的元素,并返回指向该元素的迭代器。若不存在,则返回s.end()。时间复杂度为O(logn)。
    //因此我们可以使用if(s.find(x)==s.end())来判断是否能在set集合里找到x;时间复杂度为O(logn)

    if(s.find(4)==s.end())//判断x在a中的是否存在
    cout<<"4不存在集合s中"<< endl;
//lower_bound()与upper_bound()是set最核心的两个操作
    cout<<*s.lower_bound(2)<<endl;//找到大于等于2的最小元素的迭代器;时间复杂度为O(logn)

    cout<<*s.upper_bound(2)<<endl;//找到大于2的最小的元素的迭代器;时间复杂度为O(logn)

    set<int>::iterator it=s.begin();

    s.erase(it);//删除迭代器it指向的元素;时间复杂度为O(logn)
    s.erase(2);//把所有值为2的元素;时间复杂度为O(k+logn)其中k为删除的元素的个数
/*
erase():如果参数是一个元素数字,那么删除与这个数字相同的节点
        如果参数是一个迭代器,那么删除这个迭代器
*/
    cout<<s.count(3)<<endl;//s.count(x) 返回集合s中等于x的元素个数,时间复杂度为 O(k +logn),其中k为元素x的个数。
    return 0;
}

//运行结果

1
4不存在集合s中
2
3
1

#include<unordered_set>

unordered_multiset底层都是是哈希表,不支持lower_bound()与upper_bound()

#include<iostream>
#include<algorithm>
#include<undered_set>
using namespace std;
int main(){
    //相对于set的有序集合,undered_set是一个无序集合
    undered_set<int> s;//底层实现是一个哈希表,不能存储重复元素
    //undered_set除了set中的lower_bound()和upper_bound()函数无法使用之外,其余函数都可以使用,时间复杂度都是O(1),但不支持二分
    undered_multiset<int> c;//底层实现是一个哈希表
    return 0;
}

#include <map>

map容器是一个键值对key-value的映射,其内部实现是一棵以key为关键码的红黑树。Map的key和value可以是任意类型,其中key必须定义小于号运算符。其实y总说还有一种mutimap(但是不经常用)这里就不说了。这两个都是基于平衡二叉树

map是一个十分玄幻的容器----y总

#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
int main(){
    map<int,int> a;
    a[1]=3;
    a[520]=1314;
    cout<<a[520]<<endl;

    map<string,int> b;
    b["lml"]=38;
    cout<<b["lml"]<<endl;

    map<string,vector<int>> c;
    c["lml"]=vector<int>({1,2,3,4});
    c["wyq"]=vector<int>();

    cout<<c["lml"][1]<<endl;

    cout<<c["wyq"].size()<<endl;
    return 0;
}

//运行结果
1314
38
2
0

​ 声明

map<key_type, value_type> name;

​ 例如:

map<long, long, bool> vis;

map<string, int> hash;

map<pair<int, int>, vector<int>> test;

size/empty/clear/begin/end均与set类似。

Insert/erase

​ 与set类似,但其参数均是pair<key_type, value_type>插入的必须是一个二元组

#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
int main(){
    map<string,vector<int>> b;
    b.insert({"lml",{1,2,3,4}});
    cout<<b["lml"][1]<<endl;
    return 0;
}

//运行结果
2

h.find(x) 在变量名为h的map中查找key为x的二元组;返回的是一个迭代器

    cout<<(b.find("lml")==b.end())<<endl;

​ []操作符

​ h[key] 返回key映射的value的引用,时间复杂度为O(logn)。

[]操作符是map最吸引人的地方。我们可以很方便地通过h[key]来得到key对应的value,还可以对h[key]进行赋值操作,改变key对应的value。

#include <unordered_map>

unordered_multimap底层一样都是哈希表来实现

哈希表实现增删改查的时间复杂度是O(1)。

unordered_map是C++中的哈希表,可以在任意类型与类型之间做映射。

基本操作
引用头文件(C++11):#include    <unordered_map>
定义:unordered_map<int,int>unordered_map<string, double>
插入:例如将(“ABC” -> 5.45) 插入unordered_map<string, double> hash中,hash["ABC"]=5.45
查询:hash[“ABC”]会返回5.45
判断key是否存在:hash.count("ABC") != 0 hash.find("ABC") != hash.end()
遍历
for (auto &item : hash) { cout << item.first << ' ' << item.second << endl; }

for (unordered_map<string, double>::iterator it = hash.begin(); it != hash.end(); it ++ ) { cout << it->first << ' ' << it->second << endl; }
进阶操作
如果想让自定义的class作为key(unordered_map<key,value>)来使用unordered_map,需要实现:
(1) 哈希函数,需要实现一个class重载operator(),将自定义class变量映射到一个size_t类型的数。一般常用std::hash模板来实现。
(2) 判断两个自定义class类型的变量是否相等的函数,一般在自定义class里重载operator==。
示例代码:

#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

class Myclass
{
public:
 int first;
 vector<int> second;

// 重载等号,判断两个Myclass类型的变量是否相等
bool operator== (const Myclass &other) const
{
 return first == other.first && second == other.second;
}

};

// 实现Myclass类的hash函数
namespace std
{
 template <>
 struct hash<Myclass>
 {
 size_t operator()(const Myclass &k) const
 {
 int h = k.first;
 for (auto x : k.second)
 {
 h ^= x;
 }
 return h;
 }
 };
}

int main()
{
 unordered_map<Myclass, double> S;
 Myclass a = { 2, {3, 4} };
 Myclass b = { 3, {1, 2, 3, 4} };
 S[a] = 2.5;
 S[b] = 3.123;
 cout << S[a] << ' ' << S[b] << endl;
 return 0;
}

输出结果:

2.5 3.123

#include <bitset>

#include<iostream>
#include<algorithm>
#include<bitset>
using namespace std;
int main(){
    bitset<1000> a,b;//定义长度为1000的01串
    a[0]=1;a[1]=1;
    cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<endl;//没有设置为1的位置默认为0

    cout<<a.count()<<endl;//count()函数可以返回当前01串中1的个数
    a&=b;
    a|=b;//可以进行位运算

    b.set(3);//将下标为3的b的这一位上置1
    cout<<b[3]<<endl;
    b.reset(3);//将下标为3的b的这一位上置0
    cout<<b[3]<<endl;
    return 0;
}

bitset<10000> s;

~, &, |, ^

<<,>>
==, !=
[]

count() 返回有多少个1
any() 判断是否至少有一个1
none() 判断是否全为0
set() 把所有位置成1
set(k, v) 将第k位变成v
reset() 把所有位变成0
flip() 等价于~
flip(k) 把第k位取反

pair

pair是一个二元组,可能看上去有点像map,但是实现起来就不是map了哈哈

#include<iostream>

using namespace std;

int main(){
    pair<int,string> a;
    a={123,"lml"};

    cout<<a.first<<" "<<a.second<<endl;

    //但是在C++99里面pair就不能这样赋值,需要使用函数
    a=make_pair(676,"wyq");

    cout<<a.first<<" "<<a.second<<endl;
    return 0;
}

//运行结果

123 lml
676 wyq

也可以使用pair内嵌到pair中:

pair<int,pair<int,int>> a;

pair有什么好处?

pair可以比较二元的大小,比较运算符在pair都可以使用(<、>、<=、>=、!、==.stc)

它会先比较first的大小,如果first不同就比较second的大小

string,字符串
size()/length() 返回字符串长度
empty()
clear()
substr(起始下标,(子串长度)) 返回子串
c_str() 返回字符串所在字符数组的起始地址

queue, 队列
size()
empty()
push() 向队尾插入一个元素
front() 返回队头元素
back() 返回队尾元素
pop() 弹出队头元素

priority_queue, 优先队列,默认是大根堆
size()
empty()
push() 插入一个元素
top() 返回堆顶元素
pop() 弹出堆顶元素
定义成小根堆的方式:priority_queue<int, vector, greater> q;

stack, 栈
size()
empty()
push() 向栈顶插入一个元素
top() 返回栈顶元素
pop() 弹出栈顶元素

deque, 双端队列
size()
empty()
clear()
front()/back()
push_back()/pop_back()
push_front()/pop_front()
begin()/end()
[]

set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
size()
empty()
clear()
begin()/end()
++, – 返回前驱和后继,时间复杂度 O(logn)

set/multiset
    insert()  插入一个数
    find()  查找一个数
    count()  返回某一个数的个数
    erase()
        (1) 输入是一个数x,删除所有x   O(k + logn)
        (2) 输入一个迭代器,删除这个迭代器
    lower_bound()/upper_bound()
        lower_bound(x)  返回大于等于x的最小的数的迭代器
        upper_bound(x)  返回大于x的最小的数的迭代器
map/multimap
    insert()  插入的数是一个pair
    erase()  输入的参数是pair或者迭代器
    find()
    []  注意multimap不支持此操作。 时间复杂度是 O(logn)
    lower_bound()/upper_bound()

unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
和上面类似,增删改查的时间复杂度是 O(1)
不支持 lower_bound()/upper_bound(), 迭代器的++,–

bitset, 圧位
bitset<10000> s;
~, &, |, ^
>>, <<
==, !=
[]

count()  返回有多少个1

any()  判断是否至少有一个1
none()  判断是否全为0

set()  把所有位置成1
set(k, v)  将第k位变成v
reset()  把所有位变成0
flip()  等价于~
flip(k) 把第k位取反
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

明璐花生牛奶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值