导航
头文件
#include <iostream>
#include <string>
#include <cstring>
#include <cmath>
#include <sstream>
#include <iomanip>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>
#include <functional>
#include <queue>
#include <cctype>
#include <climits>
#include <stack>
using namespace std;
常用函数
位运算
-
左移。将二进制左移,右边补0
1 << N
相当于 2 N 2^N 2N
-
异或。位不同为1,位相同为0。
a ^ b ^ c = a ^ c ^ b
:交换律x ^ 0 = x
:任何数和0异或为其本身x ^ x = 0
:相同的数异或为0
快速排序
sort(v.begin(),v.end(),cmp)
- v是迭代器,如果只想将一部分排序,可以使用类似
sort(v,v+3)
- cmp为比较函数,默认是升序排序,也可以重载进行自定义。
return true
时x排在y前面。
bool cmp(const int& x, const int& y)
{
return x > y;
}
int main()
{
vector<int> vec{ 1,3,2 };
sort(vec.begin(), vec.end(), cmp);
cout << vec[0] << " " << vec[1] << " " << vec[2];//3 2 1
return 0;
}
小贴士
'c'-'a'
和'7'-'0'
用来表示字符的次序。'a'<= ch <='z'
用来判断为小写字母。ch+('A'-'a')
用来小写变大写- 线性时间复杂度遍历两次三次都没关系,所以可以第一次收集频率,第二次收集位置等等
- 使用字符串流来按行读取输入
int
格式的数据取值范围为 [ − 2 31 , 2 31 − 1 ] [-2^{31},2^{31}-1] [−231,231−1],这两个界可以分别用常量INT_MIN
和INT_MAX
表示(需加上头文件#include <climits>
)- 链表最好一直带一个头节点(不存储数据的结点)。如果给出的链表没有,可以自己
new
一个,然后NewNode->next=head
,也就是说,如果head
可能被删除,仍然可以用NewNode->next
表示第一个数据
Cctype
包含很多字符串处理函数的库。
#include <cctype>
检测
isalnum(ch);//是否为字母数字
isalpha(ch);//是否为字母
islower(ch);//是否为小写字母
isupper(ch);//是否为大写字母
isdigit(ch);//是否为数字
转换
注意:非字母的字符不会处理。
tolower(ch);//转为小写字母
toupper(ch);//转为大写字母
String
字符串。
#include <string>
string str;
基本操作
初始化
遍历
for(int i=0; i<str.length(); i++)
{
char c=str[i];
}
元素编辑
进阶操作
- 字符串所有小写字母变为大写
transform(str.begin(),str.end(),str.begin(),toupper);
- 字符串所有大写字母变为小写
transform(str.begin(),str.end(),str.begin(),tolower);
Vector
迭代器,可以充当动态数组
#include <vector>
vector<char> s;
基本操作
初始化
vector<char> s1(3,'a');//['a','a','a']
vector<char> s2{ 'a','b','c' };//['a','b','c']
vector<char> s3(3);//['\0','\0','\0']
遍历
for(int i=0; i<s.size() ;i++)
{
char c = (*it);
}
vector<char>::iterator it;
for (it = s.begin(); it != s.end(); it++)
{
char c = (*it);
}
元素编辑
int l=s.size();//返回元素个数
char c=s[2];//类似数组的存取方法
s.push_back('A');//在尾部增加元素
s.pop_back();//删除尾部元素
Map
散列表。map由红黑树构建,自动会排序键,适合排序题,unordered则适合查询题,省去排序时间。
#include <map>
map<string,int> m;
unordered_map<int,int> mm;
基本操作
初始化
vector<char> s1(3,'a');//['a','a','a']
vector<char> s2{ 'a','b','c' };//['a','b','c']
vector<char> s3(3);//['\0','\0','\0']
遍历
map<string,int>::iterator it;
for(it = m.begin(); it!=m.end(); ++it)
{
cout<< it->first << " " << it->second << endl;
}
元素编辑
int l=m.size();//返回元素个数
m["a"]=1;//如果存在,则赋值;如果不存在,则会自动增加键值对。
m.erase("a");//删除该键
if(m.count("a"))//count只会是0(不存在)或1(存在),因为键不会重复
递归
将问题分解为越来越小的子问题,调用递归函数递归地解决。对于数学问题通常要写出递归式
基础案例
阶乘
$$
f(n)=\left { \begin{matrix}
1&n=1\
n\cdot f(n-1)&n>1
\end{matrix} \right.
$$
- 递归出口:n=1 时,f=1
- 递归传递:返回参数 n 的值
- 单次递归内的过程:用 n 乘以“递归传递”的返回值
int fact(int n)
{
if (n <= 1) return 1;
return n * fact(n - 1);
}
两两交换链表节点
- 递归出口:
head
或head->next
为空指针 - 递归传递:返回交换后的链表头指针
- 单次递归内的过程:先将
head
指向“递归传递”的返回值,再将head->next
指回head
ListNode* swapPairs(ListNode* head) {
if (head == NULL || head->next == NULL) {
return head;
}
ListNode* next = head->next;
head->next = swapPairs(next->next);
next->next = head;
return next;
}
DFS
栈
一种先进后出的数据结构。可以理解为只在尾部进行增删操作。
#include <stack>
stack<int> S;
基本操作
S.push(1);//元素插入到尾部
int a=S.top();//获取尾部元素的值
S.pop();//删除尾部元素
int len=S.size();//获取栈长度
if(S.empty())//判断栈是否为空
链表
一种链式存储的数据结构。
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
快慢指针
声明两个指针slow
和fast
,让他们以不同的方式遍历链表。
- 起跑线不同:
slow
和fast
的初始结点不同,步长相同。这样遍历时fast
始终领先于slow
N个节点
ListNode *slow = head;
ListNode *fast = slow;
while (N--)
{
fast = fast->next;
}
//开始遍历
- 步长不同:
slow
和fast
的初始结点相同,步长不同。这样遍历时fast
会越来越快
ListNode *slow = head;
ListNode *fast = slow->next;
//开始遍历
while (fast != NULL)
{
slow = slow->next;
fast = fast->next==NULL ? fast->next : fast->next->next;
}
树
一种非线性存储的数据结构。树的大家族中最常用的就是二叉树了:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
建立二叉树
- 按根-左-右输入顺序建立
TreeNode* InitTree()
{
int val;
TreeNode* root;
cin>>val;
if(val==0) return NULL;
else
{
root=new TreeNode(val);
root->left=InitTree();
root->right=InitTree();
}
return root;
}
TreeNode* root=InitTree();
遍历二叉树
因为二叉树的特殊结构,所以递归是一个很好的遍历方式:
void XXXOrder(TreeNode* T)
{
if(T!=NULL)
{
visit(T);//根
XXXOrder(T->left);//左
XXXOrder(T->right);//右
}
}
只要交换代码的顺序就能轻易实现先序、中序、后序遍历。
但是递归容易产生较高的复杂度,因此常常写成借助栈的非递归形式。非递归形式的难点是难以记忆,但其实本质上的原理还是一样的:
void XXXOrder(TreeNode* T)
{
map<TreeNode*,int> visitTimes;//键不存在代表未访问过,存在代表已压栈过
stack<TreeNode*> S;
TreeNode* CurrNode;
S.push(T);
while(!S.empty())
{
CurrNode=S.top();
S.pop();
if(CurrNode==NULL) continue;
//这是第一次访问根,需要按照所求遍历(如右左根)相反的方向压栈
if(visitTimes.count(CurrNode)==0)
{
visitTimes[CurrNode]=0;S.push(CurrNode);//根
S.push(CurrNode->left));//左
S.push(CurrNode->right));//右
}
//这是第二次访问,需要进行输出
else
{
visit(CurrNode);
}
}
}
注意这种写法C++并不能取得较高的效率.
先序遍历
顺序是 根节点>左子树>右子树。
- 遇到非空结点就访问。并把左子树作为下一个访问对象
- 如果结点为空,说明根结点与左子树访问完毕。取出栈顶结点,去访问他的右子树。
void PreOrder(TreeNode *T)
{
stack<TreeNode *> S;
TreeNode* p = T;
while (p!=NULL || !S.empty())
{
if (p!=NULL)
{
//1.Root
int v=p->val; //visit p
S.push(p);
p = p->left; //2.Left
}
else
{
p=S.top();
S.pop();//退栈
p = p->right; //3.Right
}
}
}
中序遍历
顺序是 左子树>根节点>右子树。
- 遇到非空结点就先入栈。保存住根节点的信息。然后先访问左子树。
- 如果结点为空,说明栈顶存的是最左边未访问的子树了。此时取出栈顶结点,进行访问。然后再访问右子树
void InOrder(TreeNode *T)
{
stack<TreeNode *> S; //Save Roots
TreeNode *p = T;
while (p != NULL || !S.empty())
{
if (p != NULL)
{
S.push(p);
p = p->left; //1.Left
}
else
{
p = S.top(); //2.Root
S.pop();
int v = p->val; //visit p
p = p->right; //3.Right
}
}
}
后序遍历
顺序是 左子树>右子树>根节点。注意,空树需要单独判断,因为先访问子树。关于后序遍历有较多的非递归解法:
- 迭代:只有当上一个访问的节点是当前节点的子节点时,才说明当前节点左右子树都访问完毕,才可以访问当前结点并出栈。不然就按右子树>左子树的顺序入栈。
- 逆向输出。将先序遍历访问结点的顺序改为根>右>左,然后逆向输出,即为左>右>根。这个方法需要两个栈。
void PostOrder(TreeNode *T)
{
stack<TreeNode *> S;
stack<int> result;
TreeNode* p = T;
while (p!=NULL || !S.empty())
{
if (p!=NULL)
{
//1.Root
int v=p->val; //visit p
result.push(v);
S.push(p);
p = p->right; //2.Right
}
else
{
p=S.top();
S.pop();//退栈
p = p->left; //3.Left
}
}
}