算法比赛备赛笔记

个人觉得,对于计算机专业的大学生来说,算法竞赛应该是性价比最高的比赛了。除了icpc和ccpc这两个比较难拿国奖之外,其他的比赛获奖难度并不大,比如蓝桥杯、天梯赛、睿抗,认真学习一年算法,水个国奖完全没问题。

本篇博客是我在一年多的学习和比赛中所做的笔记,记录的内容都是我认为在比赛中高频次出现的算法,而且除了线段树之外都是比较基础的算法。

应该会不断更新吧。

一. 算法

1. 数论基础

循环小数转换为分数

转换方法很简单,用例子来说明:

  • 0.1234234234… = 1 / 10 + 234 / 9990 = 137 / 1110
  • 0.11111… = 1 / 9
  • 0.012343434… = 12 / 1000 + 34 / 99000 = 611 / 49500

总而言之就是把小数拆成两部分:非循环部分和循环部分。非循环部分除以10的n次幂得到一个分数,循环部分除以999…和10的n次幂的积,得到一个分数。两个分数加起来,通分,就得到了最后的结果。

蓝桥杯例题:
算法比赛备赛笔记_第1张图片

import java.util.List;
import java.util.Scanner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;


public class Main{
	static Scanner sc = new Scanner(System.in);
	static int p, q;
	static Integer[] arr = new Integer[1000010];
	static double EPS = 1e-8 * 0.35;
	static long gcd(long a, long b) {
		if(b == 0) {
			return a;
		}
		return gcd(b, a % b);
	}
	public static void main(String[] args) {
		p = sc.nextInt();
		q = sc.nextInt();
		String s = sc.next();
		long x1 = p == 1 ? 0 : Long.valueOf(s.substring(0, p - 1));
		long y1 = 1;
		for(int i = 1; i < p; i++) {
			y1 *= 10;
		}
		long x2 = Long.valueOf(s.substring(p - 1));
		long y2 = 9;
		for(int i = p; i < q; i++) {
			y2 = y2 * 10 + 9;
		}
		for(int i = 1; i < p; i++) {
			y2 *= 10;
		}
//		System.out.println(x1 + " " + y1);
//		System.out.println(x2 + " " + y2);
		long x = x1 * y2 + x2 * y1;
		long y = y1 * y2;
		long g = gcd(x, y);
		System.out.println(x / g + " " + y / g);
	}
}

异或运算规律

  1. 归零律:a ^ a = 0,
  2. 恒等律:a ^ 0 = a
  3. 交换律:a ^ b = b ^ a
  4. 结合律:a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c
  5. 自反:a ^ b ^ b = a,有这样的关系:a ^ b = c → c ^ b = a ^ b ^ b = a
  6. d = a ⊕ b ⊕ c 可以推出 a = d ⊕ b ⊕ c.

约数相关

一个数A可以被分解成如下若干个质因数乘积的形式,pi为质因数:
在这里插入图片描述

约数个数公式:
在这里插入图片描述

约数之和公式:
在这里插入图片描述

获取质因数p和它们的指数,返回值map的key是质因数,value是该质因数的指数:

map<int, int> getYue(int a){
	map<int, int> res;
	for(int i = 2; i <= a / i; i++){
		if(a % i == 0){
			int cnt = 0;
			while(a % i == 0){
				cnt++;
				a /= i;
			}
			res[i] = cnt;
		}
	}
	if(a != 1){
		res[a] = 1;
	}
	return res;
}

最大公约数

int gcd(int a, int b)
{
    if (a % b==0) return b;
    else return gcd(b, a % b);
}

埃氏筛素数

[1, n]有多少个素数
#include 
using namespace std;
bool isPrime[1000010];
int n;
int ans;
int main()
{
    cin >> n;
    for(int i = 2; i <= n; i++)
    {
        isPrime[i] = true;
    }
    for(int i = 2; i <= n; i++)
    {
        if(isPrime[i])
        {
            ans++;
            for(int j = 2; j * i <= n; j++)
            {
                isPrime[j * i] = false;
            }
        }
    }
    cout << ans << endl;
}

线性筛素数

线性筛的步骤描述如下图所示
算法比赛备赛笔记_第2张图片

欧拉函数

互质的定义:公约数只有1的两个整数,叫做互质整数。

