数据结构之图——探索图论的奥秘

news2024/11/16 23:46:29

前言

在这篇文章中,我们一起来看看我们生活中都会用到,但却不那么熟悉的数据结构——图(英语:graph)。我们看下百科定义:

在计算机科学中,图(英语:graph)是一种抽象数据类型,用于实现数学中图论的无向图和有向图的概念。

图的数据结构包含一个有限(可能是可变的)的集合作为节点集合,以及一个无序对(对应无向图)或有序对(对应有向图)的集合作为边(有向图中也称作弧)的集合。节点可以是图结构的一部分,也可以是用整数下标或引用表示的外部实体。

图的数据结构还可能包含和每条边相关联的数值(edge value),例如一个标号或一个数值(即权重,weight;表示花费、容量、长度等)。

从上面定义,我们知道图分为有向图和无向图,图是由顶点和边,还有可能权重。我们看看图在哪些方面有应用:交通运输规划,每个地方就是图的顶点,而每条路就是图的边;社交网络分析,每个用户就是顶点,而用户之间是否为好友就是边;互联网,每个网页就是顶点,而网页上跳转到其他网页就是边,我们每天使用的百度,谷歌搜索引擎实现其中用到很重要技术就是图。还有像电路设计,通信网络,计划与排程问题都用到了图数据结构来表示他们的关系。

起源(欧拉与柯尼斯堡的七桥问题)

十八世纪,在今天俄罗斯加里宁格勒市还被称为柯尼斯堡的年代。像其他许多大城市一样,一条大河(普列戈利亚河)穿城而过。柯尼斯堡除了被一分为二以外,还包含河中的两个岛屿,人们建有七座桥梁连接着不同的陆地。当时有一个著名的游戏谜题,就是在所有桥都只能走一遍的前提下,怎样才能把这片区域所有的桥都走遍?这个谜题成为当地人一项热衷的消遭运动,许多人声称已经找到了这样一条路径,但当被要求按照规则再走一遍时,却发现还是没有人能够做到。直到 1736 年,瑞士数学家莱昂哈德·欧拉给出了答案,他在论文《柯尼斯堡的七桥》中解释了其中的原因,证明这样的步行方式并不存在,并在文中提出和解决了一笔画问题。

莱昂哈德·欧拉(Leonhard Euler,1707年4月15日–1783年9月18日)是18世纪最杰出的数学家之一,同时也是物理学、天文学和工程学领域的重要贡献者。大家感兴趣可以看下号称数学最美公式之一的欧拉公式

图论基础知识

图由节点(Vertex)和连接节点的边(Edge)组成。

节点(Vertex):图中的基本元素,也称为顶点。节点可以代表各种事物,如城市、人物、物体等。

边(Edge):连接图中两个节点的线,表示节点之间的关系。边可以是有向的(箭头指向特定的方向)或无向的(没有箭头,表示双向关系)。

度(Degree):节点的度是与其相连的边的数量。对于有向图,节点的度分为入度(In-Degree)和出度(Out-Degree),分别表示指向该节点的边的数量和从该节点出发的边的数量。

路径(Path):图中连接节点的一系列边构成的序列。路径的长度是指路径上的边的数量。如果路径中的所有节点都是不同的,则称该路径是简单路径。

环(Cycle):由图中一系列边构成的闭合路径,起点和终点相同,并且路径中的节点都是不同的。

连通性(Connectivity):一个图被称为连通的,如果从图中的任意一个节点到另一个节点都存在至少一条路径。如果一个图不是连通的,则可以分为多个连通分量。

图的类型:根据图中边的性质,图可以分为有向图(Directed Graph)和无向图(Undirected Graph)。此外,图还可以是加权的,即边上带有权重(Weight)。

邻接矩阵(Adjacency Matrix)和邻接表(Adjacency List):这两种数据结构用于表示图的连接关系。邻接矩阵是一个二维数组,其中的元素表示节点之间是否相连。邻接表是由一组链表或数组构成的数据结构,用于表示每个节点的邻居节点。

