图基础知识以及例题

目录

1 图的存储方式

2 图的宽度优先遍历和广度优先遍历

2.1 图的宽度优先遍历

2.2 图的广(深)度优先遍历

3 拓扑排序算法

4 kruskal算法

5 prim算法

6 Dijkstra算法


1 图的存储方式

(1)邻接表

邻接表,存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构

(2)邻接矩阵

逻辑结构分为两部分:V和E集合,其中,V是顶点,E是边。因此,用一个一维数组存放图中所有顶点数据;用一个二维数组存放顶点间关系(边或弧)的数据。 

(3)在实际解题的时候图有各种各样的表达形式。可以将其先转化成自己常用的一种模板,再用自己熟悉的算法去完成。

public class Graph{
    //大图的结构:点集和边集
    public HashMap<Integer, Node> nodes;
    public HashSet<Edge> edges;

    public Graph(){
        nodes = new HashMap<>();
        edges = new HashSet<>();
    }
}

public class Node{
    public int value; //点上的值,例如城市“a”或者城市1
    public int in;    //点的入度,其他点指向该点的个数
    public int out;   //点的出度,该点指向其他点的个数 无向图的每个店的出度和入度相等
    public ArrayList<Node> nexts; //从该点直接发散出去的边
    public ArrayList<Edge> edges; //属于该点的边

    public Node(int value){
        this.value = value;
        in = 0;
        out = 0;
        nexts = new ArrayList<>();
        edges = new ArrayList<>();
    }
}

public class Edge{
    public int weight; //即该边的长度
    public Node from;  //这里均是有向边,无向边即将两条有向边合起来即可
    public Node to;

    public Edge(int weight, Node from, Node to){
        this.weight = weight;
        this.from = from;
        this.to = to;
    }
}

假设给一个图的类型的结构,将其转化成自己的模板

//matrix 所有的边
//N*3的矩阵
//[weight, from节点上面的值, to节点上面的值]

public static Graph createGraph(Integer[][] matrix){
    Graph graph = new Graph();
    for(int i = 0; i < matrix.length; i++){
        Integer from = matrix[i][0];
        Integer to = matrix[i][1];
        Integer weight = matrix[i][2];
        if(!graph.nodes.containsKey(from)){//如果该城市是第一次遇见,则将其加入图中
            graph.nodes.put(from, new Node(from));//在图的点集中,将该城市新建出来。
                                                  //编号及其对应的城市挂成记录放入点集
        }
        if(!graph.nodes.containsKey(to)){//同上
            graph.nodes.put(to, new Node(to));
        }        
        Node fromNode = graph.nodes.get(from);
        Node toNode = graph.nodes.get(to);
        Edge newEdge = new Edge(weight, fromNode, toNode); //将边建立起来
        fromNode.next.add(toNode); //将该边加入到fromNode中的next里
        fromNode.out++;
        toNode.in++;
        fromNode.edges.add(newEdge); //新建的这条边是属于from点的
        graph.edges.add(newEdge); //将该边放入图里的边集
    }
    return graph;
}

2 图的宽度优先遍历和广度优先遍历

2.1 图的宽度优先遍历

(1)利用队列实现

(2)从源节点开始依次按照宽度进队列,然后弹出

(3)每弹出一个点,把该节点所有没有进过队列的邻接点放入队列

(4)直到队列变空

//从node出发,进行宽度优先遍历
public static void bfs(Node node){
    if (node == null){
        return;
    }
    Queue<Node> queue = new LinkedList<>();
    HashSet<Node> set = new HashSet<>();
    queue.add(node);
    set.add(node);
    while(!queue.isEmpty()){
        Node cur = queue.poll();
        System.out.println(cur.value);
        for(Node next : cur.nexts){
            if(!set.contains(next)){
                set.add(next);
                queue.add(next);
            }
        }
    }
}

2.2 图的广(深)度优先遍历

(1)利用栈实现

(2)从源节点开始把节点按照深度放入栈,然后弹出

(3)每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈

(4)直到栈变空