phi(n)的含义是{1, 2, 3 …, n-1, n}中与n的互质的数的个数,它的计算公式为:

phi(n) = n * (1 - 1/p1) * (1 - 1/p2) * (1 - 1/p3) * (1 - 1/p4) * …* (1 - 1/pn);
其中p1,p2,p3…pn是n的质因子

计算一个数的欧拉函数的代码如下:

int getPhi(int x){
    // 获取质因数,p是质因数列表
    vector<int> p;
    int tmp = x;
    for(int i = 2; i <= tmp / i; i++){
        if(tmp % i == 0){
            p.push_back(i);
            while(tmp % i == 0){
                tmp /= i;
            }
        }
    }
    if(tmp != 1){
        p.push_back(tmp);
    }
    
    // 根据质因数列表,计算欧拉函数。
    // 当计算n * (1 - 1 / pi)时,要写成n - n / pi的形式
    int res = x;
    for(auto i : p){
        res = res - res / i;
    }
    return res;
}

2. 并查集

int p[N];
for (int i = 1; i <= n; i ++ ) p[i] = i;
int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

3. 快速幂

返回 a ^ b mod p
ll qmi(ll a, ll b, ll p)
{
	ll ans = 1;
	while(b > 0)
	{
	    if(b & 1 == 1)
	    {
	        ans = ans * a % p;
	    }
	    a = a * a % p;
	    b = b >> 1;
	}
	return ans;
}

4. 组合数

从y个元素里面取x个元素有C[y][x]种取法。
如果y = x或x = 0,那么C[y][x] = 1;如果x > y,那么C[y][x] = 0
它可以被划分为两种情况,取第y个元素C[y - 1][x - 1]和不取第y个元素C[y - 1][x]。
所以C[y][x] = C[y - 1][x - 1] + C[y - 1][x]。

int c[N][N];
void init()
{
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j <= i; j ++ )
            if (!j) c[i][j] = 1;
            else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}

5. 线段树(单点修改)

题目链接
算法比赛备赛笔记_第3张图片

#include 
using namespace std;
const int M = 200010; // 序列的长度
struct Node{
	int l, r;
	int v;
}bt[M * 4]; // 开四倍的线段树数组
int m, p;
void build(int u, int l, int r){
	bt[u].l = l;
	bt[u].r = r;
	int mid = (l + r) / 2; // 最好把mid写出来,容易出错
	if(l < r){
		build(u * 2, l, mid);
		build(u * 2 + 1, mid + 1, r);
	}
}
// 根据子节点的状态计算u节点
void pushUp(int u){
	bt[u].v = max(bt[u * 2].v, bt[u * 2 + 1].v);
}
// 把序列中第x个数修改为v,是从树根往下递归,回溯时pushUp
void modify(int u, int x, int v){

	if(bt[u].l == bt[u].r && bt[u].l == x){
		bt[u].v = v;
		return ;
	}
	int mid = (bt[u].l + bt[u].r) / 2;
	if(x <= mid){
		modify(u * 2, x, v);
	}
	else {
		modify(u * 2 + 1, x, v);
	}
	pushUp(u);
}
// 查询[l, r]区间的最大值
int query(int u, int l, int r){
	
	// 查询区间包含了整棵树
	if(bt[u].l >= l && bt[u].r <= r){
		return bt[u].v;
	}
	
	// 如果与查询区间左子树有交集,则递归左子树,与右子树有交集则递归右子树
	// 不存在查询区间与整棵树无交集的情况
	int mid = (bt[u].l + bt[u].r) / 2;
	int res = 0;
	if(l <= mid){
		res = query(u * 2, l, r);
	}
	if(r > mid){
		res = max(res, query(u * 2 + 1, l, r));
	}
	return res;
}
int main(){
	build(1, 1, M);
	cin >> m >> p;	
	int len = 0, a = 0; // len是序列的长度
	while(m--){
		string s;
		int x;
		cin >> s >> x;
		if(s == "A"){
			int v = ((long long)x + (long long) a) % p;  // x + a可能爆int
			modify(1, ++len, v);
		}
		else{
			a = query(1, len - x + 1, len);
			cout << a << endl;
		}
	}
}

5.5 线段树(区间修改、懒标记)

题目链接
算法比赛备赛笔记_第4张图片