在这里插入图片描述
上图我们发现,右边就是我们熟悉的图。那么图在什么情况下能叫做树。
树是一种特殊的无向图。

无向图:树是一种无向图,其中任意两个节点之间都有唯一的简单路径。
连通图:树是连通图,即从任意一个节点出发,都可以到达图中的所有其他节点。换句话说,树是一个连通且无环的图。
无回路:树不包含回路,即没有形成环的路径。每个节点都只能通过一条路径到达另一个节点,因此树不会出现闭合的环路。
N个节点的树有N-1条边:如果一个无向图含有N个节点,并且是树的话,那么它必然有N-1条边。这个特性被称为树的性质之一。

图表示方式

**邻接矩阵(Adjacency Matrix)邻接表(Adjacency List)**是两种常用的图表示方法,它们各自有自己的优缺点:

邻接矩阵的优缺点:

优点:
直观易懂:邻接矩阵直观地展示了图中节点之间的连接关系,易于理解和实现。
快速查找:可以在O(1)的时间内查找两个节点之间是否有边相连。
适用于稠密图:对于边的数量较多的稠密图,邻接矩阵的空间复杂度较低,使用更为高效。
缺点:

空间复杂度高:邻接矩阵需要使用N^2的空间来存储N个节点的连接关系,对于稀疏图来说,会浪费大量空间。
不适用于大规模图:当图规模很大时,邻接矩阵的空间开销会变得非常巨大,导致内存不足或效率低下。
无法存储边的权重:如果图中的边有权重,邻接矩阵需要额外的空间来存储这些权重信息,增加了复杂度。

下面是用Java实现邻接矩阵表示图

import java.util.Arrays;

public class AdjacencyMatrix {
    private int[][] matrix;
    private int numVertices;

    public AdjacencyMatrix(int numVertices) {
        this.numVertices = numVertices;
        matrix = new int[numVertices][numVertices];
    }

    // 添加边
    public void addEdge(int src, int dest) {
        matrix[src][dest] = 1;
        matrix[dest][src] = 1; // 无向图,所以需要反向也设置为1
    }

    // 删除边
    public void removeEdge(int src, int dest) {
        matrix[src][dest] = 0;
        matrix[dest][src] = 0; // 无向图,所以需要反向也设置为0
    }

    // 打印邻接矩阵
    public void printMatrix() {
        for (int i = 0; i < numVertices; i++) {
            System.out.println(Arrays.toString(matrix[i]));
        }
    }

    public static void main(String[] args) {
        int numVertices = 5;
        AdjacencyMatrix graph = new AdjacencyMatrix(numVertices);

        // 添加边
        graph.addEdge(0, 1);
        graph.addEdge(0, 4);
        graph.addEdge(1, 2);
        graph.addEdge(1, 3);
        graph.addEdge(1, 4);
        graph.addEdge(2, 3);
        graph.addEdge(3, 4);

        // 打印邻接矩阵
        graph.printMatrix();
    }
}

邻接表的优缺点:

优点:

空间利用率高:邻接表只存储有连接关系的节点,对于稀疏图来说,空间利用率更高。
易于插入和删除边:在邻接表中,插入或删除边的操作更为高效,时间复杂度为O(1)。
适用于稀疏图:对于边的数量较少的稀疏图,邻接表的空间复杂度更低,更加节省内存空间。
缺点:

查找边的时间复杂度高:在邻接表中查找两个节点之间是否有边相连,时间复杂度为O(n),其中n为节点的度数。
不适用于密集图:对于边的数量较多的密集图,邻接表的空间复杂度会增加,效率较低。
不适合快速查找:如果需要频繁进行两个节点之间是否相连的查找操作,邻接表的效率可能不如邻接矩阵。

下面是用Java实现表示邻接表

import java.util.*;

public class AdjacencyList {
    private Map<Integer, List<Integer>> adjList;

    public AdjacencyList() {
        adjList = new HashMap<>();
    }

    // 添加节点
    public void addNode(int node) {
        adjList.put(node, new LinkedList<>());
    }

