以下是C++ 机试里面最常用的数据结构和函数的操作,包括vector、stack、queue、set(和pair、tuple等组合)、unordered_set、map、unordered_map、 priority_queue 以及自定义sort、string、位操作、进制转换等(可充当常用api字典),没有技巧,全是模板;
vector
构造
vector<int> v1(5); //创建一个vector,元素个数为5
vector<int> v2(5, -1); //创建一个vector,元素个数为5,所有元素值-1
vector<int> v3(v2); // 复制构造函数
vector<int> v4(v3.begin(), v3.end()); //复制v3[begin,end)区间内的元素到v4中
操作
v1.push_back(1); // 尾插
cout << v1.size() << endl; // 输出6
/* 插入 */
v2.insert(v2.begin(), 100); // 第0个位置100,后面都是初始化的-1
v2.insert(v2.begin() + 1, 5, -100); // 第1个位置开始连插入5个-100
v2.insert(v2.end(), v1.begin(), v1.end()); // v2的末尾插入v1
for (auto it : v2) cout << it << ' '; // 遍历
/* 删除 */
v1.erase(v1.begin());
v1.erase(v1.begin(), v1.end());
stack & queue (这两底层也是vector实现的)
就几个操作,一般用到stack或者queue的题都比较明显
栈:
stack<int> s1;
/* 状态 */
cout << s1.size() << endl;
cout << s1.empty() << endl;
/* 操作 */
s1.push(1);
int tmp = s1.top();
s1.pop();
队列:
queue<int> q1;
/* 状态 */
cout << q1.size() << endl;
cout << q1.empty() << endl;
/* 操作 */
q1.push(1);
int tmp = q1.front();
int tmp2 = q1.back(); // 最后一个元素(很少用)
q1.pop();
set & map
set 和 map 两者本身有序, set按照存放的数排序,map按照key排序(默认从小到大),所以插入查找等操作都是logN的;这里需要注意的是,专业级的设计题有点花,set和map经常结合pair/tuple存信息,unordered_map套set套pair这种,但下面总结的肯定够用;
集合:
/* 构造 */
set<int> set1;
set<pair<int, int>> set2; // 按照 a 排, a 排完按照 b 排(前面是a,后面是b,意会一下)
set<tuple<int, int, int>> set3; // 按照 a 排, a 排完按照 b 排, b 排完按照 c
vector<int> v1;
set<int> set4(v1.begin(), v1.end()); // 偶尔可能需要把给的数据转换方便后续操作
/* 插入 */
set1.insert(1);
set2.insert(make_pair(1, 2));
set3.insert(make_tuple(1, 2, 3));
/* 查找find, 返回的是迭代器 */
set1.find(1) == set1.end() ? false : true;
/* 遍历 */
这里从前往后遍历出来的数 == 从小往大,如果题目要求从大往小,就从后往前遍历:
for(auto it = set1.end(); it != set1.begin(); it--)一样的
for (int it : set1) {
cout << it << endl;
}
for (const auto &it : set2) {
cout << it.first << '-' << it.second << endl;
}
for (const auto &it : set3) {
cout << get<0>(it) << '-' << get<1>(it) << '-' << get<2>(it) << endl;
}
/* 删除 */
set1.erase(1);
set2.erase({1, 2});
set3.erase({2, 3, 2});
映射 map
// map<key, value> key 唯一
map<int, int> map1;
map<int, pair<int, int>> map2;
map<int, tuple<int, int, int>> map3;
/* 插入 */
map1.insert({1, 2}); // 等于map[2] = 3
map2[1] = {2, 3}; // map[key] = value, 显然key相同会自动重叠
map3[1] = {8, 9, 10};
/* 查找 */
ps:不要老想着用value查key,个人认为如果频繁用value查key就失去了map设计的意义;
如果value也是唯一的,可以再堆个map映射value到key,专业级有挺多这样花来花去的题,空间换时间
bool isExist = map1.find(1) == map1.end() ? false : true;
cout << "find:" << map1.find(1)->first << '-' << map1.find(1)->second << endl;
cout << "find:" << map2[1].first << '-' << map2[1].second << endl;
/* 删除 */
map1.erase(1)
map2.erase(1)
map3.erase(1); // 只需要erase key就行了
/* 遍历 */
for (auto it : map1) {
cout << it.first << '-' << it.second << endl;
}
for (const auto it : map2) {
cout << it.first << '-' << it.second.first << ',' << it.second.second << endl;
}
for (const auto &it : map3) {
cout << it.first << '-' << get<0>(it.second) << ',' << get<1>(it.second) << ',' << get<2>(it.second) << endl;
}
unordered_set & unordered_map
这里不详细写了,和上面的操作语法一样,唯一不一样的是这两内部数据无序,hash实现的,查找和插入是O(1)
题目如果没有要求排序,总是要求查找就用这两
这里推荐一道题: leetcode 1912 这个题得做明白
String
string 的操作很碎,但有用(输出看一看,以免有错)
string str = "abcdefg";
string str_1 = "hijk";
/* 长度、空等 */
cout << str.empty() << endl;
cout << str.size() << endl;
cout << str.length() << endl;
/* 子串 */
cout << str.substr(0, 3) << endl; // abc
/* 翻转 */
reverse(str.begin(), str.end()); // gfedcba
cout << str << endl;
/* 插入 */
str.insert(str.begin(), '1'); // 1gfedcba
str_1.insert(1, str); // h1gfedcbaijk
/* 删除 */
cout << str << endl; // 1gfedcba
str.erase(3, 2); // pos = 3, len = 2 : 1gfcba
str.erase(str.begin() + 2, str.begin() + 3); // 1gcba
/* 数值转换 int & string */
string str_2 = "123";
int a = stoi(str_2);
str_2 = to_string(a);
/* char 数组 和 string 互相转换 */
char ch1[] = "1234567890";
string str_3(ch1);
string str_4 = ch1;
char buf[2];
str_3.copy(buf, 2); // 拷贝到buf里,考2位
sort
sort库函数下面本质是qsort快排,这里主要列举自定义排序规则的模板;
优先队列heap_priority也放在这里,因为本质优先队列底层是堆排序,题目如果出现求topN可以考虑用优先队列,下面也列了优先队列自定义排序规则的模板;
排序:sort
/* vector 直接排 */
vector<int> v1 = {21, 2, 23, 1};
sort(v1.begin(), v1.end()); // 小到大 1-2-21-23
sort(v1.begin(), v1.end(), greater<>()); // 大到小 23-21-2-1
/* 自定义结构体排 */
struct node
{
int num1;
int num2;
};
/* num1由小到大,num2由大到小,自定义 */
bool cmp_n(node a, node b) {
if (a.num1 == b.num1) return a.num2 > b.num2;
else return a.num1 < b.num1;
}
int num1 = 2, num2 = 3;
vector<node> v2 = {node{1, 2}, node{num1, num2}, node{2, 4}};
sort(v2.begin(), v2.end(), cmp_n); // {1, 2} {2, 4} {2, 3}
/* unordered_map value sort 极少出现,看看咋操作就行 */
unordered_map<string, int> umap = {
{"zhangsan", 3},
{"zhaoliu", 5},
{"wangwu", 5},
{"lisi", 4}
};
// 先转vector再排序
vector<pair<string, int>> arr;
for(const auto& it : umap) {
arr.emplace_back(it);
}
sort(arr.begin(), arr.end(), [](auto p1, auto p2) {return p1.second == p2.second ?
p1.first < p2.first : p1.second < p2.second;});
优先队列
priority_queue<int> heap1; // 最基础,大顶堆
priority_queue<int, vector<int>, less<>> heap2; // 大根(这里vector是指定内部用什么来实现优先队列,这里就是用vector,下同)
priority_queue<int, vector<int>, greater<>> heap3; // 小根
/* 操作 */
heap1.push(2);
heap1.push(3);
heap1.push(0);
while (!heap1.empty()) {
cout << heap1.top();
heap1.pop();
} // 3 2 0
/* 自定义优先队列 */
template <typename T>
class cmp_n_2
{
public:
//重载 () 运算符
bool operator()(T a, T b)
{
return a.num1 < b.num1; // 大根堆
}
};
priority_queue<node, vector<node>, cmp_n_2<node>> heap4;
heap4.push(node{1, 4});
heap4.push(node{3, 2});
heap4.push(node{2, 2});
while (!heap4.empty()) {
cout << heap4.top().num1; // num1: 3 2 1
heap4.pop();
}
cout << endl;
/* 优先队列配合 pair */
priority_queue<pair<int, int>> heap5; // first降序 + second降序
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> heap6; // first升序 + second升序
pair<int, int> a(3, 4);
pair<int, int> b(5, 4);
pair<int, int> c(5, 3);
heap6.push(a);
heap6.push(b);
heap6.push(c);
while (!heap6.empty()) {
cout << heap6.top().first << '-' << heap6.top().second << endl;
cout << endl;
heap6.pop();
}
进制转换
10进制2进制16进制互相转换,这里不用库函数是因为都想用string类型来承接转换结果
/*
* 任意2-36进制数转化为10进制数, 入参radix:进制
* */
int AtoInt(const string& s, int radix) {
int ans = 0;
for(char t : s)
{
if(t >= '0' && t <= '9') ans = ans * radix + t - '0';
else ans = ans * radix + t -'a' + 10;
}
return ans;
}
/*
* 10进制数转其他进制,注意最后出参是string
* */
string intToA(int n, int radix) // n是待转数字,radix是指定的进制
{
string ans;
do {
int t = n % radix;
if (t >= 0 && t <= 9) ans += t + '0';
else ans += t - 10 + 'a';
n /= radix;
} while (n != 0); // 以防止输入为0的情况
reverse(ans.begin(), ans.end());
return ans;
}
2进制和16进制互转,注意是string类型和string类型
void AToA() {
/* string to string (16 to 2 & 2 to 16)
* stoi(字符串,起始位置,string进制)*/
string bi = "1111111001";
string hex;
stringstream ss;
ss << std::hex << stoi(bi, nullptr, 2);
ss >> hex;
cout << hex << endl;
cout << std::bitset<16>(stoi(hex, nullptr, 16)) << endl; // 16->10->2
}
位运算
位运算: & | ~ ^ <<(left) >>(right)
位运算非常容易和进制转换配合使用,下面举例:
/* 如果需要固定字节,int,16进制需要转成 unsigned char型 */
/* 16进制转换 */
string str1 = "f0";
string str2 = "00";
unsigned char ch1 = char(AtoInt(str1, 16)); // 用上面自己抄的函数
unsigned char ch2 = char(AtoInt(str2, 16));
uint16_t show1 = ((ch1 << 8) + ch2); // 右移8位再拼接上ch2的8位就是16位,前面得拿16位的来接住
uint16_t show2 = (((ch1 << 8) + ch2) & 0x1FFF); // 多了一个mask前三位
cout << show1 << '-' << show2 << endl;