public static void dfs(Node node){
    if(node == null){
        return;
    }
    Stack<Node> stack = new Stack<>();
    HashSet<Node> set = new HashSet<>();
    stack.add(node);
    set.add(node);
    System.out.println(node.value);
    while(!stack.isEmpty()){
        Node cur = stack.pop();
        for (Node next : cur.nexts){
            if(!set.contains(next)){
                stack.push(cur);
                stack.push(next);
                set.add(next);
                System.out.println(next.value);
                break;
            }
        }
    }
}

3 拓扑排序算法

适用范围:要求有向图,且有入度为0的节点,且没有环

解题思路:

①首先找到入度为0的点

②将该点打印出来,随后将其与其影响从图中抹去

 ③回到①

//directed graph and no loop
public static List<Node> sortedTopology(Graph graph){
    //key: 某一个node
    //value: 剩余的入度
    HashMap<Node, Integer> inMap = new HashMap<>();
    //入度为0的点,才能进这个队列
    Queue<Node> zeroInQueue = new LinkedList<>();
    for(Node node : graph.nodes.values()){
        inMap.put(node, node.in);
        if(node.in == 0){
            zeroInQueue.add(node);
        }
    }

    //拓扑排序的结果,依次加入result
    List<Node> result = new ArrayList<>();
    while(!zeroInQueue.isEmpty()){
        Node cur = zeroInQueue.poll();
        result.add(cur);
        for(Node next : cur.nexts){
            inMap.put(next, inMap.get(next) - 1);
            if(inMap.get(next) == 0){
                zeroInQueue.add(next);
            }
        }
    }
    return result;
}

4 kruskal算法

适用范围:要求无向图

问题:求图中能把所有点连起来的最小的边和

解题思路:

①将每个点当成一个独立的集合

②从小到大依次考察每条边,from跟to是否在一个集合里

③如果不在,将两点合在一个集合里,且选择该边;如果在,则该边不要

④回到②

哈希集结构:

public static class MySets{
    public HashMap<Node,List<Node>> setMap;
    public MySets(List<Node> nodes){
        for(Node cur : nodes){
            List<Node> set = new ArrayList<Node>();
            set.add(cur);
            setMap.put(cur,set);
        }
    }

    public boolean isSameSet(Node from, Node to) {
        List<Node> fromSet = setMap.get(from);
        List<Node> toSet = setMap.get(to);
        return fromSet == toSet;
    }

    public void union(Node from, Node to){
        List<Node> fromSet = setMap.get(from);
        List<Node> toSet = setMap.get(to);

        for(Node toNode : toSet){
            fromSet.add(toNode);
            setMap.put(toNode,fromSet);
        }
    }
}

 并查集结构:

public static Set<Edge> KruskalMST(Graph graph){
    UnionFind unionFind = new UnionFind();
    unionFind.makeSets(graph.nodes.values());
    PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator));
    for(Edge edge : graph.edges){//M 条边
        priorityQueue.add(edge); // O(logM)
    }
    
    Set<Edge> result = new HashSet<>();
    while (!priorityQueue.isEmpty()){ //M条边
        Edge edge = priorityQueue.poll(); // O(logM)
        if(!unionFind.isSameSet(edge.from, edge.to)){ //O(1)
            result.add(edge);
            unionFind.union(edge.from, edge.to);
        }
    }
    return result;
}

5 prim算法

适用范围:要求无向图

算法思路:

①从任意一点出发,然后将与该点相连的边解锁

②取最小的边,连接到下一个点

③使用过的边不许再使用,从新的点解锁与新点相连的边

④回到②

public static class EdgeComparator implements Comparator<Edge>{

    public int compare(Edge o1, Edge o2){
        return o1.weight - o2.weight;
    }
}

public static Set<Edge> primMST(Graph graph){

    //解锁的边进入小根堆
    PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(
            new EdgeComparator());

    HashSet<Node> set = new HashSet<>();

    Set<Edge> result = new HashSet<>(); //依次挑选的边在result里

    for(Node node : graph.nodes.values()){ //随便挑了一个点
        // node 是开始点
        if(!set.contains(node)){
            set.add(node);
            for(Edge edge: node.edges){ //由一个点,解锁所有相连的边
                priorityQueue.add(edge);
            }
            while(!priorityQueue.isEmpty()){
                Edge edge = priorityQueue.poll(); //弹出解锁的边中,最小的边
                Node toNode = edge.to;
                if(!set.contains(toNode)){ //不含有的时候,就是新的点
                    set.add(toNode);
                    result.add(edge);
                    for(Edge nextEdge : toNode.edges){
                        priorityQueue.add(nextEdge);
                    }
                }
            }
        }
    }
    return result;
}