    // 添加边
    public void addEdge(int src, int dest) {
        adjList.get(src).add(dest);
        adjList.get(dest).add(src); // 无向图,所以需要反向也添加
    }

    // 删除边
    public void removeEdge(int src, int dest) {
        adjList.get(src).remove((Integer) dest);
        adjList.get(dest).remove((Integer) src); // 无向图,所以需要反向也删除
    }

    // 打印邻接表
    public void printList() {
        for (Map.Entry<Integer, List<Integer>> entry : adjList.entrySet()) {
            System.out.print(entry.getKey() + " -> ");
            for (Integer neighbor : entry.getValue()) {
                System.out.print(neighbor + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        AdjacencyList graph = new AdjacencyList();

        // 添加节点
        for (int i = 0; i < 5; i++) {
            graph.addNode(i);
        }

        // 添加边
        graph.addEdge(0, 1);
        graph.addEdge(0, 4);
        graph.addEdge(1, 2);
        graph.addEdge(1, 3);
        graph.addEdge(1, 4);
        graph.addEdge(2, 3);
        graph.addEdge(3, 4);

        // 打印邻接表
        graph.printList();
    }
}

最短路径算法

图的最短路径算法用于找到两个节点之间的最短路径。

迪杰斯特拉算法(Dijkstra Algorithm)

package demo.algorithm.algorithm;

import java.util.*;
/**
 *最短路径算法-迪杰斯特拉(Dijkstra)算法(1956年发表)
 * 迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。
 * Dijkstra 算法是一个基于「贪心」、「广度优先搜索」、「动态规划」求一个图中一个点到其他所有点的最短路径的算法,时间复杂度 O(n2)
 * 每次从 「未求出最短路径的点」中 取出 距离距离起点 最小路径的点,以这个点为桥梁 刷新「未求出最短路径的点」的距离
 * 迪杰斯特拉(Dijkstra)算法,能处理有环图,不能处理含有负权边的图.
 *
 * 如果存在负权重,则可能在之后的计算中得到总权重更小的路径,从而影响之前的结果(译注:即可能出现多绕路反而路线更短的情况,不合实际)。
 * @author jishenglong on 2023/1/24 9:14
 **/
public class DijkstraAlgorithm {
    private static final int INF = Integer.MAX_VALUE; // 无穷大表示不可达

    public static void dijkstra(int[][] graph, int start, int end) {
        int n = graph.length; // 图的顶点数
        int[] dist = new int[n]; // 存储起始点到各个顶点的最短距离
        boolean[] visited = new boolean[n]; // 标记顶点是否已访问
        int[] prev = new int[n]; // 记录最短路径中的前驱顶点

        // 初始化距离数组和前驱数组
        Arrays.fill(dist, INF);
        Arrays.fill(prev, -1);
        dist[start] = 0;

        // 开始算法
        for (int i = 0; i < n - 1; i++) {
            int minDist = INF;
            int minIndex = -1;

            // 找到当前未访问顶点中距离起始点最近的顶点
            for (int j = 0; j < n; j++) {
                if (!visited[j] && dist[j] < minDist) {
                    minDist = dist[j];
                    minIndex = j;
                }
            }
            System.out.println(String.format("minIndex:%s,minDist:%s",minIndex,minDist ));
            // 标记该顶点为已访问
            visited[minIndex] = true;

            // 更新与该顶点相邻顶点的最短距离和前驱顶点
            for (int j = 0; j < n; j++) {
                if (!visited[j] && graph[minIndex][j] != INF) {
                    int newDist = dist[minIndex] + graph[minIndex][j];
                    if (newDist < dist[j]) {
                        dist[j] = newDist;
                        prev[j] = minIndex;
                    }
                }
            }
        }
        System.out.println("cost: " + dist[end]);
        // 打印最短路径
        printShortestPath(prev, start, end);
    }

    // 递归打印最短路径
    private static void printShortestPath(int[] prev, int start, int end) {
        if (end == start) {
            System.out.print(start);
        } else if (prev[end] == -1) {
            System.out.println("No path exists from " + start + " to " + end);
        } else {
            printShortestPath(prev, start, prev[end]);
            System.out.print(" -> " + end);
        }
    }

    public static void main(String[] args) {
        int[][] graph = {
                {0, 4, 2, INF, INF},
                {4, 0, 1, 5, INF},
                {2, 1, 0, 8, 10},
                {INF, 5, 8, 0, 2},
                {INF, INF, 10, 2, 0}
        };

        int start = 0; // 起始点
        int end = 3; // 终点

        dijkstra(graph, start, end);
    }
}


Floyd算法

package demo.algorithm.algorithm;

/**
 * 最短路径算法
 * Floyd算法是一个经典的动态规划算法,它又被称为插点法。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。
 * Floyd算法是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,算法目标是寻找从点i到点j的最短路径。
 * 时间复杂度:O(n^3);空间复杂度:O(n^2)
 * 弗洛伊德算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或有向图或负权(但不可存在负权回路)的最短路径问题,同时也被用于计算有向图的传递闭包
 *
 * @author jishenglong on 2023/1/24 9:51
 **/
public class FloydAlgorithm {
    private int numVertices;
    private int[][] distance;

    public FloydAlgorithm(int numVertices) {
        this.numVertices = numVertices;
        this.distance = new int[numVertices][numVertices];

        // 初始化距离矩阵,表示顶点之间的初始距离
        for (int i = 0; i < numVertices; i++) {
            for (int j = 0; j < numVertices; j++) {
                if (i == j) {
                    distance[i][j] = 0; // 顶点到自身的距离为0
                } else {
                    distance[i][j] = Integer.MAX_VALUE; // 初始化为无穷大
                }
            }
        }
    }

    // 添加边和权重
    public void addEdge(int source, int destination, int weight) {
        distance[source][destination] = weight;
        // 如果是有向图,注释掉下一行
        distance[destination][source] = weight;
    }

    /**
     * 佛洛伊德算法的核心方法
     * 在三重循环的每一次迭代中,我们检查通过中间顶点 k 是否能够获得更短的路径。
     * 具体操作是检查从顶点 i 到顶点 k 的路径与从顶点 k 到顶点 j 的路径之和是否小于从顶点 i 到顶点 j 的当前已知路径。如果是,则更新最短路径
     * <p>
     * 外层循环 (k): 该循环遍历所有可能的中间顶点。在每次迭代中,k 表示中间顶点,我们尝试看看通过中间顶点是否能够找到更短的路径。
     * 第一层内层循环 (i): 该循环遍历所有可能的起始顶点。
     * 第二层内层循环 (j): 该循环遍历所有可能的目标顶点
     */
    public void floydWarshall() {
        // 三重循环,依次考虑每个顶点作为中间点的情况
        for (int k = 0; k < numVertices; k++) {
            for (int i = 0; i < numVertices; i++) {
                for (int j = 0; j < numVertices; j++) {
                    if (distance[i][k] != Integer.MAX_VALUE && distance[k][j] != Integer.MAX_VALUE &&
                            distance[i][k] + distance[k][j] < distance[i][j]) {
                        distance[i][j] = distance[i][k] + distance[k][j];
                    }
                }
            }
        }

        // 打印最短路径矩阵
        printShortestPaths();
    }

    private void printShortestPaths() {
        System.out.println("Shortest paths between all pairs of vertices:");

        for (int i = 0; i < numVertices; i++) {
            for (int j = 0; j < numVertices; j++) {
                if (distance[i][j] == Integer.MAX_VALUE) {
                    System.out.print("INF\t");
                } else {
                    System.out.print(distance[i][j] + "\t");
                }
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        int numVertices = 4;
        FloydAlgorithm floydGraph = new FloydAlgorithm(numVertices);

        floydGraph.addEdge(0, 1, 3);
        floydGraph.addEdge(0, 2, 5);
        floydGraph.addEdge(1, 2, 1);
        floydGraph.addEdge(2, 3, 7);

        floydGraph.floydWarshall();
    }
}

当我们只要求一个节点到其他节点的最短路径那么迪杰斯特拉(Dijkstra)算法比较适合;如果要求任意两点间的最短路径那么Floyd算法比较适合。

K短路

有时候需求是需要展示从A点到B点,3条最短路径,也就是k短路。k短路问题,可以参考以下文章k 短路

package demo.algorithm.algorithm;

import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

/**
 * AStar K短路
 * https://oi-wiki.org/graph/kth-path/
 *
 * @author jisl on 2023/12/20 15:54
 **/
public class AStarKPaths {
    // 定义常量:表示无穷大的边权值
    final int inf = Integer.MAX_VALUE / 2;
    // 声明变量:节点数量
    int n;
    // 声明数组:存储节点的启发式值
    int[] H;
    // 声明数组:记录节点在搜索过程中被访问的次数
    int[] cnt;
    // 声明变量:记录当前处理的正向边和逆向边的数量
    int cur, cur1;
    // 正向图的邻接表
    int[] h;  // 头结点
    int[] nxt;  // 下一条边的索引
    int[] p;    // 相邻节点的编号
    int[] w;    // 边的权重
    // 逆向图的邻接表
    int[] h1;  // 头结点
    int[] nxt1;  // 下一条边的索引
    int[] p1;    // 相邻节点的编号
    int[] w1;    // 边的权重

    // 声明数组:记录节点是否被访问过
    boolean[] tf;


    // 构造函数
    public AStarKPaths(int vertexCnt, int edgeCnt) {
        n = vertexCnt;
        //数组从0开始,要定义访问到vertexCnt,那么数组长度需要+1
        int vertexLen = vertexCnt + 1;
        int edgeLen = edgeCnt + 1;
        // 初始化数组
        H = new int[vertexLen];
        cnt = new int[vertexLen];
        h = new int[vertexLen];
        nxt = new int[edgeLen];
        p = new int[edgeLen];
        w = new int[edgeLen];
        h1 = new int[vertexLen];
        nxt1 = new int[edgeLen];
        p1 = new int[edgeLen];
        w1 = new int[edgeLen];
        tf = new boolean[vertexLen];
    }

    /**
     * 添加正向图的边
     *
     * @param x 起点
     * @param y 终点
     * @param z 边的权重
     */
    void addEdge(int x, int y, int z) {
        // 当前边的索引
        cur++;
        // 将当前边的下一条边指向原来的头结点
        nxt[cur] = h[x];
        // 更新头结点为当前边的索引
        h[x] = cur;
        // 记录相邻节点和边的权重
        p[cur] = y;
        w[cur] = z;
    }


    /**
     * 添加逆向图的边
     *
     * @param x 起点
     * @param y 终点
     * @param z 边的权重
     */
    void addEdge1(int x, int y, int z) {
        // 当前逆向边的索引
        cur1++;
        // 将当前逆向边的下一条边指向原来的逆向头结点
        nxt1[cur1] = h1[x];
        // 更新逆向头结点为当前逆向边的索引
        h1[x] = cur1;
        // 记录逆向相邻节点和边的权重
        p1[cur1] = y;
        w1[cur1] = z;
    }

    /**
     * 节点类,用于A*算法中表示搜索过程中的节点
     */
    class Node implements Comparable<Node> {
        int x, v; // 节点编号和从起点到该节点的累计代价
        List<Integer> path; // 存储路径

        /**
         * 构造方法,初始化节点
         *
         * @param x    节点编号
         * @param v    从起点到该节点的累计代价
         * @param path 从起点到该节点的路径
         */
        Node(int x, int v, List<Integer> path) {
            this.x = x;
            this.v = v;
            this.path = new ArrayList<>(path);
            this.path.add(x); // 将当前节点加入路径
        }

        /**
         * 优先队列比较方法,根据节点的估值(v + H[x])升序排序
         *
         * @param a 另一个节点
         * @return 比较结果,1表示大于,-1表示小于
         */
        @Override
        public int compareTo(Node a) {
            return v + H[x] > a.v + H[a.x] ? 1 : -1;
        }
    }


    PriorityQueue<Node> q = new PriorityQueue<>();

    /**
     * 节点类,用于Dijkstra算法中表示搜索过程中的节点
     */
    class Node2 implements Comparable<Node2> {
        int x, v; // 节点编号和从起点到该节点的累计代价

        /**
         * 构造方法,初始化节点
         *
         * @param x 节点编号
         * @param v 从起点到该节点的累计代价
         */
        Node2(int x, int v) {
            this.x = x;
            this.v = v;
        }

        /**
         * 优先队列比较方法,根据节点的累计代价(v)升序排序
         *
         * @param a 另一个节点
         * @return 比较结果,1表示大于,-1表示小于
         */
        @Override
        public int compareTo(Node2 a) {
            return v > a.v ? 1 : -1;
        }
    }


    PriorityQueue<Node2> Q = new PriorityQueue<>();

    /**
     * 构建图并执行 A* K 短路算法
     *
     * @param graph 图的邻接矩阵,表示节点间的边权值
     * @param s     起点(source)
     * @param t     目标节点(target)
     * @param k     要求的最短路径数量
     */
    public void build(int[][] graph, int s, int t, int k) {
        // 起点s、目标节点t、路径数量k等
        // 读取图的边信息并构建正向图和逆向图
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (graph[i][j] != Integer.MAX_VALUE / 2) {
                    addEdge(i, j, graph[i][j]);
                    addEdge1(j, i, graph[i][j]);
                }
            }
        }

        // 初始化启发式数组H,使用逆向边进行估值
        for (int i = 1; i <= n; i++) {
            H[i] = inf;
        }

        // 使用 Dijkstra 算法计算从目标节点 t 到其他节点的估值 H
        Q.add(new Node2(t, 0));
        while (!Q.isEmpty()) {
            Node2 x = Q.poll();
            if (tf[x.x]) {
                continue;
            }
            tf[x.x] = true;
//            启发式数组H 在Node节点比较的时候使用
            H[x.x] = x.v;
            for (int j = h1[x.x]; j != 0; j = nxt1[j]) {
                Q.add(new Node2(p1[j], x.v + w1[j]));
            }
        }

        // 使用 A* 算法计算从起点 s 到目标节点 t 的 k 条最短路径
        q.add(new Node(s, 0, new ArrayList<>())); // 将起点 s 加入优先队列 q,初始累计代价为 0,路径为空列表
        while (!q.isEmpty()) {
            Node x = q.poll(); // 弹出当前累计代价最小的节点
            cnt[x.x]++; // 增加当前节点的访问次数
            if (x.x == t) {
                // 如果当前节点是目标节点,输出 A* K 短路算法的相关信息,包括路径
                System.out.println(String.format("AStarKPaths对应值:x.v=%s,cnt[x.x]=%s,path=%s", x.v, cnt[x.x], getPath(x.path, s, t)));
            }
            if (x.x == t && cnt[x.x] == k) {
                System.out.println(x.v); // 如果找到第 k 条最短路径,输出累计代价并结束
                return;
            }
            if (cnt[x.x] > k) {
                continue; // 如果当前节点访问次数超过 k,跳过后续处理
            }
            for (int j = h[x.x]; j != 0; j = nxt[j]) {
                q.add(new Node(p[j], x.v + w[j], new ArrayList<>(x.path))); // 将该节点的邻接节点加入优先队列,并更新累计代价和路径
            }
        }
        System.out.println("-1");
    }


    private String getPath(List<Integer> path, int a, int b) {
        StringBuilder result = new StringBuilder();
        result.append("Shortest distance from ").append(a).append(" to ").append(b).append(": ");
        for (int i = 0; i < path.size(); i++) {
            result.append(" -> ").append(path.get(i));
        }
        result.append("\n==================================");
        return result.toString();
    }


    public static void main(String[] args) {
        int INF = Integer.MAX_VALUE / 2;
        int[][] graph = {
                {INF, 4, 2, INF, INF},
                {4, INF, 6, 5, INF},
                {2, 1, INF, 8, 10},
                {INF, 5, 8, INF, 2},
                {INF, INF, 10, 2, INF}
        };
        final AStarKPaths aStarKPaths = new AStarKPaths(graph.length, graph.length * graph.length);
        aStarKPaths.build(graph, 0, 3, 3);
    }
}

总结

以上简单介绍了,图论知识。对于图论中用的比较多最短路和k短路用Java实现了。实际图论是比较难的问题,这边只是做了简单的介绍,抛砖引玉。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1665452.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

py黑帽子学习笔记_网络编程工具

tcp客户端 socket.AF_INET表示使用标准IPV4地址和主机名 SOCK_STREAM表示这是一个TCP客户端 udp客户端 udp无需连接&#xff0c;因此不需要client.connect这种代码 socket.SOCK_DGRAM是udp的 tcp服务端 server.listen(5)表示设置最大连接数为5 发现kill server后端口仍占用…

大模型微调之 在亚马逊AWS上实战LlaMA案例(九)

大模型微调之 在亚马逊AWS上实战LlaMA案例&#xff08;九&#xff09; 代码阅读 src/llama_recipes/inference/prompt_format_utils.py 这段代码是一个Python模块&#xff0c;它定义了几个类和模板&#xff0c;用于生成安全评估的提示文本。以下是对每一行代码的注释和提示词…

Datax数据采集

一、Datax介绍 官网&#xff1a; DataX/introduction.md at master alibaba/DataX GitHub DataX 是阿里云 DataWorks数据集成 的开源版本&#xff0c;在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。 DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre、…

MySQL innodb_buffer_pool_size 相关常用语句

对于MySQL速度慢的问题&#xff0c;除了优化 SQL 以外&#xff0c;应该必须优先想到的即使 MySQL 数据库的 innodb_buffer_pool_size 配置问题。 一般来说&#xff0c;innodb_buffer_pool_size 的默认大小都是很小的&#xff0c;尤其是 win 下其默认大小更是只有离谱的 8M。Li…

2024 年最新本地、云服务器安装部署 miniconda 环境详细教程(更新中)

Anaconda 概述 Anaconda 是专门为了方便使用 Python 进行数据科学研究而建立的一组软件包&#xff0c;涵盖了数据科学领域常见的 Python 库&#xff0c;并且自带了专门用来解决软件环境依赖问题的 conda 包管理系统。主要是提供了包管理与环境管理的功能&#xff0c;可以很方便…

土壤墒情自动监测站—墒情异常数据报警提示

TH-TS600土壤墒情自动监测站通常配备有预警提示功能&#xff0c;用于在墒情出现异常情况时及时向用户发出警告。这一功能对于农业生产至关重要&#xff0c;因为它可以帮助农民或农田管理者及时发现土壤墒情的变化&#xff0c;并采取相应的措施来确保作物健康生长。 土壤墒情自动…

Excel实用技巧持续学习

1、Excel高效设置图标格式&#xff1a; 2、饼图可以统一设置数据标签在图外面&#xff01;&#xff01; 环形图不可以&#xff0c;但是可以中间手动加上白色圆形&#xff0c;将饼图变为圆环。 可以设置标签的文本显示&#xff1a; 3、饼图和环形图最好进行排序&#xff01;显得…

睿尔曼机械臂ROS控制

下载git工程 git clone https://github.com/RealManRobot/rm_robot.git安装配置 catkin build rm_msgs source devel/setup.bash catkin build source setup.bash这里注意&#xff0c;如果采用setup.sh多半不会成功&#xff0c;必须要source setup.bash文件&#xff0c;ros才…

云渲染动画300帧需要多久呢?瑞云渲染为你揭秘

在动画制作过程中&#xff0c;渲染的速度非常关键。对于一个项目需要渲染的300帧来说&#xff0c;由于硬件的限制&#xff0c;许多公司的设备可能无法快速完成这项任务。此时&#xff0c;借助云渲染服务的强大计算能力&#xff0c;可以显著减少完成时间&#xff0c;从而提速整个…

使用Python和akshare完成个股信息统计和实时记录

标题:使用Python和akshare完成个股信息统计和实时记录 在金融投资领域,实时获取和记录个股信息对于投资者至关重要。Python 作为一种强大的编程语言,在金融数据分析和处理方面有着广泛的应用。akshare 是一个专门用于获取金融数据的 Python 库,提供了丰富的金融数据接口,可…

抽空学学go

2024年5月9日11:14:24 学习go 看课8小时转职Golang工程师(如果你想低成本学习Go语言)_哔哩哔哩_bilibili 文档[8小时转职Golang工程师 (yuque.com)]( 1.安装go 2024年5月9日11:27:16 2.安装 vscode go配置环境 vs code配置go开发环境 (zhihu.com) vscode里面配置代理&…

Rust使用HashSet对Vec类型的元素进行去重

在Rust语言中&#xff0c;对Vec类型的元素进行去重&#xff0c;一种常见的方法是使用一个HashSet来帮助我们快速检查元素是否已经存在。以下是使用HashSet对Vec进行去重的示例代码&#xff1a; use std::collections::HashSet;fn main() {let vec_numbers vec![1, 2, 2, 3, 4…

大数据比赛-环境搭建(一)

1、安装VMware Workstation 链接&#xff1a;https://pan.baidu.com/s/1IvSFzpnQFl3svWyCGRtEmg 提取码&#xff1a;ukpo 内有安装包及破解方式&#xff0c;安装教程。 2、下载Ubuntu系统 阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区 (aliyun.com) 点击下载&#xff…

The Sandbox 在利雅得的首次教育活动

2024年4月22日&#xff0c;The Sandbox 与沙特阿拉伯利雅得的 KACST&#xff08;阿卜杜勒阿齐兹国王科技城&#xff09;合作&#xff0c;举办了首次创作者研讨会。此次活动标志着沙特在推动生态系统增长和扩展方面的重要一步。 The Sandbox 的核心使命是通过无编程工具赋能下一…

在做题中学习(54):点名

LCR 173. 点名 - 力扣&#xff08;LeetCode&#xff09; 此题有不同的几种解法&#xff1a; 解法一&#xff1a;暴力枚举 O(n); 解法二&#xff1a;哈希表 把原数组丢入哈希表&#xff0c;遍历哈希表&#xff0c;看看哪个数值为0即可。 O(n)空间O(n)时间 解法三&…

【stm32-2】按键控制LED光敏传感器控制蜂鸣器

1.按键控制LED uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //读取输入数据寄存器某一个端口的输入值 uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx); //读取整个输入数据寄存器 uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDe…

Codeforces Round 944 (Div. 4) A - G

div.4只写部分题解了&#xff0c;都比较基础&#xff0c;数学偏多一点&#xff0c;几乎没有算法&#xff0c;有不懂的欢迎评论区提问&#xff01; A. My First Sorting Problem #include<bits/stdc.h> using namespace std ; typedef long long ll ; const int maxn 2…

vuex核心概念-mutations

目录 一、mutations基本认知 二、mutations的基本使用 三、mutations传参语法 四、注意 五、辅助函数&#xff1a;mapMutations 一、mutations基本认知 目标&#xff1a;明确vuex同样遵循单向数据流&#xff0c;组件中不能直接修改仓库的数据。 通过 strict:true可以开启…

掼蛋—开牌三步走

掼蛋是消遣也是一项脑力活动&#xff0c;除了牌运&#xff0c;还要掌握技巧。和大家一起分享一下掼蛋游戏中的“开牌三步走”技巧。 1、快速理牌 目的&#xff1a;潜力最大化 开局时可以快速查看一下自己的同花顺&#xff0c;优先保留不损害其他炸弹的同花顺&#xff0c;并及时…

使用 sudo apt upgrade 出现的提示

┌────────────────────────────────────────────────┤ Pending kernel upgrade ├────────────────────────────────────────────────┐ │ …