#include 
using namespace std;
// add表示当前节点包含区间内所有元素都要增加的值,也就是懒标记
// 该节点的值是计算过懒标记后的值,在本题中的意思就是,该节点的sum值,已经加上了该节点的add值
struct Node{
	int l, r;
	long long sum, add;
}bt[400010];
int n, m;
int initValue[100010];

void pushUp(int u){
	bt[u].sum = bt[u * 2].sum + bt[u * 2 + 1].sum;
}
// pushDonw操作把当前节点的add传递给子节点
void pushDown(int u){
	// 把当前节点懒标记add清空,节点的值sum不变
	int add = bt[u].add;
	bt[u].add = 0;
	
	// 子节点加上懒标记add,并更新sum值
	Node* ln = bt + u * 2;
	Node* rn = bt + u * 2 + 1;
	ln->add += add;
	rn->add += add;
	ln->sum += add * (ln->r - ln->l + 1);
	rn->sum += add * (rn->r - rn->l + 1);
}
long long query(int u, int l, int r){
	// 如果查询区间包含节点区间,直接返回
	if(bt[u].r <= r && bt[u].l >= l){
		return bt[u].sum;
	}
	
	// 否则,先执行pushDown操作,然后根据情况递归查询左区间和右区间
	pushDown(u);
	int mid = (bt[u].l + bt[u].r) / 2;
	long long res = 0;
	if(mid >= l){
		res += query(u * 2, l, r);
	}
	if(mid < r) {
		res += query(u * 2 + 1, l, r);
	}
	return res;
}

void modify(int u, int l, int r, int add){
	// 如果修改区间包含节点区间,直接修改add和sum
	if(bt[u].l >= l && bt[u].r <= r){
		bt[u].add += add;
		bt[u].sum += add * (bt[u].r - bt[u].l + 1);
		return ;
	}
	
	// 否则,先执行pushDown操作,然后根据情况递归修改左区间和右区间,递归完要pushUp
	pushDown(u);
	int mid = (bt[u].r + bt[u].l) / 2;
	if(mid >= l){
		modify(u * 2, l, r, add);
	}
	if(mid < r){
		modify(u * 2 + 1, l, r, add);
	}
	pushUp(u);
}

void buildTree(int u, int l, int r){
	bt[u].l = l, bt[u].r = r;
	int mid = (l + r) / 2;
	if(l == r){
		// 赋初始值
		bt[u].sum = initValue[l];
	}
	else{
		buildTree(u * 2, l, mid);
		buildTree(u * 2 + 1, mid + 1, r);
		// 因为在build的过程中赋了初始值,所以每次递归后都要pushUp
		pushUp(u);
	}
}

int main(){
	
	cin >> n >> m;

	for(int i = 1; i <= n; i++){
		cin >> initValue[i];
	}
	buildTree(1, 1, n);
	while(m--){
		string s;
		cin >> s;
		if(s == "C"){
			int l, r, d;
			cin >> l >> r >> d;
			modify(1, l, r, d);
		}
		else if(s == "Q"){
			int l, r;
			cin >> l >> r;
			cout << query(1, l, r) << endl;
		}
	}
}

6. 弗洛伊德最短路

void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

7. 朴素dijkstra

朴素版,复杂度O(n ^ 2)

void dijkstra()
{
	d[1] = 0;
	for(int i = 0; i < n; i++)
	{
	    int m = INF;
	    int x = 0;
	    for(int j = 1; j <= n; j++)
	    {
	        if(!flag[j] && d[j] < m)
	        {
	            m = d[j];
	            x = j;
	        }
	    }
	    flag[x] = true;
	    for (int j = 1; j <= n; j++ )
	    {
	        if(!flag[j])
	        {
	            d[j] = min(d[j], d[x] + g[x][j]);
	        }
	    }
	}
}

7.5 堆优化dijkstra

堆优化版,建立一个小根堆,每次从小根堆中取出的就是距离最短的点,用该点去更新其余点的距离。复杂度O(mlogn)