6 Dijkstra算法

适用范围:没有累加和为负数的环

算法思路:

(1)从其中一点出发(必须规定出发点),分别计算起点距离各个点的距离

(2)一开始赋值起点为0,其余均为正无穷

(3)从该点开始选择各条边,看是否更新其余距离使其更小,有的话即更新

(4)使用完该点后,从起点到该点的距离锁死不用。

(5)从表中剩下的记录中选取最小的值,回到(3)

 

public static HashMap<Node, Integer> dijkstra1(Node head){
    //从head出发到所有点的最小距离
    //key : 从head出发到达key
    //value : 从head出发到达key的最小距离
    //如果在表中,没有T的记录,含义是从head出发到T这个点的距离为正无穷
    HashMap<Node, Integer> distanceMap = new HashMap<>();
    distanceMap.put(head,0);
    //已经求过距离的节点,存在selectedNodes中,以后再也不碰
    HashSet<Node> selectedNodes = new HashSet<>();
    Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
    while( minNode != null){
        int distance = distanceMap.get(minNode);
        for(Edge edge : minNode.edges){
            Node toNode = edge.to;
            if(!distanceMap.containsKey(toNode)){
                distanceMap.put(toNode, distance + edge.weight);
            }
            distanceMap.put(edge.to, Math.min(distanceMap.get(toNode),
                            distance + edge.weight));
        }
        selectedNodes.add(minNode);
        minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
    }
    return distanceMap;
}

public static Node getMinDistanceAndUnselectedNode(
        HashMap<Node, Integer> entry : distanceMap,
        HashSet<Node> touchedNodes){
    Node minNode = null;
    int minDistance = Integer.MAX_VALUE;
    for(Entry<Node, Integer> entry : distanceMap.entrySet()){
        Node node = entry.getKey();
        int distance = entry.getValue();
        if(!touchedNodes.contains(node)&& distance < minDistance){
            minNode = node;
            minDistance = distance;
        }
    }
    return minNode;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是一份可能涵盖到的C语言考试知识点和一些例题: 1. 基础语法和数据类型 - 基本语法:变量、运算符、表达式、控制语句等等。 - 数据类型:整型、浮点型、字符型、数组、指针等等。 例题: ```c int a = 10; float b = 3.14; char c = 'A'; int arr[5] = {1, 2, 3, 4, 5}; int *p = &a; ``` 2. 函数和指针 - 函数:定义、声明、调用、参数传递等等。 - 指针:定义、操作、指针与数组、指针与函数等等。 例题: ```c int max(int a, int b) { return a > b ? a : b; } int main() { int x = 10; int y = 20; int *p = &x; printf("%d\n", max(x, y)); printf("%d\n", *p); return 0; } ``` 3. 字符串和文件操作 - 字符串:定义、赋值、函数库、操作等等。 - 文件操作:打开、读写、关闭、错误处理等等。 例题: ```c char str1[] = "hello"; char str2[10]; strcpy(str2, str1); printf("%s\n", str2); FILE *fp; char c; fp = fopen("file.txt", "r"); if (fp == NULL) { printf("Error opening file!\n"); return 1; } while ((c = getc(fp)) != EOF) { printf("%c", c); } fclose(fp); ``` 4. 动态内存分配和结构体 - 动态内存分配:malloc、calloc、realloc、free等等。 - 结构体:定义、访问、指针、函数等等。 例题: ```c typedef struct { char name[50]; int age; } Person; Person *person = malloc(sizeof(Person)); strcpy(person->name, "Tom"); person->age = 20; printf("%s %d\n", person->name, person->age); free(person); ``` 以上是一些C语言考试可能会涉及到的知识点和例题,希望能够对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KevinJune

希望我的内容对你们有所帮助

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值