关键词
- 通用基础
- 数据结构性质
- 数据结构
通用基础
值引用和引用传递
在C++中以值传递参数时,实际上是创建了实参的一个副本。这就意味着你在每次进行递归调用时都在复制整个数组。这导致了你的时间和空间复杂度大大增加
而引用传递参数,可以实现不复制整个数组,直接引用原数组,很大程度减少空间和时间复杂度
总结!在递归调用,只需要读取的参数最好也是用引用传递
make_pair()和pair()
- 作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型。
- C++标准程序库中凡是“必须返回两个值”的函数, 也都会利用pair对象。
当数据以make_pair/pair 绑定到了一起则想要访问不是[0][1],而是first和second
定义数据结构时:deque<pair<string,int>> haha;
运行普通函数:haha.push_front(make_pair(“123”,1));
insert和emplace
insert和emplaceinsert是插入一个完全的对象,而emplace是先调用该对象的构造函数生成该对象,再将该对象插入vector中,使用emplace时,该对象类必须有相应的构造函数
struct A{
int a;
float b;
A(int _a,float _b): a{_a},b{_b} {};//使用emplace必须要有构造函数
//没有会产生报错
//no matching function for call to 'main()::A::A(int)'
A(int _a):a{_a},b{0.0} {};
};
A x1(1,1.1);
vector<A> v1;
v1.insert(v1.end(),x1);
v1.emplace(v1.begin(),2,2.2);
v1.emplace_back(3,3.3);
v1.emplace_back(4);
for(int i=0;i<4;i++){
cout<<v1[i].a<<" "<<v1[i].b<<endl;
}
c++构造类
class LockingTree{
public:
//注意有些题目不需要通过构造去初始化,外部通过调用内部函数去给类传递数据(如下一示例)
LockingTree(可有外部系数可没有){//构造函数
this->a=a;
this->b=vector<int>9=(n,-1);
}
bool lock(){//内部函数
int y=b[i];
int x=a;
int a=x;//错误写法
a=x;//正确写法,int a是内部函数的局部变量,a才是整个构造数据类型的private变量
...}
private:
//属性并不一定要写在private里面,可以写在public或者protected里面
//在该类中定义的属性,内部都能直接访问
int a;
vector <int> b;//写明属性
//注意!!!数据不能直接在构造函数中初始化,一定要先写明
//以及!!!不能内部函数中再定义属性,而是应该直接使用属性
//以及!!!如果数据有自定义的数据结构,可以在public:或者private:定义数据结构
//eg:
struct Node{
int c,d;
}
vector<Node> e;
}
eg:
c++结构体
1,结构体自带初始化
2,运算符重载(指定运算符进行操作)
struct Node{
int cnt,time;
//自带初始化
Node(int_cnt,int_time):cnt(_cnt),time(_time){}
//运算符重载
bool operator < (const Node&rhs) const{
return cnt==rhs.cnt?time<rhs.time:cnt<rhs.cnt;
}
}
struct Node {
vector<int> nums;
int num;
Node() = default;
Node(const Node&) = default;
Node& operator = (const Node&) = default;
Node(vector<int> _nums, int _num) : nums(_nums), num(_num) {} // 修改构造函数
};
初始化
创建对象
Node cache=Node(1,time);
创建指针
Node *pNode =new Node(1,time);
创建引用
attention:C++中,引用一旦被初始化为对某个对象的引用,就无法改变为引用另一个不同的对象。也就是说,引用一旦绑定到一个对象上,就不能解除绑定或者重新绑定到另一个对象上。
Node cache=Node(1,time);
Node& ca1=cache;
attention:对象和指针获取值的方式相同,直接用.
引用获取值的方式为->
运算符重载
set <Node> s;
s.begin();//s.begin()是以上规则中的min值
因为s为set类型数据集合,set自动进行排序,这个写法相当于重置set的排序规则
unordered_map插入自定义结构体小tips
unordered_map在插入元素时,需要拷贝或移动构造value对象。
但Node类没有实现默认构造函数和拷贝构造函数的话,无法完成拷贝操作
方法1,调用函数
Node cache=Node(1,time);
unordered_map<int, Node> m;
key_table.insert(make_pair(1,cache));
方法2,实现构造函数和拷贝构造函数
struct Node {
// ...
Node() = default;
Node(const Node&) = default;
Node& operator=(const Node&) = default;
};
unordered_map<int, Node> m;
Node cache=Node(1,time)/new Node(1,time);;
m[1]=cache;
memset函数
分割字符串
方法1
eg:将 “i love you”变成string a={“i”,“love”,“you”}
两层while循环,外层while循环索引单词起始位置,内层while循环快速锁定单词终止位置
int m=s.size();
int wordStart=0;//单词的起点索引
int wordEnd=0;//单词的终点索引(边界或指向空格)不包含
string word;
vector<string> resul;
while(wordStart<m){
while(wordEnd<m&&s[wordEnd]!=' ')wordEnd++;
word=s.substr(wordStart,wordEnd-wordStart);//截取单词
resul.push_back(word);
// 更新单词区间,起点为当前终点的下一个位置;终点初始与起点相同
wordStart = wordEnd + 1;
wordEnd = wordStart;
}
方法2
匿名函数lambda表达式实现,可以直接写在主函数中
解析主要做了:
- 定义了一个lambda表达式函数split,接受字符串s和分隔符delim。
- 返回值设定为vector类型,即返回多个子串组成的字符串向量。
- 声明cur字符串变量存储当前累积的子串,ans变量存储最终结果。
- 对s中的每个字符:
- 如果是分隔符delim,则将cur加入ans,并清空cur重新开始累积新子串。
- 否则将字符添加到cur末尾。
- 循环结束后,将最后一个未加入的cur子串移动加入ans。
- 返回ans向量。
通过判断字符是否为分隔符,动态累积子串字符串。利用move函数进行高效字符串移动避免复制,返回结果vector采用移动语义。实现了按分隔符分割字符串的功能。
auto split = [](const string& s, char delim) -> vector<string> {
vector<string> ans;
string cur;
for (char ch: s) {
if (ch == delim) {
ans.push_back(move(cur));
cur.clear();
}
else {
cur += ch;
}
}
ans.push_back(move(cur));
//move() 将对象的状态或者所有权从一个对象转移到另一个对象
//只是转移,没有内存的搬迁或者内存拷贝,所以可以提高效率,改善性能。
return ans;
};
vector<string> result=split(path,'/');
//将字符串数组path以/分割为多个子字符串
翻转链表
方法1
定义三个辅助节点,一个指向当前需要进行指向翻转的节点(cur),一个指向cur的前一个节点(pre),一个指向cur的后一个节点(nextt)
listnode* reverse(listnode*head){
listnode*pre=nullptr,*cur=head;
while(cur){
listnode*nextt=cur->next;
cur->next=pre;
pre=cur;
cur=nextt;
}
return pre;//遍历到后面cur指向空节点,pre指向原链表尾节点,即新链表头节点
}
应用
leetcode t92反转链表
ListNode *reverseBetween(ListNode *head, int left, int right) {
ListNode *dummy = new ListNode(0, head), *p0 = dummy;
//定义头节点指向首节点,因为若首节点也参与反转,此时找不到首节点前一个节点
//定义头节点为首节点的前一个节点
for (int i = 0; i < left - 1; ++i)
p0 = p0->next;
//1,先找到链表中反转链表段的前一个节点
ListNode *pre = nullptr, *cur = p0->next;
//2,需要反转的链表段的每一个节点的指向都翻转
for (int i = 0; i < right - left + 1; ++i) {
ListNode *nxt = cur->next;
cur->next = pre; // 每次循环只修改一个 next
pre = cur;
cur = nxt;
}
//3,指向改变
//p0指向的next仍然为原反转链表段的首节点
//cur指向的是反转链表段的后一节点
//pre指向的是反转链表段的尾节点,反转后为首节点
p0->next->next = cur;
p0->next = pre;
return dummy->next;
}
最大最小值
INT_MIN:-2147483648(-231)****INT_MAX:2147483647(**231-1)****LONG_MIN:-2147483648(**-231)****LONG_MAX:2147483647(**231-1)****LLONG_MIN:-9223372036854775808(-**263)****LLONG_MAX:9223372036854775807(**263-1)
数据结构性质
数据结构底层
底层红黑树 会自动进行全排列 增删改查为o(logn):树高红黑树为平衡二叉搜索树
- 平衡二叉树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1
- 二叉搜索树: 根节点的左子树值均小于根节点的值,根节点的右子树值均大于根节点的值。
- vector 数组
- list 链表
- map 键值对 底层红黑树
- set 集合 元素唯一 底层红黑树
- multiset 集合 元素可重复 底层红黑树
- priority_queue 优先队列 底层堆:大根堆/小根堆
数据结构必记
- 栈头进头出
- 队列头进尾出
数据结构
vector可变多维数组
定义数组:
vector<vector> g;//注意!单纯定义数组,没有初始化数组时,直接访问数组会报错
数组初始化:
vector<vector<int>> v;
v.resize(n);
for(int i=0;i<n;i++){
v[i].resize(m);
}
数组定义同时初始化://常用!!
一维数组:
vector g(n,0);//g数组有n位,每一位都是0
二维数组:
vector<vector> v(n,vector(m,num));//n行m列,初始化数组数值为num
vector<vector>g(n);//n个int*的变量
数组索引:
g.back()[i];//索引最后一个元素的第i维数据
g[i]/g[i][j]
遍历数组:
for(auto&row:g){
for(aoto&col:row){
}
}
数组操作:
插入
- g[i].push_back(a);//在g的第i行后加数据a
- g[i].emplace_back(a);//c++11之后使用,前提是有对应的构造函数
数组大小
g.size();g[i].size();
排序
sort(a.begin(),a.end());//默认从小到大排序
sort(a.begin(),a.end(),greater<int>());//从大到小排序
//自定义排序方式
//方式1
bool comp(const int&a,const int&b){return a>b;}//comp的意思为从大到小排序
sort(a.begin(),a.end(),comp);
//attention!
//从大到小排序return a>b
//从小到大排序return a<b
//方式2 lambda函数
sort(a.begin(),a.end(),[](vector<int>&v1,vector<int>&v2)->bool{
if(v1[0]==v2[0]) return v1[1]<v2[1];
return v1[0]<v2[0];
});//二维数组排序,先按第一个维度排序,再按第二个维度排序,v1,v2的比较实际上是a内数据的比较
sort(a.begin(),a.end(),[&](int a,int b){
return c[a]<c[b];});//c[]不是函数内的系数需要进行捕捉,即c[]数组与a无关
//[&]是一个“捕捉列表(capture list)”,用于描述将要被lambda函数以引用传参方式使用的局部变量
//[]意思是什么都不捕捉
已知无序数组,不改变原数组获得有序排列(即对于原数组获得排列索引
vector<int> id(plantTime.size());
iota(id.begin(),id.end(),0);//id[i]=i
sort(id.begin(),id.end(),[&](int i,int j){ return growTime[i]>growIime[j];});
清空数组
g.clear();
范围内数组值操作
accumulate是numeric库中的一个函数,主要用来对指定范围内元素求和,但也自行指定一些其他操作,如范围内所有元素相乘、相除等。
accumulate(起始迭代器, 结束迭代器, 初始值, 自定义操作函数)
accumulate(arr.begin()+2, arr.end(),0);// 初值0 + (arr[2]… + arr[n-1])
accumulate(arr.begin(), arr.end(),1,multiplies());// 初值1 * (1 * 2 * 3 * 4 *… * 10)
attention:对数组进行累加,会导致超出int上限的情况
解决办法:定义初值为0LL,C和C ++中的常量。后缀LL表示常量的类型为long long
long long sum=accumulate(milestones.begin(),milestones.end(),0LL);//解决办法是初值定义为0LL
数组最大值
int maxV = *max_element(milestones.begin(), milestones.end());
普通数组
初始化
int a[]={ };
int b[][c]={ };//c++的二维数组必须标明列数;
={{},{},{}};
长度
数字型:
sizeof(a)/sizeof(a[0])
sizeof(a[0])/sizeof(数据类型)
字符型:
length()
size()
deque 双端动态数组
概念:
Vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间,又称双端动态数组。所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,无法被接受。
与vector的区别
- deque允许使用常数项时间对头端进行元素的插入和删除 操作
- deque没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,换句话说,像vector那样,”旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque身上是不会发生的
- 除非有必要,我们应该尽可能的 使用vector,而不是deque。对deque进行的排序操作,为了最高效率,可将deque先完整的复制到一个vector中,对vector容器进行排序,再复制回deque。(排序比vector复杂)
deque容器实现原理
array 无法成长,vector虽可成长,却只能向尾端成长,而且其成长其实是一个假象,事实上(1) 申请更大空间 (2)原数据复制新空间 (3)释放原空间 三步骤
Deque是由一段一段的定量的连续空间构成。一旦有必要在deque前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在deque的头端或者尾端。Deque最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。 既然deque是分段连续内存空间,那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。Deque代码的实现远比vector或list都多得多。 Deque采取一块所谓的map(注意,不是STL的map容器)作为主控,这里所谓的map是一小块连续的内存空间,其中每一个元素(此处成为一个结点)都是一个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是deque的存储空间的主体。
deque常用api
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> deq;
//向后插入
deq.push_back(1);
deq.push_back(2);
//向前插入
deq.push_front(0);
//打印
for(int x : deq){
cout << x << " ";
}
//删除第一个元素
deq.pop_front();
//在开头位置插入-1元素
deq.insert(deq.begin(), -1);
//反向打印
for(auto it=deq.rbegin(); it!=deq.rend(); it++){
cout << *it << " ";
}
return 0;
}
st.empty();//是否为空
查找
st.front();//头部元素
st.back();//尾部元素
随机访问
#include <deque>
using namespace std;
int main() {
deque<int> deq = {1,2,3,4,5};
// 随机访问第3个元素
int elem = deq[2];
// 访问最后一个元素
int last = deq[deq.size()-1];
// 修改第二个元素
deq[1] = 10;
return 0;
}
增删
st.insert(st.begin(),-1);//在开头位置插入元素-1
st.pop_front();
st.push_front();
st.pop_back();
st.push_back();
attention:和队列/栈一样,想要获得删除的数需要先取数再删数
a=b.front();
b.pop_back();
unordered_map哈希表(无序,查找复杂度o(1))
初始化
unordered_map<key,value>name;
查找
//找到
auto it=name.find(key);
int it=name.count(key);//(i>0则找到了)
//找到之后取值
//取value
valuetype a=it->second;
//取key
keytype b=it->first;
//找不到
if(it==name.end());//如果找不到的话it迭代器会指向末尾
二分查找
lower_bound(a);//找到第一个大于等于a的数
upper_bound(a);//找到第一个大于a的数
插入
- name.insert(make_pair(key,value));
- name[key]=value;
删除
name.erase(key);
大小
name.size();
清空
name.clear();
修改(注意修改value时,如果value是自定义构造体,则必须对整个value进行修改)
key_table [key].time=time;(x)
auto it=key_table.find(key);
Node a=it->second;
change(a);
it->second=a;
遍历
for(auto [key,value]:name){}
//直接将key:value一个一个取出来去遍历
map与unordered_map比较
- 底层实现:map采用红黑树,unordered_map采用哈希表
- map元素按照键的大小进行有序排列,unordered_map无序, unordered_map则适合用于需要快速查找元素的情况下,例如查找是否存在某个键值对、统计某个值出现的次数等
map 键值对
set 集合
set中每个元素的值都唯一,而且系统能根据元素的值自动排序
attention!set中元素的值不能直接被改变,得已知a,先删a,再修改a,再将a增加进去
增删改查的复杂度为o(logn)
插入
s.insert(a);
删除
s.erase(a);//可以删除元素或是指向元素的迭代器
查找
s.find(a);
最小值
s.begin();//注意s.begin()返回的是迭代器,取值为s.begin();
注意!!set只有begin()获取第一个元素的迭代器原因是:
- set作为无序容器,元素按关键字自动排序,没有固定的"第一个"元素概念
- 但begin()迭代器指向红黑树中的最小值元素
所以获取set容器第一个元素,可以:
- 调用begin()获取最小值元素的迭代器
- 或直接解引用*begin()
multiset 集合
其他都和set一致,唯一区别multiset允许存储重复的元素
插入删除查找同set
最小值
*s.begin();
最大值
*s.rbegin()
multiset删所有重复
a.erase(n);//n为元素
multiset删一个重复元素
auto it=set1.find(n);
if(it!=set1.end()){
set1.erase(it);};
unordered_set 集合
其他都和set一致,唯一区别set会自动排序,unordered_set不会自动排序
初始化
unordered_set a={‘a’,‘b’,‘c’};
api
.count();//计算个数
.contains(str)//查找该序列中是否存在,返回bool类型
随机索引
unordered_set <int> myset;
auto it=myset.begin();
std::advance(it,rand()%myset.size());
//std::advance函数用来使迭代器it指向任意位置,它通过迭代器的后继运算符++实现移动。
//注意集合数据结构不能和数组一样按照数字索引直接取值 nums[i],而是应该先获得一个迭代器,再按照迭代器往后移动
list 双向链表
初始化
list a;
list :: iterator b;//迭代器 索引
- list容器为列表本身,可以直接修改列表。
- 迭代器为列表遍历指针,不能直接修改列表,仅用于迭代访问每个元素。
插入
a.push_back©;
a.push_front(d);
指定位置插入元素
iterator insert(iterator position, const value_type& val);
#include <list>
list<int> lst;
// 在lst中第二个位置插入值5
list<int>::iterator it = lst.begin();
std::advance(it, 1);
lst.insert(it, 5);
删除
a.erase(b);//删除b索引对应的元素
a.pop_back();
a.pop_front();
查找
查找某个值:list :: iterator it1 =find(l1.begin(),l1.end(),3);//查找l1中的数字3的位置
查找某个位置的值:
list::iterator it = lst.begin();
std::advance(it, 1); //it指向链表中的第二个位置
front();back();返回元素本身
begin();end();返回迭代器
最后一个元素
1,a.back();
list<int> mylist;
//...
int last = mylist.back();
2,a.end();
list<int>::iterator it = mylist.end();
it--;
int last = *it;
这两种API的区别和用法:
- back()直接获取最后一个元素,更简单直接。
- end()获取最后一个元素后位置的迭代器,需要前置减运算获得最后元素。
使用场景:
- 只需要元素值,使用back()更简单直接。
- 需要迭代列表,如遍历等操作,可以使用end()获取迭代器。
第一个元素
1,a.front();
list<int> mylist;
//...
int first = mylist.front();
2,a.begin();
list<int>::iterator it = mylist.begin();
int first = *it;
这两种API的区别和用法:
- front()直接获取第一个元素,更简洁直接。
- begin()获取第一个元素的迭代器,需要解引用操作*it才能访问元素。
使用场景:
- 只需要元素值,使用front()更简单直接。
- 需要迭代列表,如遍历等操作,可以使用begin()获取迭代器。
queue 队列
queue q;
q.push/pop/front/empty();
出队操作:
int a=q.pop();//错误
int a=q.front();
q.pop();//正确
priority_queue 优先队列
小根堆:父节点的值小于或者等于子节点的值,浮出最小值
大根堆:父节点的值大于或者等于子节点的值,浮出最大值
初始化
priority_queue p;//默认大根堆
修改为小根堆
1,greater作为比较器
priority_queue <int,vector,greater >q;//小根堆
priority_queue <int,vector,less >q;//大根堆
attention!当我们声明的时候碰到两个<或者两个>放在一起的时候,一定要记得在中间加一个空格,这样编译器才不会把两个连在一起的符号判断成位运算的左移或者右移。
2,自定义优先级
struct cmp {
bool operator() (int a, int b) {
return a < b;
}
};
priority_queue<int, vector<int>, cmp> pq;
api
q.push();//插入元素到队尾并排序
//注意不存在push_back函数,将数放入容器自动排序而不是放置在末尾
q.pop();//弹出队头元素
q.top();
q.empty();
与set比较
优先队列基于堆,仅保证首元素max/min
set基于红黑树,全排列
stack 栈
p.push();
p.pop();
p.top();
注意!!仅有stack和priority_queue(栈和优先队列)是top()取第一个数,其他为front()或者begin()
p.empty();
stringstream 字符串流
声明
#include
stringstream ss;
写入数据
int age = 30;
ss << age<<“dsa”<<“dsadas”;
//空格也会如实记录到字符串流中
读取数据
string name;
ss >> name;
//提取字符串流的内容直到空格为止
提取string内容,空格也会提取
string str = ss.str();
清除stringstream
ss.clear();
string 字符串
查找
s.find(a);//在字符串s中查找是否有子字符串a,如果不存在返回-1
替换
string replace (size_t pos, size_t len, const string& str);
pos表示要替换的子串在原字符串中的起始位置,len表示要替换的原来子串的长度,str表示用来替换的字符串。
void replace(string&s){
int pos=s.find(" ");
while(pos!=-1){
s.replace(pos,1,"");
pos=s.find(" ");
}
}
//注意这个代码在数据量过多的时候可能会出现超出时间限制,因为replace和find操作的复杂度都是o(N)级别的
//以下代码只需要对字符串进行一次遍历
void replaceSpaces(string S) {
string mystr = "";
for (int i = 0; i < S.size(); i++)
{
if (S[i] == char(' '))
mystr += "%20";
else
mystr += S[i];
}
}
};
截取
s.substr(a,b);//从下标为a(a>=0)的位置开始,取b个数
转换
stoi();//将数字类型的字符串转化为数字(默认10进制)避免多次%10取数
stoi(str, nullptr, 16);//将str字符串转化为16进制的数,输出16进制再转化为的10进制
//eg str="ff"则输出255
to_string();//将其他类型的转化为字符串
加号拼接
string s=“”;
直接s=s+“a”;//"a"可以为char型
消除首尾空格
s.erase(0.s.find_first_not_of(" “));//删除首部空格
s.erase(s.find_last_not_of(” ")+1);//删除结尾空格
删除
string s = "hello world";
// 删除从索引5开始到结尾的字符
s.erase(5);
//hello
// 删除从索引0开始长度为1的字符部分
s.erase(0, 1);
//ello
// 使用迭代器删除指定范围字符
string::iterator it = s.begin()+1;
s.erase(it, s.end());
//e
char 字符
isdigit(a);//判断a字符是否是数字
isalpha(a);//判断a字符是否是字母
关键词
- 通用基础
- 数据结构性质
- 数据结构
通用基础
值引用和引用传递
在C++中以值传递参数时,实际上是创建了实参的一个副本。这就意味着你在每次进行递归调用时都在复制整个数组。这导致了你的时间和空间复杂度大大增加
而引用传递参数,可以实现不复制整个数组,直接引用原数组,很大程度减少空间和时间复杂度
总结!在递归调用,只需要读取的参数最好也是用引用传递
make_pair()和pair()
- 作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型。
- C++标准程序库中凡是“必须返回两个值”的函数, 也都会利用pair对象。
当数据以make_pair/pair 绑定到了一起则想要访问不是[0][1],而是first和second
定义数据结构时:deque<pair<string,int>> haha;
运行普通函数:haha.push_front(make_pair(“123”,1));
insert和emplace
insert和emplaceinsert是插入一个完全的对象,而emplace是先调用该对象的构造函数生成该对象,再将该对象插入vector中,使用emplace时,该对象类必须有相应的构造函数
struct A{
int a;
float b;
A(int _a,float _b): a{_a},b{_b} {};//使用emplace必须要有构造函数
//没有会产生报错
//no matching function for call to 'main()::A::A(int)'
A(int _a):a{_a},b{0.0} {};
};
A x1(1,1.1);
vector<A> v1;
v1.insert(v1.end(),x1);
v1.emplace(v1.begin(),2,2.2);
v1.emplace_back(3,3.3);
v1.emplace_back(4);
for(int i=0;i<4;i++){
cout<<v1[i].a<<" "<<v1[i].b<<endl;
}
c++构造类
class LockingTree{
public:
//注意有些题目不需要通过构造去初始化,外部通过调用内部函数去给类传递数据(如下一示例)
LockingTree(可有外部系数可没有){//构造函数
this->a=a;
this->b=vector<int>9=(n,-1);
}
bool lock(){//内部函数
int y=b[i];
int x=a;
int a=x;//错误写法
a=x;//正确写法,int a是内部函数的局部变量,a才是整个构造数据类型的private变量
...}
private:
//属性并不一定要写在private里面,可以写在public或者protected里面
//在该类中定义的属性,内部都能直接访问
int a;
vector <int> b;//写明属性
//注意!!!数据不能直接在构造函数中初始化,一定要先写明
//以及!!!不能内部函数中再定义属性,而是应该直接使用属性
//以及!!!如果数据有自定义的数据结构,可以在public:或者private:定义数据结构
//eg:
struct Node{
int c,d;
}
vector<Node> e;
}
eg:
c++结构体
1,结构体自带初始化
2,运算符重载(指定运算符进行操作)
struct Node{
int cnt,time;
//自带初始化
Node(int_cnt,int_time):cnt(_cnt),time(_time){}
//运算符重载
bool operator < (const Node&rhs) const{
return cnt==rhs.cnt?time<rhs.time:cnt<rhs.cnt;
}
}
struct Node {
vector<int> nums;
int num;
Node() = default;
Node(const Node&) = default;
Node& operator = (const Node&) = default;
Node(vector<int> _nums, int _num) : nums(_nums), num(_num) {} // 修改构造函数
};
初始化
创建对象
Node cache=Node(1,time);
创建指针
Node *pNode =new Node(1,time);
创建引用
attention:C++中,引用一旦被初始化为对某个对象的引用,就无法改变为引用另一个不同的对象。也就是说,引用一旦绑定到一个对象上,就不能解除绑定或者重新绑定到另一个对象上。
Node cache=Node(1,time);
Node& ca1=cache;
attention:对象和指针获取值的方式相同,直接用**.
引用获取值的方式为->**
运算符重载
set s;
s.begin();//s.begin()是以上规则中的min值
因为s为set类型数据集合,set自动进行排序,这个写法相当于重置set的排序规则
unordered_map插入自定义结构体小tips
unordered_map在插入元素时,需要拷贝或移动构造value对象。
但Node类没有实现默认构造函数和拷贝构造函数的话,无法完成拷贝操作
方法1,调用函数
Node cache=Node(1,time);
unordered_map<int, Node> m;
key_table.insert(make_pair(1,cache));
方法2,实现构造函数和拷贝构造函数
struct Node {
// ...
Node() = default;
Node(const Node&) = default;
Node& operator=(const Node&) = default;
};
unordered_map<int, Node> m;
Node cache=Node(1,time)/new Node(1,time);;
m[1]=cache;
memset函数
分割字符串
方法1
eg:将 “i love you”变成string a={“i”,“love”,“you”}
两层while循环,外层while循环索引单词起始位置,内层while循环快速锁定单词终止位置
int m=s.size();
int wordStart=0;//单词的起点索引
int wordEnd=0;//单词的终点索引(边界或指向空格)不包含
string word;
vector<string> resul;
while(wordStart<m){
while(wordEnd<m&&s[wordEnd]!=' ')wordEnd++;
word=s.substr(wordStart,wordEnd-wordStart);//截取单词
resul.push_back(word);
// 更新单词区间,起点为当前终点的下一个位置;终点初始与起点相同
wordStart = wordEnd + 1;
wordEnd = wordStart;
}
方法2
匿名函数lambda表达式实现,可以直接写在主函数中
解析主要做了:
- 定义了一个lambda表达式函数split,接受字符串s和分隔符delim。
- 返回值设定为vector类型,即返回多个子串组成的字符串向量。
- 声明cur字符串变量存储当前累积的子串,ans变量存储最终结果。
- 对s中的每个字符:
- 如果是分隔符delim,则将cur加入ans,并清空cur重新开始累积新子串。
- 否则将字符添加到cur末尾。
- 循环结束后,将最后一个未加入的cur子串移动加入ans。
- 返回ans向量。
通过判断字符是否为分隔符,动态累积子串字符串。利用move函数进行高效字符串移动避免复制,返回结果vector采用移动语义。实现了按分隔符分割字符串的功能。
auto split = [](const string& s, char delim) -> vector<string> {
vector<string> ans;
string cur;
for (char ch: s) {
if (ch == delim) {
ans.push_back(move(cur));
cur.clear();
}
else {
cur += ch;
}
}
ans.push_back(move(cur));
//move() 将对象的状态或者所有权从一个对象转移到另一个对象
//只是转移,没有内存的搬迁或者内存拷贝,所以可以提高效率,改善性能。
return ans;
};
vector<string> result=split(path,'/');
//将字符串数组path以/分割为多个子字符串
翻转链表
方法1
定义三个辅助节点,一个指向当前需要进行指向翻转的节点(cur),一个指向cur的前一个节点(pre),一个指向cur的后一个节点(nextt)
listnode* reverse(listnode*head){
listnode*pre=nullptr,*cur=head;
while(cur){
listnode*nextt=cur->next;
cur->next=pre;
pre=cur;
cur=nextt;
}
return pre;//遍历到后面cur指向空节点,pre指向原链表尾节点,即新链表头节点
}
应用
leetcode t92反转链表
ListNode *reverseBetween(ListNode *head, int left, int right) {
ListNode *dummy = new ListNode(0, head), *p0 = dummy;
//定义头节点指向首节点,因为若首节点也参与反转,此时找不到首节点前一个节点
//定义头节点为首节点的前一个节点
for (int i = 0; i < left - 1; ++i)
p0 = p0->next;
//1,先找到链表中反转链表段的前一个节点
ListNode *pre = nullptr, *cur = p0->next;
//2,需要反转的链表段的每一个节点的指向都翻转
for (int i = 0; i < right - left + 1; ++i) {
ListNode *nxt = cur->next;
cur->next = pre; // 每次循环只修改一个 next
pre = cur;
cur = nxt;
}
//3,指向改变
//p0指向的next仍然为原反转链表段的首节点
//cur指向的是反转链表段的后一节点
//pre指向的是反转链表段的尾节点,反转后为首节点
p0->next->next = cur;
p0->next = pre;
return dummy->next;
}
最大最小值
INT_MIN:-2147483648(-231)****INT_MAX:2147483647(**231-1)****LONG_MIN:-2147483648(**-231)****LONG_MAX:2147483647(**231-1)****LLONG_MIN:-9223372036854775808(-**263)****LLONG_MAX:9223372036854775807(**263-1)
数据结构性质
数据结构底层
底层红黑树 会自动进行全排列 增删改查为o(logn):树高红黑树为平衡二叉搜索树
- 平衡二叉树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1
- 二叉搜索树: 根节点的左子树值均小于根节点的值,根节点的右子树值均大于根节点的值。
- vector 数组
- list 链表
- map 键值对 底层红黑树
- set 集合 元素唯一 底层红黑树
- multiset 集合 元素可重复 底层红黑树
- priority_queue 优先队列 底层堆:大根堆/小根堆
数据结构必记
- 栈头进头出
- 队列头进尾出
数据结构
vector可变多维数组
定义数组:
vector<vector> g;//注意!单纯定义数组,没有初始化数组时,直接访问数组会报错
数组初始化:
vector<vector<int>> v;
v.resize(n);
for(int i=0;i<n;i++){
v[i].resize(m);
}
数组定义同时初始化://常用!!
一维数组:
vector g(n,0);//g数组有n位,每一位都是0
二维数组:
vector<vector> v(n,vector(m,num));//n行m列,初始化数组数值为num
vector<vector>g(n);//n个int*的变量
数组索引:
g.back()[i];//索引最后一个元素的第i维数据
g[i]/g[i][j]
遍历数组:
for(auto&row:g){
for(aoto&col:row){
}
}
数组操作:
插入
- g[i].push_back(a);//在g的第i行后加数据a
- g[i].emplace_back(a);//c++11之后使用,前提是有对应的构造函数
数组大小
g.size();g[i].size();
排序
sort(a.begin(),a.end());//默认从小到大排序
sort(a.begin(),a.end(),greater<int>());//从大到小排序
//自定义排序方式
//方式1
bool comp(const int&a,const int&b){return a>b;}//comp的意思为从大到小排序
sort(a.begin(),a.end(),comp);
//attention!
//从大到小排序return a>b
//从小到大排序return a<b
//方式2 lambda函数
sort(a.begin(),a.end(),[](vector<int>&v1,vector<int>&v2)->bool{
if(v1[0]==v2[0]) return v1[1]<v2[1];
return v1[0]<v2[0];
});//二维数组排序,先按第一个维度排序,再按第二个维度排序,v1,v2的比较实际上是a内数据的比较
sort(a.begin(),a.end(),[&](int a,int b){
return c[a]<c[b];});//c[]不是函数内的系数需要进行捕捉,即c[]数组与a无关
//[&]是一个“捕捉列表(capture list)”,用于描述将要被lambda函数以引用传参方式使用的局部变量
//[]意思是什么都不捕捉
已知无序数组,不改变原数组获得有序排列(即对于原数组获得排列索引
vector<int> id(plantTime.size());
iota(id.begin(),id.end(),0);//id[i]=i
sort(id.begin(),id.end(),[&](int i,int j){ return growTime[i]>growIime[j];});
清空数组
g.clear();
范围内数组值操作
accumulate是numeric库中的一个函数,主要用来对指定范围内元素求和,但也自行指定一些其他操作,如范围内所有元素相乘、相除等。
accumulate(起始迭代器, 结束迭代器, 初始值, 自定义操作函数)
accumulate(arr.begin()+2, arr.end(),0);// 初值0 + (arr[2]… + arr[n-1])
accumulate(arr.begin(), arr.end(),1,multiplies());// 初值1 * (1 * 2 * 3 * 4 *… * 10)
attention:对数组进行累加,会导致超出int上限的情况
解决办法:定义初值为0LL,C和C ++中的常量。后缀LL表示常量的类型为long long
long long sum=accumulate(milestones.begin(),milestones.end(),0LL);//解决办法是初值定义为0LL
数组最大值
int maxV = *max_element(milestones.begin(), milestones.end());
普通数组
初始化
int a[]={ };
int b[][c]={ };//c++的二维数组必须标明列数;
={{},{},{}};
长度
数字型:
sizeof(a)/sizeof(a[0])
sizeof(a[0])/sizeof(数据类型)
字符型:
length()
size()
deque 双端动态数组
概念:
Vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间,又称双端动态数组。所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,无法被接受。
与vector的区别
- deque允许使用常数项时间对头端进行元素的插入和删除 操作
- deque没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,换句话说,像vector那样,”旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque身上是不会发生的
- 除非有必要,我们应该尽可能的 使用vector,而不是deque。对deque进行的排序操作,为了最高效率,可将deque先完整的复制到一个vector中,对vector容器进行排序,再复制回deque。(排序比vector复杂)
deque容器实现原理
array 无法成长,vector虽可成长,却只能向尾端成长,而且其成长其实是一个假象,事实上(1) 申请更大空间 (2)原数据复制新空间 (3)释放原空间 三步骤
Deque是由一段一段的定量的连续空间构成。一旦有必要在deque前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在deque的头端或者尾端。Deque最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。 既然deque是分段连续内存空间,那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。Deque代码的实现远比vector或list都多得多。 Deque采取一块所谓的map(注意,不是STL的map容器)作为主控,这里所谓的map是一小块连续的内存空间,其中每一个元素(此处成为一个结点)都是一个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是deque的存储空间的主体。
deque常用api
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> deq;
//向后插入
deq.push_back(1);
deq.push_back(2);
//向前插入
deq.push_front(0);
//打印
for(int x : deq){
cout << x << " ";
}
//删除第一个元素
deq.pop_front();
//在开头位置插入-1元素
deq.insert(deq.begin(), -1);
//反向打印
for(auto it=deq.rbegin(); it!=deq.rend(); it++){
cout << *it << " ";
}
return 0;
}
st.empty();//是否为空
查找
st.front();//头部元素
st.back();//尾部元素
随机访问
#include <deque>
using namespace std;
int main() {
deque<int> deq = {1,2,3,4,5};
// 随机访问第3个元素
int elem = deq[2];
// 访问最后一个元素
int last = deq[deq.size()-1];
// 修改第二个元素
deq[1] = 10;
return 0;
}
增删
st.insert(st.begin(),-1);//在开头位置插入元素-1
st.pop_front();
st.push_front();
st.pop_back();
st.push_back();
attention:和队列/栈一样,想要获得删除的数需要先取数再删数
a=b.front();
b.pop_back();
unordered_map哈希表(无序,查找复杂度o(1))
初始化
unordered_map<key,value>name;
查找
//找到
auto it=name.find(key);
int it=name.count(key);//(i>0则找到了)
//找到之后取值
//取value
valuetype a=it->second;
//取key
keytype b=it->first;
//找不到
if(it==name.end());//如果找不到的话it迭代器会指向末尾
二分查找
lower_bound(a);//找到第一个大于等于a的数
upper_bound(a);//找到第一个大于a的数
插入
- name.insert(make_pair(key,value));
- name[key]=value;
删除
name.erase(key);
大小
name.size();
清空
name.clear();
修改(注意修改value时,如果value是自定义构造体,则必须对整个value进行修改)
key_table [key].time=time;(x)
auto it=key_table.find(key);
Node a=it->second;
change(a);
it->second=a;
遍历
for(auto [key,value]:name){}
//直接将key:value一个一个取出来去遍历
map与unordered_map比较
- 底层实现:map采用红黑树,unordered_map采用哈希表
- map元素按照键的大小进行有序排列,unordered_map无序, unordered_map则适合用于需要快速查找元素的情况下,例如查找是否存在某个键值对、统计某个值出现的次数等
map 键值对
set 集合
set中每个元素的值都唯一,而且系统能根据元素的值自动排序
attention!set中元素的值不能直接被改变,得已知a,先删a,再修改a,再将a增加进去
增删改查的复杂度为o(logn)
插入
s.insert(a);
删除
s.erase(a);//可以删除元素或是指向元素的迭代器
查找
s.find(a);
最小值
s.begin();//注意s.begin()返回的是迭代器,取值为s.begin();
注意!!set只有begin()获取第一个元素的迭代器原因是:
- set作为无序容器,元素按关键字自动排序,没有固定的"第一个"元素概念
- 但begin()迭代器指向红黑树中的最小值元素
所以获取set容器第一个元素,可以:
- 调用begin()获取最小值元素的迭代器
- 或直接解引用*begin()
multiset 集合
其他都和set一致,唯一区别multiset允许存储重复的元素
插入删除查找同set
最小值
*s.begin();
最大值
*s.rbegin()
multiset删所有重复
a.erase(n);//n为元素
multiset删一个重复元素
auto it=set1.find(n);
if(it!=set1.end()){
set1.erase(it);};
unordered_set 集合
其他都和set一致,唯一区别set会自动排序,unordered_set不会自动排序
初始化
unordered_set a={‘a’,‘b’,‘c’};
api
.count();//计算个数
.contains(str)//查找该序列中是否存在,返回bool类型
随机索引
unordered_set <int> myset;
auto it=myset.begin();
std::advance(it,rand()%myset.size());
//std::advance函数用来使迭代器it指向任意位置,它通过迭代器的后继运算符++实现移动。
//注意集合数据结构不能和数组一样按照数字索引直接取值 nums[i],而是应该先获得一个迭代器,再按照迭代器往后移动
list 双向链表
初始化
list a;
list :: iterator b;//迭代器 索引
- list容器为列表本身,可以直接修改列表。
- 迭代器为列表遍历指针,不能直接修改列表,仅用于迭代访问每个元素。
插入
a.push_back©;
a.push_front(d);
指定位置插入元素
iterator insert(iterator position, const value_type& val);
#include <list>
list<int> lst;
// 在lst中第二个位置插入值5
list<int>::iterator it = lst.begin();
std::advance(it, 1);
lst.insert(it, 5);
删除
a.erase(b);//删除b索引对应的元素
a.pop_back();
a.pop_front();
查找
查找某个值:list :: iterator it1 =find(l1.begin(),l1.end(),3);//查找l1中的数字3的位置
查找某个位置的值:
list::iterator it = lst.begin();
std::advance(it, 1); //it指向链表中的第二个位置
front();back();返回元素本身
begin();end();返回迭代器
最后一个元素
1,a.back();
list<int> mylist;
//...
int last = mylist.back();
2,a.end();
list<int>::iterator it = mylist.end();
it--;
int last = *it;
这两种API的区别和用法:
- back()直接获取最后一个元素,更简单直接。
- end()获取最后一个元素后位置的迭代器,需要前置减运算获得最后元素。
使用场景:
- 只需要元素值,使用back()更简单直接。
- 需要迭代列表,如遍历等操作,可以使用end()获取迭代器。
第一个元素
1,a.front();
list<int> mylist;
//...
int first = mylist.front();
2,a.begin();
list<int>::iterator it = mylist.begin();
int first = *it;
这两种API的区别和用法:
- front()直接获取第一个元素,更简洁直接。
- begin()获取第一个元素的迭代器,需要解引用操作*it才能访问元素。
使用场景:
- 只需要元素值,使用front()更简单直接。
- 需要迭代列表,如遍历等操作,可以使用begin()获取迭代器。
queue 队列
queue q;
q.push/pop/front/empty();
出队操作:
int a=q.pop();//错误
int a=q.front();
q.pop();//正确
priority_queue 优先队列
小根堆:父节点的值小于或者等于子节点的值,浮出最小值
大根堆:父节点的值大于或者等于子节点的值,浮出最大值
初始化
priority_queue p;//默认大根堆
修改为小根堆
1,greater作为比较器
priority_queue <int,vector,greater >q;//小根堆
priority_queue <int,vector,less >q;//大根堆
attention!当我们声明的时候碰到两个<或者两个>放在一起的时候,一定要记得在中间加一个空格,这样编译器才不会把两个连在一起的符号判断成位运算的左移或者右移。
2,自定义优先级
struct cmp {
bool operator() (int a, int b) {
return a < b;
}
};
priority_queue<int, vector<int>, cmp> pq;
api
q.push();//插入元素到队尾并排序
//注意不存在push_back函数,将数放入容器自动排序而不是放置在末尾
q.pop();//弹出队头元素
q.top();
q.empty();
与set比较
优先队列基于堆,仅保证首元素max/min
set基于红黑树,全排列
stack 栈
p.push();
p.pop();
p.top();
注意!!仅有stack和priority_queue(栈和优先队列)是top()取第一个数,其他为front()或者begin()
p.empty();
stringstream 字符串流
声明
#include
stringstream ss;
写入数据
int age = 30;
ss << age<<“dsa”<<“dsadas”;
//空格也会如实记录到字符串流中
读取数据
string name;
ss >> name;
//提取字符串流的内容直到空格为止
提取string内容,空格也会提取
string str = ss.str();
清除stringstream
ss.clear();
string 字符串
查找
s.find(a);//在字符串s中查找是否有子字符串a,如果不存在返回-1
替换
string replace (size_t pos, size_t len, const string& str);
pos表示要替换的子串在原字符串中的起始位置,len表示要替换的原来子串的长度,str表示用来替换的字符串。
void replace(string&s){
int pos=s.find(" ");
while(pos!=-1){
s.replace(pos,1,"");
pos=s.find(" ");
}
}
//注意这个代码在数据量过多的时候可能会出现超出时间限制,因为replace和find操作的复杂度都是o(N)级别的
//以下代码只需要对字符串进行一次遍历
void replaceSpaces(string S) {
string mystr = "";
for (int i = 0; i < S.size(); i++)
{
if (S[i] == char(' '))
mystr += "%20";
else
mystr += S[i];
}
}
};
截取
s.substr(a,b);//从下标为a(a>=0)的位置开始,取b个数
转换
stoi();//将数字类型的字符串转化为数字(默认10进制)避免多次%10取数
stoi(str, nullptr, 16);//将str字符串转化为16进制的数,输出16进制再转化为的10进制
//eg str="ff"则输出255
to_string();//将其他类型的转化为字符串
加号拼接
string s=“”;
直接s=s+“a”;//"a"可以为char型
消除首尾空格
s.erase(0.s.find_first_not_of(" “));//删除首部空格
s.erase(s.find_last_not_of(” ")+1);//删除结尾空格
删除
string s = "hello world";
// 删除从索引5开始到结尾的字符
s.erase(5);
//hello
// 删除从索引0开始长度为1的字符部分
s.erase(0, 1);
//ello
// 使用迭代器删除指定范围字符
string::iterator it = s.begin()+1;
s.erase(it, s.end());
//e
char 字符
isdigit(a);//判断a字符是否是数字
isalpha(a);//判断a字符是否是字母