// acwing原题
#include 
#include 
#include 
using namespace std;
const int INF = 0x3f3f3f3f;
// 存储点的下标和距离,用来构建队列
struct ND{
	int idx;
	int dist;
};
bool operator<(ND a, ND b){
	return a.dist > b.dist;
}
// 存储点的信息和邻边
struct Node{
	int idx;
	int dist;
	bool isMin;
	map<Node*, int> nei;
}nodes[150010];
int n, m;
void add(int x, int y, int z){
	if(x == y){
		return ;
	}
	if(nodes[x].nei.count(nodes + y)){
		nodes[x].nei[nodes + y] = min(nodes[x].nei[nodes + y], z);
	}
	else {
		nodes[x].nei[nodes + y] = z;
	}
}
void dijkstra(){
	// 首先往队列中塞入第一个点,并把点的dist设置为0
	priority_queue<ND> q;
	q.push({1, 0});
	nodes[1].dist = 0;
	
	// 和朴素版不太一样,不是用for循环n次,直接用while循环
	while(!q.empty()){
		ND nd = q.top();
		q.pop();
		
		// 如果队头的点已经确定最短距离了,那么continue
		if(nodes[nd.idx].isMin){
			continue;
		}
		
		// 把点的标记设置为true,然后更新这个点的所有相邻点的距离
		nodes[nd.idx].isMin = true;
		for(pair<Node*, int> j : nodes[nd.idx].nei){
			j.first->dist = min(j.first->dist, nodes[nd.idx].dist + j.second);
			q.push({j.first->idx, j.first->dist});
		}
	}
}
int main(){
	cin >> n >> m;
	for(int i = 1; i <= n; i++){
		nodes[i].idx = i;
		nodes[i].dist = INF;
	}
	while(m--){
		int x, y, z;
		cin >> x >> y >> z;
		add(x, y, z);
	}
	dijkstra();
	if(nodes[n].dist == INF){
		cout << -1 << endl;
	}
	else {
		cout << nodes[n].dist << endl;
	}
}

8. bellman ford最短路

可以限制经过的边数,如下限制边数为k

	static Scanner sc = new Scanner(System.in);
	static int n;
	static int m, k;
	static int[] dist = new int[510];
	static final int INF = 0x3f3f3f3f;
	static int[] neDist = new int[510];
	static List<Edge> edges = new ArrayList<>();
	static void bellmanFord() {
		for(int i = 1; i <= n; i++) {
			dist[i] = INF;
		}
	    dist[1] = 0;
		for(int i = 1; i <= k; i++) {
			for(int j = 1; j <= n; j++) {
				neDist[j] = dist[j];
			}
			for(Edge edge : edges) {
				// 核心就是这一行,更新neDist使用dist+边长
				neDist[edge.y] = Math.min(neDist[edge.y], dist[edge.x] + edge.z);
			}
			for(int j = 1; j <= n; j++) {
				dist[j] = neDist[j];
			}
		}
	}

8.5 spfa最短路

spfa算法可以求带负权边的最短路,也可以判负环,

struct Node{
	int idx;
	int dist;
	int cnt; // 最短路经过的边数
	bool inQ;
	map<Node*, int> nei;
}nodes[100010];
// s代表源结点下标
bool spfa(){
	queue<Node*> q;
	q.push(nodes + s);
	nodes[s].dist = 0;
	nodes[s].inQ = true; // 注意要实时更新结点是否在队列中的状态
	while(!q.empty()){
		Node* node = q.front();
		q.pop();
		node->inQ = false;
		for(pair<Node*, int> i : node->nei){
			if(node->dist + i.second < i.first->dist){
				i.first->dist = node->dist + i.second;
				i.first->cnt++; // 每次更新最短距离,说明最短路又多经过了一条边
				if(i.first->cnt >= n){ // 最短路经过的变数大于等于n,说明有负环
					return true;
				}
				if(!i.first->inQ){
					q.push(i.first);
					i.first->inQ = true;
				}
			}
		}
	}
	return false; // 如果没负环,则执行到末尾,所有点的最短路都求出来了
}

9. prim最小生成树

初始化所有点到集合的距离为INF,重复以下操作n次:

  • 找到到集合距离最短的点,把它加入到集合里。
  • 利用该点更新所有不在集合的点到集合的距离
  • 和dijsktra相比主要是更新距离的操作不同
	static int[] dist = new int[510];
	static int prim() {
		Set<Integer> set = new HashSet<>();
		dist[1] = 0;
		int ans = 0;
		for(int i = 0; i < n; i++) {
			int minD = INF;
			int minI = 0;
			for(int j = 1; j <= n; j++) {
				if(minD > dist[j] && !set.contains(j)) {
					minI = j;
					minD = dist[j];
				}
			}
			if(minD == INF) {
				return INF;
			}
			ans += minD;
			set.add(minI);
			for(int j = 1; j <= n; j++) {
				if(!set.contains(j)) {
					// 和dijkstra主要是这一行更新距离的代码不同
					dist[j] = Math.min(dist[j], edge[minI][j]);
				}
			}
		}
		return ans;
	}

