并查集
- 有若干個(gè)樣本a仍劈、b厕倍、c、d…類型假設(shè)是V贩疙。
- 在并查集中一開始認(rèn)為每個(gè)樣本都在單獨(dú)的集合里讹弯。
- 用戶可以在任何時(shí)候調(diào)用如下兩個(gè)方法:
1.boolean isSameSet(V x, V y) : 查詢樣本x和樣本y是否屬于一個(gè)集合。
2.void union(V x, V y) : 把x和y各自所在集合的所有樣本合并成一個(gè)屋群。 - isSameSet和union方法的代價(jià)越低越好闸婴。(時(shí)間復(fù)雜度O(1))。
并查集特征
1)每個(gè)節(jié)點(diǎn)都有一條往上指的指針芍躏。
2)節(jié)點(diǎn)a往上找到的頭節(jié)點(diǎn)邪乍,叫做a所在集合的代表節(jié)點(diǎn)。
3)查詢x和y是否屬于同一個(gè)集合对竣,就是看看找到的代表節(jié)點(diǎn)是不是一個(gè)庇楞。
4)把x和y各自所在集合的所有點(diǎn)合并成一個(gè)集合,只需要小集合的代表點(diǎn)掛在大集合的代表點(diǎn)的下方即可否纬。
并查集的優(yōu)化
1)節(jié)點(diǎn)往上找代表點(diǎn)的過程吕晌,把沿途的鏈變成扁平的。
2)小集合掛在大集合的下面临燃。
3)如果方法調(diào)用很頻繁睛驳,那么單次調(diào)用的代價(jià)為O(1),兩個(gè)方法都如此膜廊。
/**
* 并查集
* 提供兩個(gè)方法:
* 1乏沸、查詢兩個(gè)元素是否在同一個(gè)集合中 isSameSet
* 2、把兩個(gè)元素所在的集合合并成一個(gè) union
*
* 假定用戶一次直接給出所有集合
*/
public class UnionSearch<V> {
// 節(jié)點(diǎn)位置表
Map<V,Node> nodes;
// 查找當(dāng)前節(jié)點(diǎn)對應(yīng)的父節(jié)點(diǎn)表
Map<Node,Node> parents;
// 作為代表元素表 k代表元素 v 集合中有幾個(gè)元素
Map<Node,Integer> size;
public UnionSearch(List<V> list){
nodes = new HashMap<>();
parents = new HashMap<>();
size = new HashMap<>();
for(V v : list){
// 出事時(shí)每個(gè)元素單獨(dú)作為一個(gè)結(jié)合
Node node = new Node(v);
nodes.put(v,node);
parents.put(node,node);
size.put(node,1);
}
}
public boolean isSameSet(V v1,V v2){
if(!nodes.containsKey(v1)
|| !nodes.containsKey(v2)){
return false;
}
Node node1 = findFather(v1);
Node node2 = findFather(v2);
if(node1 == node2){
return true;
}
return false;
}
public void union(V v1,V v2){
if(!nodes.containsKey(v1)
|| !nodes.containsKey(v2)){
return;
}
Node node1 = findFather(v1);
Node node2 = findFather(v2);
// 兩個(gè)元素不在一個(gè)集合中才合并
if(node1 != node2){
// 看誰的元素多
Node big = size.get(node1) > size.get(node2) ? node1 : node2;
Node small = size.get(node1) <= size.get(node2) ? node1 : node2;
// 小的掛在大的上面
parents.put(small,big);
size.put(big,size.get(big) + size.get(small));
size.remove(small);
}
}
// 調(diào)用此方法是一定保證集合中有此元素
private Node findFather(V node){
// 做一個(gè)扁平化優(yōu)化,把從當(dāng)前節(jié)點(diǎn)網(wǎng)上找的節(jié)點(diǎn)都加入容器中
Stack<Node> stack = new Stack<>();
Node cur = nodes.get(node);
while (parents.get(cur) != cur){
// 路徑上經(jīng)過的節(jié)點(diǎn)加入容器
stack.push(cur);
// 只要此時(shí)節(jié)點(diǎn)不等于父節(jié)點(diǎn)爪瓜,說明不是代表節(jié)點(diǎn)就一直往上找
cur = parents.get(cur);
}
// 把所有經(jīng)過節(jié)點(diǎn)直接和代表節(jié)點(diǎn)相連蹬跃,下次查找是O(1)
while (!stack.isEmpty()){
parents.put(stack.pop(),cur);
}
//跳出來說明已經(jīng)找到了
return cur;
}
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("g");
UnionSearch<String> unionSearch = new UnionSearch<>(list);
unionSearch.union("a","b");
unionSearch.union("c","b");
System.out.println(unionSearch.isSameSet("a", "g"));
}
}
圖
1)由點(diǎn)的集合和邊的集合構(gòu)成。
2)雖然存在有向圖和無向圖的概念铆铆,但實(shí)際上都可以用有向圖來表達(dá)蝶缀。
3)邊上可能帶有權(quán)值丹喻。
圖結(jié)構(gòu)的表達(dá)
1)鄰接表法
2)鄰接矩陣法
3)除此之外還有其他眾多的方式
圖的數(shù)據(jù)結(jié)構(gòu)
- 點(diǎn)
// 點(diǎn)
public class Node {
// 結(jié)點(diǎn)值
public int value;
// 入度
public int in;
// 出度
public int out;
// 鄰居
List<Node> nexts;
// 存一份邊
List<Edge> edges;
public Node(int value){
this.value = value;
// 新結(jié)點(diǎn)入度為0
this.in = 0;
// 新結(jié)點(diǎn)出度為0
this.out = 0;
// 新結(jié)點(diǎn)鄰居為空
this.nexts = new ArrayList<>();
this.edges = new ArrayList<>();
}
}
- 邊
// 邊
public class Edge {
// 權(quán)重
public int weight;
// 起點(diǎn)邊
public Node from;
// 終點(diǎn)邊
public Node to;
public Edge(Node from,Node to,int weight){
this.from = from;
this.to = to;
this.weight = weight;
}
}
- 圖
// 圖
public class Graph {
// 點(diǎn)的集合
HashMap<Integer,Node> nodes;
// 邊的集合
HashSet<Edge> edges;
public Graph(){
this.nodes = new HashMap<>();
this.edges = new HashSet<>();
}
}
- 生成圖
public class GraphGenerator {
// matrix 所有的邊
// N*3 的矩陣
// [weight, from節(jié)點(diǎn)上面的值,to節(jié)點(diǎn)上面的值]
public static Graph graphGenerator(Integer[][] matrix){
Graph graph = new Graph();
for (int i = 0;i < matrix.length;i++){
// 每一行代表一個(gè)點(diǎn)
// 權(quán)重
int weight = matrix[i][0];
// 起始點(diǎn)
int from = matrix[i][1];
// 終點(diǎn)
int to = matrix[i][2];
// 如果沒有起始點(diǎn)就新建
if(graph.nodes.containsKey(from)){
graph.nodes.put(from,new Node(from));
}
if(graph.nodes.containsKey(to)){
graph.nodes.put(to,new Node(to));
}
// 取出點(diǎn)構(gòu)建圖
Node fromNode = graph.nodes.get(from);
Node toNode = graph.nodes.get(to);
// 構(gòu)建邊
Edge edge = new Edge(fromNode,toNode,weight);
// fromNode 幾點(diǎn)出度加一翁都,鄰居加一
fromNode.out ++;
fromNode.nexts.add(toNode);
fromNode.edges.add(edge);
// toNode入度加一
toNode.in ++;
// 圖中邊集加一
graph.edges.add(edge);
}
return graph;
}
}
寬度優(yōu)先遍歷
1碍论,利用隊(duì)列實(shí)現(xiàn)
2,從源節(jié)點(diǎn)開始依次按照寬度進(jìn)隊(duì)列荐吵,然后彈出
3骑冗,每彈出一個(gè)點(diǎn)赊瞬,把該節(jié)點(diǎn)所有沒有進(jìn)過隊(duì)列的鄰接點(diǎn)放入隊(duì)列
4先煎,直到隊(duì)列變空
- 注意要點(diǎn),不能重復(fù)遍歷巧涧,便利過的點(diǎn)應(yīng)該排除薯蝎。
// 圖的寬度有限便利
public class GraphBfs {
public static void graphBfs(Node node){
if(node == null){
return;
}
HashSet<Node> set = new HashSet<>();
Queue<Node> queue = new LinkedList<>();
queue.offer(node);
set.add(node);
while (!queue.isEmpty()){
Node cur = queue.poll();
System.out.print(cur.value + " ");
// 全部鄰居加入隊(duì)列
for (Node next : cur.nexts){
if(!set.contains(next)){
queue.offer(next);
set.add(next);
}
}
}
}
}
深度優(yōu)先遍歷
1.利用棧實(shí)現(xiàn)
2.從源節(jié)點(diǎn)開始把節(jié)點(diǎn)按照深度放入棧,然后彈出
3.每彈出一個(gè)點(diǎn)谤绳,把該節(jié)點(diǎn)下一個(gè)沒有進(jìn)過棧的鄰接點(diǎn)放入棧
4.直到棧變空
- 圖的深度優(yōu)先遍歷主要注意重復(fù)結(jié)點(diǎn)不遍歷
// 圖的寬度優(yōu)先遍歷
public class GraphDfs {
public static void graphDfs(Node node){
if(node == null){
return;
}
HashSet<Node> set = new HashSet<>();
// 利用棧
Stack<Node> stack = new Stack<>();
// 入棧就打印
stack.push(node);
System.out.print(node.value + " ");
while (!stack.isEmpty()){
Node cur = stack.pop();
for(Node item : cur.nexts){
if(!set.contains(item)){
stack.push(cur);
stack.push(item);
set.add(item);
System.out.print(item.value + " ");
break;
}
}
}
}
}
圖的拓?fù)渑判蛩惴?/h1>
1)在圖中找到所有入度為0的點(diǎn)輸出
2)把所有入度為0的點(diǎn)在圖中刪掉占锯,繼續(xù)找入度為0的點(diǎn)輸出,周而復(fù)始
3)圖的所有點(diǎn)都被刪除后缩筛,依次輸出的順序就是拓?fù)渑判?br>
要求:有向圖且其中沒有環(huán)
應(yīng)用:事件安排消略、編譯順序
// 拓?fù)渑判?public class TopologySort {
public static List<Node> topologySort(Graph graph){
if(graph == null){
return null;
}
// 入度為0的點(diǎn)進(jìn)入此隊(duì)列
Queue<Node> zeroQueue = new LinkedList<>();
// k :結(jié)點(diǎn) v :入度數(shù)
HashMap<Node,Integer> map = new HashMap<>();
List<Node> res = new ArrayList<>();
for(Node item : graph.nodes.values()){
map.put(item,item.in);
if(item.in == 0){
zeroQueue.offer(item);
}
}
while (!zeroQueue.isEmpty()){
Node cur = zeroQueue.poll();
res.add(cur);
for(Node temp : cur.nexts){
map.put(temp,map.get(temp)-1);
if(map.get(temp) == 0){
zeroQueue.offer(temp);
}
}
}
return res;
}
}
最小生成樹K算法
/**
* 最小生成樹算法之Kruskal
* 1)總是從權(quán)值最小的邊開始考慮,依次考察權(quán)值依次變大的邊(優(yōu)先級(jí)隊(duì)列)
* 2)當(dāng)前的邊要么進(jìn)入最小生成樹的集合瞎抛,要么丟棄
* 3)如果當(dāng)前的邊進(jìn)入最小生成樹的集合中不會(huì)形成環(huán)艺演,就要當(dāng)前邊
* 4)如果當(dāng)前的邊進(jìn)入最小生成樹的集合中會(huì)形成環(huán),就不要當(dāng)前邊
* 5)考察完所有邊之后桐臊,最小生成樹的集合也得到了
*/
public class Kruskal {
public static Set<Edge> kruskal(Graph graph){
if(graph == null){
return null;
}
Set<Edge> res = new HashSet<>();
// 先把所有點(diǎn)都加入并查集
List<Node> nodeList = (List<Node>)graph.nodes.values();
UnionSearch<Node> unionSearch = new UnionSearch<>(nodeList);
// 獲取所有邊
PriorityQueue<Edge> queue = new PriorityQueue<>(new MyComparator());
for(Edge item : graph.edges){
queue.offer(item);
}
while (!queue.isEmpty()){
Edge cur = queue.poll();
Node from = cur.from;
Node to = cur.to;
if(!unionSearch.isSameSet(from,to)){
res.add(cur);
// 加入并查集
unionSearch.union(from,to);
}
}
return res;
}
private void makeUnion(List<Node> list,UnionSearch<Node> unionSearch){
for(Node item : list){
}
}
static class MyComparator implements Comparator<Edge>{
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
static class UnionSearch<V> {
// 節(jié)點(diǎn)位置表
Map<V,Node2> nodes;
// 查找當(dāng)前節(jié)點(diǎn)對應(yīng)的父節(jié)點(diǎn)表
Map<Node2,Node2> parents;
// 作為代表元素表 k代表元素 v 集合中有幾個(gè)元素
Map<Node2,Integer> size;
public UnionSearch(List<V> list){
nodes = new HashMap<>();
parents = new HashMap<>();
size = new HashMap<>();
for(V v : list){
// 出事時(shí)每個(gè)元素單獨(dú)作為一個(gè)結(jié)合
Node2 node = new Node2(v);
nodes.put(v,node);
parents.put(node,node);
size.put(node,1);
}
}
public boolean isSameSet(V v1,V v2){
if(!nodes.containsKey(v1)
|| !nodes.containsKey(v2)){
return false;
}
Node2 node1 = findFather(v1);
Node2 node2 = findFather(v2);
if(node1 == node2){
return true;
}
return false;
}
public void union(V v1,V v2){
if(!nodes.containsKey(v1)
|| !nodes.containsKey(v2)){
return;
}
Node2 node1 = findFather(v1);
Node2 node2 = findFather(v2);
// 兩個(gè)元素不在一個(gè)集合中才合并
if(node1 != node2){
// 看誰的元素多
Node2 big = size.get(node1) > size.get(node2) ? node1 : node2;
Node2 small = size.get(node1) <= size.get(node2) ? node1 : node2;
// 小的掛在大的上面
parents.put(small,big);
size.put(big,size.get(big) + size.get(small));
size.remove(small);
}
}
// 調(diào)用此方法是一定保證集合中有此元素
private Node2 findFather(V node){
// 做一個(gè)扁平化優(yōu)化,把從當(dāng)前節(jié)點(diǎn)網(wǎng)上找的節(jié)點(diǎn)都加入容器中
Stack<Node2> stack = new Stack<>();
Node2 cur = nodes.get(node);
while (parents.get(cur) != cur){
// 路徑上經(jīng)過的節(jié)點(diǎn)加入容器
stack.push(cur);
// 只要此時(shí)節(jié)點(diǎn)不等于父節(jié)點(diǎn)胎撤,說明不是代表節(jié)點(diǎn)就一直往上找
cur = parents.get(cur);
}
// 把所有經(jīng)過節(jié)點(diǎn)直接和代表節(jié)點(diǎn)相連,下次查找是O(1)
while (!stack.isEmpty()){
parents.put(stack.pop(),cur);
}
//跳出來說明已經(jīng)找到了
return cur;
}
}
}
最小生成樹p算法
/**
* 最小生成樹断凶,p算法
* 1)可以從任意節(jié)點(diǎn)出發(fā)來尋找最小生成樹
* 2)某個(gè)點(diǎn)加入到被選取的點(diǎn)中后伤提,解鎖這個(gè)點(diǎn)出發(fā)的所有新的邊
* 3)在所有解鎖的邊中選最小的邊,然后看看這個(gè)邊會(huì)不會(huì)形成環(huán)
* 4)如果會(huì)认烁,不要當(dāng)前邊肿男,繼續(xù)考察剩下解鎖的邊中最小的邊,重復(fù)3)
* 5)如果不會(huì)却嗡,要當(dāng)前邊舶沛,將該邊的指向點(diǎn)加入到被選取的點(diǎn)中,重復(fù)2)
* 6)當(dāng)所有點(diǎn)都被選取稽穆,最小生成樹就得到了
*/
public class Prim {
public static Set<Edge> prim(Graph graph){
if(graph == null){
return null;
}
// 生成優(yōu)先級(jí)隊(duì)列
PriorityQueue<Edge> queue = new PriorityQueue<>(new MyComparator());
Set<Edge> edgeSet = new HashSet<>();
Set<Node> nodeSet = new HashSet<>();
Set<Edge> res = new HashSet<>();
List<Node> nodeList = (List<Node>)graph.nodes.values();
for(Node node : nodeList){ // 防止森林冠王,如果明確沒有森林可不要此循環(huán)。
if(!nodeSet.contains(node)){
// 解鎖點(diǎn)的所有邊
nodeSet.add(node);
for(Edge temp : node.edges){
if(!edgeSet.contains(temp)){
queue.offer(temp);
edgeSet.add(temp);
}
}
while (!queue.isEmpty()){
// 權(quán)重最小的邊
Edge cur = queue.poll();
// 此時(shí)from節(jié)點(diǎn)已經(jīng)在nodeSet結(jié)合中解鎖出來了舌镶,只需判斷to節(jié)點(diǎn)是否解鎖了
if(!nodeSet.contains(cur.to)){
// 要這條邊
res.add(cur);
// 加入點(diǎn)的集合
nodeSet.add(cur.to);
// 解鎖cur.to的所有邊
for (Edge e : cur.to.edges){
if(!edgeSet.contains(e)){
queue.offer(e);
edgeSet.add(e);
}
}
}
}
}
}
return res;
}
static class MyComparator implements Comparator<Edge>{
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
}
Dijkstra算法
1)Dijkstra算法必須指定一個(gè)源點(diǎn)
2)生成一個(gè)源點(diǎn)到各個(gè)點(diǎn)的最小距離表柱彻,一開始只有一條記錄豪娜,即原點(diǎn)到自己的最小距離為0,源點(diǎn)到其他所有點(diǎn)的最小距離都為正無窮大
3)從距離表中拿出沒拿過記錄里的最小記錄哟楷,通過這個(gè)點(diǎn)發(fā)出的邊瘤载,更新源點(diǎn)到各個(gè)點(diǎn)的最小距離表,不斷重復(fù)這一步
4)源點(diǎn)到所有的點(diǎn)記錄如果都被拿過一遍卖擅,過程停止鸣奔,最小距離表得到了
- 優(yōu)化點(diǎn),在distanceMap中查找起點(diǎn)到其他點(diǎn)的最短距離是用遍歷的方式時(shí)間復(fù)雜度是O(N),如果用小根堆惩阶,可以讓時(shí)間復(fù)雜度降到O(logn),但是應(yīng)為會(huì)更新加入到小根堆中的距離數(shù)據(jù)挎狸,一般的小根堆不能自動(dòng)調(diào)整,所以要自己手動(dòng)改寫小根堆断楷。
/**
* 1)Dijkstra算法必須指定一個(gè)源點(diǎn)
* 2)生成一個(gè)源點(diǎn)到各個(gè)點(diǎn)的最小距離表锨匆,一開始只有一條記錄,即原點(diǎn)到自己的最小距離為0冬筒,源點(diǎn)到其他所有點(diǎn)的最小距離都為正無窮大
* 3)從距離表中拿出沒拿過記錄里的最小記錄恐锣,通過這個(gè)點(diǎn)發(fā)出的邊,更新源點(diǎn)到各個(gè)點(diǎn)的最小距離表舞痰,不斷重復(fù)這一步
* 4)源點(diǎn)到所有的點(diǎn)記錄如果都被拿過一遍土榴,過程停止,最小距離表得到了
*/
public class Dijkstra {
public static Map<Node2,Integer> dijkstra1(Node2 node){
if(node == null){
return null;
}
// 距離表,一開始只有出發(fā)點(diǎn)到出發(fā)點(diǎn)的距離為0
Map<Node2,Integer> distanceMap = new HashMap<>();
distanceMap.put(node,0);
// 去重黑名單响牛,每次選舉最小記錄時(shí)排除此表中的數(shù)據(jù)
Set<Node2> noSelect = new HashSet<>();
// 獲取最小記錄點(diǎn)(出發(fā)點(diǎn)到某一點(diǎn)的最短距離)
Node2 minDistance = getMinAndNoSelect(distanceMap,noSelect);
while (minDistance != null){ // 把表中數(shù)據(jù)都遍歷完
for (Edge temp : minDistance.edges){
// 這個(gè)點(diǎn)往能出去的所有邊能不能更新表中的距離
Node2 to = temp.to;
if(!distanceMap.containsKey(to)){
// 如果表中不包含這個(gè)點(diǎn)玷禽,說明還沒找到過重from點(diǎn)到這個(gè)距離,直接加入表中
distanceMap.put(to,distanceMap.get(minDistance) + temp.weight);
}else{
distanceMap.put(to,Math.min(distanceMap.get(to),distanceMap.get(minDistance) + temp.weight));
}
}
minDistance = getMinAndNoSelect(distanceMap,noSelect);
}
return distanceMap;
}
private static Node2 getMinAndNoSelect(Map<Node2,Integer> distanceMap,Set<Node2> noSelcet){
// 由于Dijkstra算法中邊都是非負(fù)的用0就可以了
int min = 0;
Node2 res = null;
// 遍歷距離表
for (Map.Entry<Node2, Integer> item : distanceMap.entrySet()){
Node2 key = item.getKey();
if(noSelcet.contains(key)){
continue;
}
// 比較大小
if(item.getValue() < min){
min = item.getValue();
res = item.getKey();
}
}
noSelcet.add(res);
return res;
}
}