图论:
要素:
顶点(Vertex) 边(Edge) 边权(Weight)**
度
无向图:边数
有向图:出度和入度
树为一种比较特殊的图
四个基本容器
基本的容器有四个,分别对应四个接口:
List:按照插入的顺序保存元素,可以有重复的元素。
Set:不可以有重复的元素,可以理解为哈希表。
Queue:按照队列规则先进先出。
Map:将两个元素相关联,可以用键来查找值。
其中List、Set、Queue这三个接口继承了Collection接口。
Map则是自成一家,没有继承别人。
各接口的常用实现类
List:ArrayList、LinkedList
Set:HashSet、TreeSet、LinkedHashSet
Map:HashMap、TreeMap、LinkedHashMap
Queue:PriorityQueue、LinkedList
ArrayList和LinkedList这两个是实现类,实现了List接口,这两个类可能会增加一些接口没有的新方法(刷题时一般用不到)。
同理、Set、Queue、Map的实现类也一样。
1,类的定义方式:
如果需要用到一个List容器可以像下面这样子定义
ArrayList<Integer> list = new ArrayList<>();
LinkedList<Integer> list = new LinkedList<>();
List<Integer> list = new ArrayList<>();//接口实现的对象方法少一点
ArrayList<Integer> list = new ArrayList<>()//类实现的对象多一点
Set和Map和Queue的定义也和list一样。
List容器
ArrayList:底层使用数组实现的,查找快,插入数据和删除数据慢(常用)
LinkedList:底层使用链表实现的,查找慢,插入数据和删除数据快(常用)
1,创建一个List容器
List<Integer> list = new ArrayList<>();
ArrayList<Character> hui=new ArrayList<>();
ArrayList<String> ans=new ArrayList<>();
ans.add("qiao ben zhong.\n");//可以实现加入回车
ArrayList[] arrayList = new ArrayList[n];
//每一个一维数组都要实例化
for(int i=0;i<n;i++)
{
a[i]=new ArrayList<>();
}
for (int j = 0; j < arrayList[i].size(); j++)
List(可用arraylist也可以用linkedlist)常用方法有
arrayList[i].get(j)//二维数组
list.add(int element)//添加元素(常用)
list.get(int index)//根据索引查找元素
list.size()//反回元素的数量(常用)
list.isEmpty()//判断是否为空(常用)
list.remove()//根据下标删除元素
list.clear();//清空元素
2,模拟栈:
LinkedList<Integer> a=new LinkedList<>();
a.add(12);a.add(13);a.add(133);a.add(21);
int z=a.get(a.size()-1);//返回栈顶元素
a.remove(a.size()-1);//清除栈顶元素
System.out.println(a);
2,模拟队列:
LinkedList<Integer> b=new LinkedList<>();//队列
b.add(1);b.add(3);b.add(12);
int x=b.poll();//返回队首元素,删除队首元素
int y=b.peek();//返回队首元素
b.removeFirst();//直接删除队首元素
System.out.println(b);
Stack容器
//创建堆栈对象
Stack stack=new Stack();
System.out.println("aaaa,bbbb,cccc三个元素入栈");
//向栈中压入字符串aaaa
stack.push("aaaa");
//显示栈中的所有元素
printStack(stack);
stack.push("bbbb");
printStack(stack);
stack.push("cccc");
printStack(stack);
String s=new String("aaaa");
System.out.println("元素aaaa在堆栈的位置"+stack.search(s));
s=new String("bbbb");
System.out.println("元素bbbb在堆栈的位置"+stack.search(s));
System.out.println("aaaa,bbbb,cccc三个元素出栈");
System.out.println("元素"+stack.pop()+"出栈");
printStack(stack);
System.out.println("元素"+stack.pop()+"出栈");
printStack(stack);
System.out.println("元素"+stack.pop()+"出栈");
printStack(stack);
}
Set容器(可去重)
Set接口实现类有
HashSet:使用散列函数查找快速(也就是常说的哈希查找)(常用)
TreeSet:排序默认从小到大(去重并排序)
LinkedHashSet:使用链表维护元素的插入顺序
Set set = new HashSet<>();
可以直接取数
(缺点:取数困难,一般用新容器通过addAll()再存一遍,后取数,不知道当时怎么想的,也不知道看了哪里的资料)
Set常用方法有
set.add()//添加元素
set.remove(Object o)//删除一个对象的引用
set.clear()//清空容器
set.isEmpty()//判断容器是否为空
set.contains(Object o)//判断容器是否包含o(常用)
set.size()//返回容器中元素的数目
Map容器
Map接口实现类有
HashMap:用于快速访问,常用与哈希有关的(常用)
TreeMap:键处于排序状态
LinkedHashMap:保持元素插入的顺序
Map<String,Integer> map = new HashMap<>();
Map常用方法有
map.putIfAbsent()//添加键值对,可以去重value
map.put()//添加键值对,直接覆盖
1.使用put方法添加键值对,如果map集合中没有该key对应的值,则直接添加,并返回null,如果已经存在对应的值,则会覆盖旧值,value为新的值。
2.使用putIfAbsent方法添加键值对,如果map集合中没有该key对应的值,则直接添加,并返回null,如果已经存在对应的值,则依旧为原来的值。
map.get()//取数据
int k=entry.getKey();
int v=entry.getValue();
map.remove(Object key)//删除键值对
map.remove(Object key,Object value)//删除键值对
map.size()//返回元素的数量
map.clear()//清空容器
map.isEmpty()//判断容器是否为空
map.containsKey()//判断容器是否包含某键
map.containsValue()//判断容器是否包含某值
map.put(1,11);
map.getOrDefault(key,val2);如果key无初始值,返回val2,有就直接返回初始值
```java
遍历Map:
两种方法:
1,使用 map.keySet()
Map<Integer,Integer> mp=new HashMap<>();
mp.put(0,mp.getOrDefault(0, 0)+1);
mp.put(3, 122);
mp.put(5, mp.getOrDefault(5, 12));
for(Integer it:mp.keySet())
System.out.println(mp.get(it));
2,使用Map.Entry<> entry:变量名.entrySet()
static Map<Integer,Integer> m=new HashMap<>();
for(Map.Entry<Integer, Integer> entry:m.entrySet())
{
int k=entry.getKey();
int v=entry.getValue();
while(v-->0)
{
sb.append("*"+k);
}
}
修改value:
不同于c++。java通过覆盖换值。
使用m.put();
mc.getOrDefault(i, 0) 获取i的初始值,无就返回0.
自加操作;
mc.put(i, mc.getOrDefault(i, 0)+1);
for(int i=0;i<nums.length;i++)
{
if(m.get(nums[i])==null) m.put(nums[i], 0);
cnt=(cnt+m.get(nums[i]))%maxx;
// int value=m.get(nums[i])+1;
m.put(nums[i], m.get(nums[i])+1);
}
Queue容器
PriorityQueue:优先队列(常用)
LinkedList:普通队列(常用)
有些人可能有疑问
怎么Queue还和LinkedList联系起来了,其实Queue的添加队尾和删除队头不就是相等于插入链表的尾结点,和删除链表的头结点吗,所以说不需要在定义一个Queue的实现类了。
Queue常用方法有
offer()添加元素(常用)
peek()返回第一个元素(常用)
poll()返回第一个元素并删除(常用)
add()添加元素
element()返回第一个元素
remove()返回第一个元素并删除
put()添加元素
take()返回第一个元素并删除
使用迭代器:
方法:
it.hasnext()是否有下一个结点(
temp.contains(obj)某temp容器是否包含obj
Iterator it=list.iterator();
while(it.hasNext())
{
Object obj=it.next();//会自动迭代到下一个结点)
if(!temp.contains(obj)) temp.add(obj);
}
容器的遍历
static Map<Integer,Integer> m=new HashMap<>();
for(Map.Entry<Integer, Integer> entry:m.entrySet())
{
int k=entry.getKey();
int v=entry.getValue();
while(v-->0)
{
sb.append("*"+k);
}
}
容器的排序:
return -1对应着序列:(a-b (a<b)为升序 b-a(a>b)为降序)
Collections.sort(l,(a,b)->a-b);//升序
Collections.sort(l,(a,b)->b-a);//降序
PriorityQueue heap=new PriorityQueue<>((a,b)->a.weight-b.weight);
Collections.sort(mm,(a,b)->(b.height-a.height)<=0?-1:1); height为double 会容易超时
一般:
Collections.sort(mm,(a,b)->a-b);升序
这个不会超时
Collections.sort(mm, new Comparator<monbing>() {
@Override
public int compare(monbing o1, monbing o2) {
//为降序
if(o1.height > o2.height) return -1;
else if(o1.height == o2.height) return 0;
return 1;
}
});
Collections.sort(ban,new Comparator<Ban>() {
public int compare(Ban a,Ban b)
{
if(a.c==b.c)
{
return a.k<b.k?-1:1;
}
return a.c<b.c?-1:1;
}
});
复制容器:
方法1:
这个方法有缺点,当原容器变化时,c也会变化
LinkedList<Integer> a=new LinkedList<>();
LinkedList< Integer> c=a;
方法2:
最常用的,不会改变容器的方法
LinkedList< Integer> c=new LinkedList<>();
c.addAll(a);
System.out.println(c);
容器例题:
力扣5723:
import java.math.*;
class Solution {
public int[] findingUsersActiveMinutes(int[][] logs, int k) {
Map<Integer ,Set<Integer>> m=new HashMap<>();
for(int i=0;i<logs.length;i++)
{
int id=logs[i][0];int t=logs[i][1];
if(!m.containsKey(id)){
Set<Integer> s=new HashSet<>();
s.add(t);
m.put(id,s);
}
else
m.get(id).add(t);
}
int ans[]=new int[k];
for(Map.Entry<Integer,Set<Integer>> en:m.entrySet())
{
ans[en.getValue().size()-1]++;
}
return ans;
}
}
优先队列:实际上是一个堆,默认为小根堆,就是数据小的优先级高,所以无论按什么顺序add一堆树,在队列里都是先出最小的元素
PriorityQueue heap=new PriorityQueue<>((a,b)->a.weight-b.weight);
邻接矩阵:存所有点两两之间的关系
邻接表:存所有点的邻居
伪代码实现:
图的建立
1,邻接矩阵:
空间复杂度大,浪费空间,不容易遍历
int map[][]=new int[n][n];
2,邻接链表:
容易遍历,空间节约
无权值代码实现
ArrayList<Integer> a[]=new ArrayList[7];
for(int i=0;i<7;i++)
{
a[i]=new ArrayList<>();
}
a[1].add(2);a[1].add(3);
a[2].add(5);
for(int i=0;i<a.length;i++)
{
for(int j=0;j<a[i].size();j++)
{
System.out.println(i+" "+a[i].get(j));
}
}
####带权值代码实现
3,前向星
前向星是一种特殊的边集数组,我们把边集数组中的每一条边按照起点从小到大排序,如果起点相同就按照终点从小到大排序,
并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,那么前向星就构造好了.
用len[i]来记录所有以i为起点的边在数组中的存储长度.
用head[i]记录以i为边集在数组中的第一个存储位置.
那么对于下图:
我们输入边的顺序为:
1 2
2 3
3 4
1 3
4 1
1 5
4 5
那么排完序后就得到:
编号: 1 2 3 4 5 6 7
起点u: 1 1 1 2 3 4 4
终点v: 2 3 5 3 4 1 5
得到:
head[1] = 1 len[1] = 3
head[2] = 4 len[2] = 1
head[3] = 5 len[3] = 1
head[4] = 6 len[4] = 2
需要将起点从大到小进行排序。
4,链式前向星(常用)
在前向星的基础上,连接通道就不用排序,就成了链式前向星。
用链式前向星,就可以避免了排序.
实现:
给每一条边取号
e[i]第i边的终点
ne[i] 为上一条出边(值为边号开始都是,head的初始值,0或-1)
w[i] 第i边的权值
h[i] 结点为i的最后一条边
const int N=1e5+10,INF=0x3f3f3f3f;
int h[N],e[N],ne[N],idx,w[N];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
for(int i=h[t];i!=-1;i=ne[i])
{
}
cin>>n>>m;
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
java+结构体:
head[i] 存i结点最后一条出边
e[i]存第i条边
e[i].next为上一条出边(值为边号开始都是,head的初始值,0或-1)
从head[i]dao->e[i].next可以遍历一遍i结点的所有边。
#include<bits/stdc++.h>
const int maxx=100010;
using namespace std;
struct node
{
int to;//表示第i条边的终点
int next;//表示与第i条边同起点的下一条边的存储位置
int w;//权值
} edge[maxx];
int head[maxx];
int cnt;
void add(int u,int v,int w)
{
edge[cnt].w=w;
edge[cnt].to=v;//表示第i条边的终点
edge[cnt].next=head[u];//head[i]表示以i为起点的最后一条边的储存位置
head[u]=cnt++;
}
int main()
{
memset(head,0,sizeof(head));
cnt=1;
int n;
scanf("%d",&n);
int a,b,c;
while(n--)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
int start;
scanf("%d",&start);
for(int i=head[start]; i!=0; i=edge[i].next)
printf("%d->%d %d\n",start,edge[i].to,edge[i].w);
return 0;
}
/*
6
1 2 3
2 3 5
3 4 6
1 3 8
4 1 9
1 5 6
*/
拓扑排序
用法:
拓扑排序是一个有向无环图(DAG)(有无权值都行)的所有结点的线性序列
是一个遍历图的很好序列。
可以在图上进行遍历,因为拓扑排序具有无后效性(前面的数据不会被后面的影响),dp也具有无后效性,所以可以轻松结合使用
条件
题目为有向无环图时就要想到用拓扑排序。
该序列满足两个条件:
1,每个顶点出现且只出现一次
2,在序列中,起点在终点前面。(可以不相邻)
注意:有向无环图才有拓扑排序。
如果没有字典序要求,一个有向无环图的拓扑排序可能会多种
实现操作:
1,先统计所有节点的入度(相邻结点到自身结点,则自身结点入度+1)
入度为0的节点就可以分离出来,再把这个节点指向的节点入度-1
2,重复操作,直到全部分离,就可以得到拓扑排序
模板:
1,创建邻接表
2,用优先队列存放入度为0的点,会弹出最小值的点
3,Ans存储 队列poll出的结点
import java.util.LinkedList;
//2021年3月21日下午4:26:25
//writer:apple
import java.util.Scanner;
import java.util.PriorityQueue;
public class tuopusort {
static int n;
static int m;
static int indu[];
static LinkedList<Integer> a[];//创建邻接表
static PriorityQueue<Integer> ans=new PriorityQueue();//存放入度为0的结点
static LinkedList<Integer> Ans=new LinkedList<>();
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner s=new Scanner(System.in);
n=s.nextInt();m=s.nextInt();
a=new LinkedList[n+1];
indu=new int[n+1];
for(int i=0;i<=n;i++)
{
a[i]=new LinkedList();
}
for(int i=0;i<m;i++)
{
int b=s.nextInt();
int c=s.nextInt();
a[b].add(c);
indu[c]++;
}
for(int i=1;i<=n;i++)
{
if(indu[i]==0)
{
ans.add(i);
}
}
while(!ans.isEmpty())
{
int x=ans.poll();
Ans.add(x);//最后的答案
//遍历结点的邻接点
for(int i=0;i<a[x].size();i++)
{
int y=a[x].get(i);
indu[y]--;
if(indu[y]==0) ans.add(y);
}
}
for(int i:Ans)
System.out.print(i+" ");
}
}
例题:
1,P1137 旅行计划
这题比较简单,,在建立拓扑序列的同时,dp就行。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.PriorityQueue;
import java.util.Arrays;
import java.util.LinkedList;
//2021年3月22日上午12:58:41
//writer:apple
public class Main {
static int n;
static int m;
static int indu[];
static PriorityQueue<Integer> p=new PriorityQueue<>();
static LinkedList<Integer> map[];
static int dp[];
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String t[]=br.readLine().split(" ");
n=Integer.parseInt(t[0]);m=Integer.parseInt(t[1]);
map=new LinkedList[n+1];
indu=new int[n+1];
dp=new int[n+1];
Arrays.fill(dp, 1);
for(int i=0;i<=n;i++) map[i]=new LinkedList<>();
for(int i=0;i<m;i++)
{
String tt[]=br.readLine().split(" ");
int a=Integer.parseInt(tt[0]);int b=Integer.parseInt(tt[1]);
map[a].add(b);
indu[b]++;
}
for(int i=1;i<=n;i++)
{
if(indu[i]==0) p.offer(i);
}
while(!p.isEmpty())
{
int x=p.poll();
for(int i=0;i<map[x].size();i++)
{
int y=map[x].get(i);
indu[y]--;
if(indu[y]==0) p.offer(y);
dp[y]=Math.max(dp[y],dp[x]+1);
}
}
for(int i=1;i<=n;i++)
{
System.out.println(dp[i]);
}
}
}
2,P1807 最长路
用map存储点a,点b,和权值
package tu;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.PriorityQueue;
//2021年3月22日上午12:16:21
//writer:apple
public class tuopucccc {
static int n;
static int m;
static int indu[];
static PriorityQueue<Integer> p=new PriorityQueue<>();
static boolean vis[];
static LinkedList<Integer> ans=new LinkedList<>();//存放拓扑序列
static int dp[];//dp[i]为1到i的最长路
static class pair{
int x;
int weight;
public pair(int xx,int w)
{
x=xx;weight=w;
}
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
Map<Integer,LinkedList<pair>> map=new HashMap<>();
String t[]=br.readLine().split(" ");
n=Integer.parseInt(t[0]);m=Integer.parseInt(t[1]);
indu=new int[n+1];
vis=new boolean[n+1];
dp=new int[n+1];
for(int i=1;i<=n;i++)
{
map.putIfAbsent(i, new LinkedList<>());
}
for(int i=0;i<m;i++)
{
String temp[]=br.readLine().split(" ");
int a=Integer.parseInt(temp[0]);int b=Integer.parseInt(temp[1]);int len=Integer.parseInt(temp[2]);
indu[b]++;
map.putIfAbsent(a,new LinkedList<>());
map.get(a).add(new pair(b,len));
}
for(int i=1;i<=n;i++)
{
if(indu[i]==0) p.add(i);
}
dp[n]=-1;
vis[1]=true;
while(!p.isEmpty()) {
int x=p.poll();
for(int i=0;i<map.get(x).size();i++)
{
int y=map.get(x).get(i).x;
indu[y]--;
if(indu[y]==0) p.offer(y);
if(vis[x])
{
dp[y]=Math.max(dp[y],dp[x]+map.get(x).get(i).weight);
vis[y]=true;
}
}
}
System.out.println(dp[n]);
}
}
找环(保存路径):
bool dfs(int u){
if(vis[u]){
if(u == idx) return true;
return false;
}
vis[u] = 1;
for(int i = 0; i < g[u].size(); i++){
int v = g[u][i];
if( dfs(v) ){
ans.push_back(v);
return true;
}
}
return false;
}