10. 二分模板

	// 从左到右,返回第一个大于等于x的数的下标
	static int lowerBound(int x, List<Integer> list) {
		if(list.get(list.size() - 1) < x) {
			return -1;
		}
		int l = 0, r = list.size() - 1;
		while(l < r) {
			int mid = (l + r) / 2;
			if(list.get(mid) >= x) { // 这里的判断条件,就是要找到的数从左到右第一个满足的条件
				r = mid;
			}
			else {
				l = mid + 1;
			}
		}
		return l;
	}
	// 从右往左,返回第一个小于等于x的数的下标
	static int findLastLower(int x, List<Integer> list) {
		if(list.get(list.size() - 1) <= x) {
			return list.size() - 1;
		}
		// 二分找到从左往右第一个大于x的数的下标
		int l = 0, r = list.size() - 1;
		while(l < r) {
			int mid = (l + r) / 2;
			if(list.get(mid) > x) {
				r = mid;
			}
			else {
				l = mid + 1;
			}
		}
		// l - 1就是从右往左第一个小于等于x的数的下标
		return l - 1;
	}

二. 常用语法

1. c++语法及STL

踩的坑

超级大坑,特别注意
for(auto iter:vec)不改变迭代对象的值,for(auto &iter:vec)可以改变迭代对象的值。

printf直接输出string类型会乱码,要用c_str()方法转换成字符数组再输出

string str = "abc";
printf("%s\n", str); // 错误的
printf("%s\n", str.c_str()); // 正确的

c++字符串的substr方法和其他语言的不太一样,第一个参数是起始下标而第二个参数是字符串长度

string a = "123456789";
cout << a.substr(3, 3) << endl;
cout << a.substr(3) << endl;
输出:
456
456789

c++不可以直接相加数字和字符串,要用to_string方法把数字转为字符串

int num = 1;
string str = to_string(num);

c++ for循环和while循环里定义的变量,不可以通过指针访问,它们会被自动清除

#include 
#include 
#include 
using namespace std;
int main(){
	vector<set<int>*> v;
	for(int i = 1; i < 5; i++){
		set<int> set1;
		set1.insert(i);
		v.push_back(&set1);
	}
	for(auto it : v){
		cout << it << endl;
	}
}

控制台输出五个相同的地址:
算法比赛备赛笔记_第5张图片
读入一整行

string str;
getline(cin,str);//读入string
 
char str2[1024];
cin.getline(str2,1024);//读入char数组

c++ stl里已定义了count,所以不要把自己的变量名定义为count

快读

	ios::sync_with_stdio(false);
	cin.tie(0);

lower_bound和upper_bound

  • lower_bound:二分查找一个有序数列,返回第一个大于等于x的数的迭代器,如果没找到,返回末尾的迭代器位置
  • upper_bound:二分查找一个有序数列,返回第一个小于等于x的数的迭代器,如果没找到,返回末尾的迭代器位置
  • 如果查找一个有序数组,则返回指针
#include 
#include 
#include 
using namespace std;
int main(){
	set<string> s;
	s.insert("12");
	s.insert("99");
	auto it = lower_bound(s.begin(), s.end(), "50");
	string arr[3] = {"123", "456", "789"};
	auto it1 = lower_bound(arr, arr + 3, "3");
	cout << *it << endl;
	cout << *it1 << endl;
}

priority_queue

更详细的优先队列用法

#include 
#include 
#include 
#include 
using namespace std;
struct Student
{
	int age;
};
//优先队列比较使用的运算符是 <
bool operator<(const Student s1, const Student s2)
{
	return s1.age > s2.age;
	//写大于号时,是小根堆
}
int main()
{
	//基本数据类型
	priority_queue<int> big;	//大根堆
	priority_queue<int, vector<int>, greater<int>> little; //小根堆

	//自定义数据类型
	Student s1 = {1}, s2 = {2}, s3 = {-1};
	priority_queue<Student> s;
	s.push(s1);
	s.push(s2);
	s.push(s3);
	while(!s.empty())
	{
		cout << s.top().age << endl;
		s.pop();
	}
}

queue

push() 在队尾插入一个元素
pop() 删除队列第一个元素
size() 返回队列中元素个数
empty() 如果队列空则返回true
front() 返回队列中的第一个元素
back() 返回队列中最后一个元素

set

set是有序的,默认会把元素按升序排列。
set尽量不要存结构体,因为结构体没有<比较符,而set需要用到<比较符。可以存结构体的指针。


clear();            // 清除所有元素
count();            // 返回某个值元素的个数
empty();            // 如果集合为空,返回true
insert()–在集合中插入元素
erase()–删除集合中的元素,参数为值val或者迭代器
size()–集合中元素的数目

find()–返回一个指向被查找到元素的迭代器

begin();            // 返回指向第一个元素的迭代器
end();              // 返回指向迭代器的最末尾处(即最后一个元素的下一个位置)
for(set<int>::iterator it=numSet.begin(); it!=numSet.end(); it++)
{
	cout<<*it<<<<endl;	遍历set集合
}
 
lower_bound()–返回指向大于(或等于)某值的第一个元素的迭代器
upper_bound()–返回大于某个值元素的迭代器

stack

empty() 堆栈为空则返回真
pop() 移除栈顶元素
push() 在栈顶增加元素
size() 返回栈中元素数目
top() 返回栈顶元素

vector

vector<int> a;
sort(a.begin() + 3, a.end()); 向量排序
(3)a.back(); //返回a的最后一个元素4)a.front(); //返回a的第一个元素6)a.clear(); //清空a中的元素8)a.pop_back(); //删除a向量的最后一个元素9)a.erase(a.begin()+1,a.begin()+3); //删除a中下标为1,2的元素10)a.push_back(5); //在a的最后一个向量后插入一个元素,其值为511)a.insert(a.begin()+1,5); //把5插入到下标为1的位置,如a为1,2,3,4,插入元素后为1,5,2,3,412)a.insert(a.begin()+1,3,5); //在下标为1的位置插入3个数,其值都为513)a.insert(a.begin()+1,b+3,b+6); //b为数组,在a下标为1的位置,插入b下标3,4,5的元素,如b为1,2,3,4,5,9,8,插入元素后为1,4,5,9,2,3,4,5,9,814)a.size(); //返回a中元素的个数;数据类型为unsigned int20)a==b; //b为向量,向量的比较操作还有!=,>=,<=,>,<

map

当对map对象传入一个不存在的key值时,会返回默认零值。

map可以用类似于数组下标的形式直接访问值和设置值。

同样的,map默认会按照key值的升序进行排序,所以不要设置key为结构体,可以为结构体指针。

map和set的方法类似,其中erase()方法,均可传入迭代器或key值。

map的使用和遍历方法如下:

map<int, int> m;
m[1] = 10;
m[3] = 200;
m[2] = 30;
m.erase(1);
for(auto v : m){
	cout << v.first << " " << v.second << endl;
}
cout << m.size() << endl;
cout << m.count(1) << endl;
cout << m[4] << endl;

运算符重载

Bian是一个自定义的结构体类型
bool operator==(const Bian bs1, const Bian bs2)
{
	if(bs1.x == bs2.x && bs1.y == bs2.y)
	{
		return true;
	}
	return false;
}
bool operator<(const Bian bs1, const Bian bs2)
{
	if(bs1.x == bs2.x && bs1.y == bs2.y)
	{
		return false;
	}
	return true;
}
bool operator>(const Bian bs1, const Bian bs2)
{
	if(!(bs1 < bs2) && !(bs1 == bs2))
	{
		return false;
	}
	return true;
}

2. java语法

排序,数组与list转换

java排序有个大坑,排序函数返回值有-1,0,1三种情况,而且三种情况不能有交集,也就是说一定要满足可逆比较,假如比较o1和o2返回的是1那么反过来比较的时候返回的一定得是-1,假如返回的是0那么反过来比较时返回的一定也得是0。

下面这种写法是ok的

@Override
public int compare(Edge o1, Edge o2) {
	if(o1.w > o2.w) {
		return 1;
	}
	else if(o1.w == o2.w) {
		return 0;
	}
	else {
		return -1;
	}
}

但下面这种写法是不行的,数据达到几十万的时候,会抛错Comparison method violates its general contract

@Override
public int compare(Edge o1, Edge o2) {
	if(o1.w > o2.w) {
		return 1;
	}
	return -1;
}

比较两个Integer相等不能使用 == ,必须使用equals方法

最好的方法,是通过Integer.intValue()方法获取int值,然后再比较大小

package demo01;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;

// 升序
class AscCmp implements Comparator<Integer>{
	@Override
	public int compare(Integer o1, Integer o2) {
		if(o1.intValue() > o2.intValue()) {
			return 1;
		}
		else if(o1.intValue() == o2.intValue()){
			return 0;
		}
		else{
			return -1;
		}
	}
}

// 降序
class DescCmp implements Comparator<Integer>{
	@Override
	public int compare(Integer o1, Integer o2) {
		if(o1.intValue() > o2.intValue()) {
			return -1;
		}
		else if(o1.intValue() == o2.intValue()){
			return 0;
		}
		else{
			return 1;
		}
	}
}
public class Main{
	public static void main(String[] args) {
		
		Integer[] arr = {10000000, 2, 3, 4, 5}; // 排序数组只能用Integer
		List<Integer> list = new ArrayList<>();
		
		for(int i : arr) { // list转数组和数组转list最好手动遍历转换
			list.add(i);
		}
		
		Arrays.sort(arr, 0, 5, new AscCmp()); //数组排序比较灵活,能够指定起始下标
		Collections.sort(list, new DescCmp());
		
		for(int i : arr) {
			System.out.println(i);
		}
		for(int i : list) {
			System.out.println(i);
		}
	}
}

字符,字符串

注意字符串比大小,一定要用equals方法!!!!!

字符可以直接当作整数,整数转为字符需要强转

字符串可以转换为字符数组,字符数组可以转换为字符串

java类型的转换,往往可以通过类的valueOf方法实现。

int a = 1;
char c = (char)a;
int b = c + 1;

String s = "123456789";
char[] arr = s.toCharArray();
String s1 = String.valueOf(arr);
String s2 = String.valueOf(arr[1]);
String s3 = new String(arr);

字符串转数字:

String s1 = "123";
String s2 = "1.14";
int a = Integer.valueOf(s1);
double b = Double.valueOf(s2);
System.out.println(a + b);

字符串方法:


  public int length()
  //字符串长度
  
  public char charAt(int index)
  //返回某下标对应的字符
  
  public int compareTo(String anotherString) 
  // 按字典序比较大小,大于返回正数,小于返回复数,等于返回0
  
  public boolean equals(Object anObject)
  //在两个字符串相等的时候返回true,否则返回false.
  
  

  public String substring(int beginIndex[, int endIndex]) 
  //返回从beginIndex位置起至endIndex-1结束的字串,如不指定endIndex则一直到末尾.
  
  public boolean startsWith(String prefix) 
  //如果以指定前缀开头,返回true,否则返回false.
  
  
  public boolean endsWith(String suffix) 
   //如果以指定后缀结尾,返回true,否则返回false.
  
  public int indexOf(int ch[, int fromIndex])
   //从fromIndex开始往后搜索,字符ch第一次出现的位置.
   
  public int indexOf(string s[, int fromIndex])
   //从fromIndex开始往后搜索,字符串s第一次出现的位置.

  public String replace(char oldChar,char newChar) 
  //该方法用字符newChar替换当前字符串中所有的字符oldChar,并返回一个新的字符串.
  
  public String replaceFirst(String regex, String replacement)
  // 把等于regex的第一个子串替换为replacement
  
  public String replaceAll(String regex, String replacement)
   // 把等于regex的所有字串替换为replacement

正则表达式

^ 匹配开头,$ 匹配末尾。

java里字符串的matches方法可以判断是否匹配正则表达式,但和js有略微不同

String s = "hello";
System.out.println(s.matches("he"));
System.out.println(s.matches("he.*"));
输出是:
false
true
let s = 'hello'
console.log(/he/.test(s))
console.log(/he.*/.test(s))
输出是:
true
true

你可能感兴趣的:(算法,笔记)