一篇文章轻松掌握java图实现

news2024/11/24 22:30:47

图的基本概念:这里就不予介绍了,这里主要是讲图的代码实现

荔枝目录:

  • 1.图的存储结构
    • 1.1邻接矩阵
    • 1.2邻接表
  • 2.图的遍历
    • 2.1广度优先
    • 2.2深度优先
  • 3.最小生成树
    • 3.1Kruskal算法(全局)
    • 3.2Prim算法(局部)
  • 4.最短路径
    • 4.1单源最短路径算法1--Dijkstra算法
    • 4.2单源最短路径算法2--Bellman-Ford算法

1.图的存储结构

1.1邻接矩阵

邻接矩阵是图常见的存储结构
❤️用邻接矩阵存储图的优点是能够快速知道两个顶点是否连通
💕缺陷是如果顶点比较多,边比较少时,矩阵中存储了大量的0成为系数矩阵,比较浪费空间,并且要求两个节点之间的路径不是很好求。

具体做法是通过一个二维数组来记录每一条边或边的权值

无向图:
在这里插入图片描述
可以发现是对称的。
有向图:
在这里插入图片描述

具体代码实现呢,因为我们是默认没有权值的,如果有需要把这个二维数组的所有元素初始化为最大值(当∞使用)

//顶点集
    private char[] arrayV;
    //边集
    private int[][] arrayE;
    //是否是有向图
    private boolean isDirect;
    //存储顶点下标
    private static Map<Character,Integer> indexV=new HashMap<>();

    public MyGraph(int size, boolean isDirect) {
        arrayV=new char[size];
        arrayE=new int[size][size];
        this.isDirect=isDirect;

        /*for(int i=0;i<arrayE.length;i++){//有权值加上这一块代码
            Arrays.fill(arrayE[i],Integer.MAX_VALUE);
        }*/
    }

    /**
     * 初始化顶点集
     * @param arrV
     */
    public void initArrayV(char[] arrV){
        for (int i = 0; i < arrV.length; i++) {
            this.arrayV[i]=arrV[i];
            indexV.put(arrV[i],i);
        }
    }

    /**
     * 添加边和对应权重
     * @param v1
     * @param v2
     * @param weight
     */
    public void addEdge(char v1,char v2,int weight){
        //先找出两个顶点在顶点集的位置--搞个哈希表方便
        int v1Index=indexV.get(v1);
        int v2Index=indexV.get(v2);
        this.arrayE[v1Index][v2Index]=weight;
        //如果是无向图
        if(!isDirect){
            this.arrayE[v2Index][v1Index]=weight;
        }
    }

    /**
     * 把边集的二维数组打印出来
     */
    private void printGraph() {
        for(int i=0;i< arrayE.length;i++){
            for (int j = 0; j < arrayE[i].length; j++) {
                System.out.print(arrayE[i][j]+" ");
            }
            System.out.println();
        }
    }

测试用例:

public static void main(String[] args) {
        MyGraph graph = new MyGraph(4,false);
        char[] array = {'A','B','C','D'};
        graph.initArrayV(array);
        graph.addEdge('A','B',1);
        graph.addEdge('A','D',1);
        graph.addEdge('B','A',1);
        graph.addEdge('B','C',1);
        graph.addEdge('C','B',1);
        graph.addEdge('C','D',1);
        graph.addEdge('D','A',1);
        graph.addEdge('D','C',1);
        graph.printGraph();
    }

结果:
在这里插入图片描述

1.2邻接表

这里通过结点的形式将该边的起点和终点记录,接到对应的链表上
结点结构

static class Node{
        public int src;//起始点下标
        public int dest;//终止点下标
        public int weight;//权值
        public Node next;

        public Node(int src, int dest, int weight) {
            this.src = src;
            this.dest = dest;
            this.weight = weight;
        }
    }

图示:
在这里插入图片描述

当然也有使用入边和出边分开保存的,这里借用图片:
在这里插入图片描述

具体代码:

 //邻接结点
    static class Node{
        public int src;//起始点下标
        public int dest;//终止点下标
        public int weight;//权值
        public Node next;

        public Node(int src, int dest, int weight) {
            this.src = src;
            this.dest = dest;
            this.weight = weight;
        }
    }

    //顶点集
    private char[] arrayV;
    //是否是有向图
    private boolean isDirect;
    //存储顶点下标
    private static Map<Character,Integer> indexV=new HashMap<>();
    //存储每个顶点的边结点链表
    private List<Node> list;

    public MyGraph2(int size,boolean isDirect){
        this.arrayV=new char[size];
        this.isDirect=isDirect;
        list=new ArrayList<>(size);//给个初始大小并且置为null,方便后面判断
        for (int i = 0; i < size; i++) {
            list.add(null);
        }
    }

    /**
     * 初始化顶点集
     * @param arrV
     */
    public void initArrayV(char[] arrV){
        for (int i = 0; i < arrV.length; i++) {
            this.arrayV[i]=arrV[i];
            indexV.put(arrV[i],i);
        }
    }

    /**
     * 插入边结点到链表中
     * @param v1
     * @param v2
     * @param weight
     */
    public void addEdge(char v1,char v2,int weight){
        int src=indexV.get(v1);
        int dest=indexV.get(v2);
        addEdgeChild(src,dest,weight);
        //如果是无向图,需要反着再添加一次
        if(!isDirect){
            addEdgeChild(dest,src,weight);
        }
    }

    private void addEdgeChild(int src,int dest,int weight) {
        Node node=new Node(src,dest,weight);
        //头插入法
        Node cur= list.get(src);//获取src的链表头位置
        if(cur==null){
            list.set(src,node);
        }else{
            Node tmp=list.get(src);
            node.next=tmp;
            list.set(src,node);
        }
    }

    public void printGraph() {
        for (int i = 0; i < arrayV.length; i++) {
            System.out.print(arrayV[i]+"-> ");
            Node cur = list.get(i);
            while (cur != null) {
                System.out.print(cur.dest+"-> ");
                cur = cur.next;
            }
            System.out.println("null");
        }
    }

测试用例:

public static void main(String[] args) {
        MyGraph2 graph = new MyGraph2(3,true);
        char[] array = {'A','B','C'};
        graph.initArrayV(array);
        graph.addEdge('A','B',1);
        graph.addEdge('B','A',1);
        graph.addEdge('B','C',1);
        graph.printGraph();
    }

结果:
在这里插入图片描述

2.图的遍历

2.1广度优先

核心是确定首位置点,遍历时先遍历与该点最近的点,再遍历第二近的点,直到遍历完成。
在这里插入图片描述
这个看起来和二叉树的层序遍历很像,没错,我们在层序遍历二叉树的时候利用了一个队列,那这里也需要一个队列。不过只一个队列还不行,还需要一个数组来记录某个点是否已经遍历过。因为点与点直接是互连的,会出现一个点重复进入队列的情况。

具体代码实现:

public void bfs(char start){
        boolean isAppear[]=new boolean[arrayV.length];//默认为false
        Deque<Integer> deque=new ArrayDeque();//Integer为记录点的下标
        deque.offer(indexV.get(start));//添加第一个点并记录该点已经出现
        isAppear[indexV.get(start)]=true;
        while(!deque.isEmpty()){
            int top=deque.poll();//弹出第一个点
            System.out.print(arrayV[top]+":"+top+"->");
            //加入下一个点
            for (int i = 0; i < arrayE.length; i++) {
                if(arrayE[top][i]!=0&&isAppear[i]==false){
                    //添加点并先记录该点已经出现
                    isAppear[i]=true;
                    deque.offer(i);
                }
            }
        }
    }

这里需要再加入的时候就设置已经出现,如果在弹出的时候才设置就会导致多打印最后一个点。

2.2深度优先

和二叉树的前序遍历差不多,都是一条路先走到头再回溯换路
但是这里也需要判断当前点是否已经遍历过了,防止重复遍历
在这里插入图片描述
具体代码如下:

 public void dfs(char start){
        boolean isAppear[]=new boolean[arrayV.length];//默认为false
        int index=indexV.get(start);
        dfsChild(index,isAppear);
    }

    private void dfsChild(int cur, boolean[] isAppear) {
        isAppear[cur]=true;
        System.out.print(arrayV[cur]+"->");
        for (int i = 0; i < arrayE.length; i++) {
            if(arrayE[cur][i]!=0&&isAppear[i]==false){
                dfsChild(i,isAppear);
            }
        }
    }

3.最小生成树

基本概念

一个连通图的最小连通子图称作该图的生成树。 连通图中的每一棵生成树,都是原图的一个极大无环子图,即:从其中删去任何一条边,生成树 就不在连通;反之,在其中引入任何一条新边,都会形成一条回路。
若连通图由n个顶点组成,则其生成树必含n个顶点和n-1条边。因此构造最小生成树的准则有三 条:

  1. 只能使用图中的边来构造最小生成树
  2. 只能使用恰好n-1条边来连接图中的n个顶点
  3. 选用的n-1条边不能构成回路 构造最小生成树的方法:Kruskal算法和Prim算法。这两个算法都采用了逐步求解的贪心策略。

贪心算法:是指在问题求解时,总是做出当前看起来最好的选择。也就是说贪心算法做出的不是整体
最优的的选择,而是某种意义上的局部最优解。贪心算法不是对所有的问题都能得到整体最优解

3.1Kruskal算法(全局)

克鲁斯卡尔算法的核心是,每次在边集取一条权重最小的边加入生成树。这个算法可能会产生回路问题,所以每次取边都需要判断取该条边会不会使生成树有回路。
图示:
在这里插入图片描述
从这个图我们可以提取到我们代码的核心:
1.怎么每次取一条权值最小的边?
2.如何判断回路?
第一个问题可以采用优先级队列(小根堆)的方式来处理
第二个问题可以采用并查集的方式来处理,把已经连上的点看成一个集合,比如在图f中如果想选择左下的7,我们就到并查集里查找i和h点在不在同一个集合中在就不能选。
这里图的存储结构采用邻接矩阵

代码分析:

/**
     *
     * @return 返回最小生成树的权值
     */
    public int kruskal(MyGraph minTree){
        int totalWeight=0;
        //优先级队列存储边--小根堆
        PriorityQueue<Edge> minHead=new PriorityQueue<>(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return o1.weight-o2.weight;//自定义类型要传比较器重写compare方法确定比较条件
            }
        });
        //将所有边加入优先集队列
        for (int i = 0; i < arrayE.length; i++) {
            for (int j = 0; j < arrayE[i].length; j++) {
                if(arrayE[i][j]!=Integer.MAX_VALUE){
                    Edge edge=new Edge(i,j,arrayE[i][j]);
                    minHead.add(edge);
                }
            }
        }
        //创建一个并查集来方便检查有没有回路
        UnionFindSet loops=new UnionFindSet(arrayV.length);
        //n个结点需要n-1条边
        int size=0;
        int n=arrayV.length;
        while (size<n-1&&!minHead.isEmpty()){
            //取出边
            Edge top=minHead.poll();
            //判断这条边的加入会不会产生回路
            if(loops.isSameSet(top.srcIndex,top.destIndex)==false){
                //加入到生成树中
                minTree.addEdgeUserIndex(top.srcIndex,top.destIndex,top.weight);
                //将两点加入同一个集合
                loops.union(top.srcIndex,top.destIndex);
                //累加权值
                totalWeight+=top.weight;
                size++;
            }
        }
        //判断边不够的情况---做不了生成树--也就是不是连通图
        if(minHead.isEmpty()){
            return -1;
        }
        return totalWeight;
    }

测试代码:

public static void testGraphMinTree() {
        String str = "abcdefghi";
        char[] array = str.toCharArray();
        MyGraph g = new MyGraph(str.length(), false);
        g.initArrayV(array);
        g.addEdge('a', 'b', 4);
        g.addEdge('a', 'h', 8);
        g.addEdge('b', 'c', 8);
        g.addEdge('b', 'h', 11);
        g.addEdge('c', 'i', 2);
        g.addEdge('c', 'f', 4);
        g.addEdge('c', 'd', 7);
        g.addEdge('d', 'f', 14);
        g.addEdge('d', 'e', 9);
        g.addEdge('e', 'f', 10);
        g.addEdge('f', 'g', 2);
        g.addEdge('g', 'h', 1);
        g.addEdge('g', 'i', 6);
        g.addEdge('h', 'i', 7);
        MyGraph kminTree = new MyGraph(str.length(), false);
        System.out.println(g.kruskal(kminTree));
        kminTree.printGraph();
    }

结果:
在这里插入图片描述

3.2Prim算法(局部)

和克鲁斯卡尔算法不同的是,普利姆算法采用局部贪心的思想,从任意点出发,将已经加入生成树的点和未加入的点分成两个集合X,Y,在连接这两个集合中任意两点的边里选择一条权值最小的。选完之后,将Y集合的那个点加入X集合中。这个方法天生就是没有回路的,因为取边的时候Y的点肯定不和X的点在同一个集合。
图示:
在这里插入图片描述
这里在代码中主要问题有:
1.X和Y的集合怎么表示?
2.如何在两个集合中任意两点的边里选择一条权值最小的边?
第一个问题可以通过哈希表来表示集合
第二个问题依然采用优先级队列,每往X集合加入一个点,我们就把这个点占有的所有边加入优先级队列,每次取边时以Y集合的点为起点,判断取的这条边的另一个点即终点在不在X集合里。因为我们加入边的时候不是所有边都能取到
就像图8一样,那个7虽然小,但取不得。

代码分析:

/**
     *
     * @param minTree
     * @return 最小生成树的权值
     */
    public int prim(MyGraph minTree){
        int totalWeight=0;
        //优先级队列存储边--小根堆
        PriorityQueue<Edge> minHead=new PriorityQueue<>(new Comparator<Edge>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return o1.weight-o2.weight;//自定义类型要传比较器重写compare方法确定比较条件
            }
        });
        //创建X,Y集合  存下标
        Set<Integer> X=new HashSet<>();
        Set<Integer> Y=new HashSet<>();
        int start=0;//任意点均可
        X.add(start);
        for (int i = 0; i < arrayV.length; i++) {
            if(i!=start)
            Y.add(i);
        }
        //初始化队列--根据X的内容来初始化
        for (int j = 0; j < arrayE[start].length; j++) {
            if(arrayE[start][j]!=Integer.MAX_VALUE){
                Edge edge=new Edge(start,j,arrayE[start][j]);
                minHead.add(edge);
            }
        }
        int size=0;
        int n=arrayV.length;
        while(size<n-1&&!minHead.isEmpty()){
            Edge top=minHead.poll();
            //取的这条边的另一个点即终点在不在X集合里
            if(!X.contains(top.destIndex)){
                //加入生成树
                minTree.addEdgeUserIndex(top.srcIndex,top.destIndex,top.weight);
                //将来自Y的这个点destIndex加入X中,Y要删除它,并更新边的队列
                X.add(top.destIndex);
                Y.remove(top.destIndex);
                //每选一条边 就打印一条语句 测试一下
                System.out.println(arrayV[top.srcIndex] + "-> " + arrayV[top.destIndex] + " : " + top.weight);
                for (int j = 0; j < arrayE[top.destIndex].length; j++) {
                    if(arrayE[top.destIndex][j]!=Integer.MAX_VALUE&&!X.contains(j)){//加入边的时候不能加入了a->b还加入b->a
                        Edge edge=new Edge(top.destIndex,j,arrayE[top.destIndex][j]);
                        minHead.add(edge);
                    }
                }
                totalWeight+=top.weight;
                size++;
            }
        }
        //判断边不够的情况---做不了生成树--也就是不是连通图
        if(minHead.isEmpty()){
            return -1;
        }
        return totalWeight;
    }

测试用例:

public static void test2GraphMinTree() {
        String str = "abcdefghi";
        char[] array = str.toCharArray();
        MyGraph g = new MyGraph(str.length(), false);
        g.initArrayV(array);
        g.addEdge('a', 'b', 4);
        g.addEdge('a', 'h', 8);
        g.addEdge('b', 'c', 8);
        g.addEdge('b', 'h', 11);
        g.addEdge('c', 'i', 2);
        g.addEdge('c', 'f', 4);
        g.addEdge('c', 'd', 7);
        g.addEdge('d', 'f', 14);
        g.addEdge('d', 'e', 9);
        g.addEdge('e', 'f', 10);
        g.addEdge('f', 'g', 2);
        g.addEdge('g', 'h', 1);
        g.addEdge('g', 'i', 6);
        g.addEdge('h', 'i', 7);
        MyGraph primTree = new MyGraph(str.length(), false);
        int total=g.prim(primTree);
        System.out.println(total);
        primTree.printGraph();
    }

结果:
在这里插入图片描述

4.最短路径

最短路径问题:从在带权图的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小

4.1单源最短路径算法1–Dijkstra算法

注:单源就是给你一个起点让你求和其他点的最短路径,多源就是任意两点的最短路径。

迪杰斯特拉算法思想:
针对一个带权有向图G,将所有结点分为两组S和Q,S是已经确定最短路径的结点集合,在初始时为空(初始时就可以将源节点s放入,毕竟源节点到自己的代价是0),Q 为其余未确定最短路径的结点集合每次从Q中找出一个起点到该结点代价最小的结点u ,将u 从Q 中移出,并放入S 中,对u 的每一个相邻结点v 进行松弛操作松弛即对每一个相邻结点v ,判断源节点s到结点u 的代价与u 到v 的代价之和是否比原来s 到v 的代价更小,若代价比原来小则要将s 到v 的代价更新为s 到u 与u 到v 的代价之和,否则维持原样。如此一直循环直至集合Q 为空,即所有节点都已经查找过一遍并确定了最短路径,至于一些起点到达不了的结点在算法循环后其代价仍为初始设定的值,不发生变化。Dijkstra算法每次都是选择V-S中最小的路径节点来进行更新,并加入S中,所以该算法使用的是贪心策略。
在这里插入图片描述在这里插入图片描述
缺点:不支持图中带负权路径,如果带有负权路径,则可能会找不到一些路径的最短路
径。例如:就找不到a->c->b这条最短路径
在这里插入图片描述

代码分析
主要问题:1.如何表示该点已经固定
2.如何获取我们的最短路径(怎么走的)
解答:
1.可通过boolean数组表示
2.通过pPath数组下标对应值来表示,例如:
在这里插入图片描述
代码:

public void dijkstra(char vSrc,int[] dist,int[] pPath){
        //获取起点下标
        int srcIndex=indexV.get(vSrc);
        //初始化dist数组--初始全无穷,起点下标设成0
        Arrays.fill(dist,Integer.MAX_VALUE);
        dist[srcIndex]=0;
        //初始化pPath数组--初始全-1,起点的下标设为自己的下标
        Arrays.fill(pPath,-1);
        pPath[srcIndex]=srcIndex;
        //表示某点已经固定--初始全false
        boolean[] isSure=new boolean[dist.length];
        int n=dist.length;
        for (int k = 0; k < n; k++) {
            //要松弛的结点
            int u=srcIndex;
            //每次循环确定一个最小值固定,并确定下一次要松弛的结点
            int min=Integer.MAX_VALUE;
            for (int i = 0; i < n; i++) {
                if(dist[i]!=Integer.MAX_VALUE&&isSure[i]==false&&dist[i]<min){
                    min=dist[i];
                    u=i;
                }
            }
            //固定找到的最小值
            isSure[u]=true;
            //对u点进行松弛操作
            for (int v = 0; v < n; v++) {
                /*
                判断源节点s到结点u 的代价与u 到v 的代价之和是否比原来s 到v 的代价更小,
                若代价比原来小则要将s 到v 的代价更新为s 到u 与u 到v 的代价之和,否则维持原样
                 */
                //v顶点未固定 并且 u->v 是有权值的 并且 加起来的权值 < dist[v]
                if(isSure[v]==false&&arrayE[u][v]!=Integer.MAX_VALUE&&arrayE[u][v]+dist[u]<dist[v]){
                    dist[v]=arrayE[u][v]+dist[u];//修改最小值
                    pPath[v]=u;//修改路径
                }
            }
        }
    }

4.2单源最短路径算法2–Bellman-Ford算法

迪杰斯特拉算法虽好,但是解决不了带负权的图,那么要处理有负权图的最短路径,就要用到贝尔曼-福特算法。
它的优点是可以解决有负权边的单源最短路径问题,而且可以用来判断是否有负权回路。它也有明显的缺点,它的时间复杂度 O(N*E)(N是点数,E是边数)普遍是要高于Dijkstra算法O(N²)的。像这里如果我们使用邻接矩阵实现,那么遍历所有边的数量的时间复杂度就是O(N^3),这里也可以看出来贝尔曼-福特算法就是一种暴力求解更新。
注意负权不能有负权回路!不然永远都求不到最短。例如:
在这里插入图片描述
接下来我们来研究一下这个算法是如果处理的吧!
这个算法就是对N个点每个点都进行N次松弛操作,因为后一次的负权路径可能会影响到前面的最短路径,例如:我们按a,b,c,d依次进行一次松弛操作可以发现
在这里插入图片描述
所以,单单一次松弛是不能够搞定的,每个结点所以要进行n次松弛操作才能保证我们的结果是正确的。

代码:
代码和迪杰斯特拉算法的代码很相似,但不再需要判断该点是否固定。

/**
     *
     * @param vSrc
     * @param dist
     * @param pPath
     * @return 是否存在负权回路
     */
    public boolean bellmanFord(char vSrc,int[] dist,int[] pPath) {
        //获取起点下标
        int srcIndex=indexV.get(vSrc);
        //初始化dist数组--初始全无穷,起点下标设成0
        Arrays.fill(dist,Integer.MAX_VALUE);
        dist[srcIndex]=0;
        //初始化pPath数组--初始全-1,起点的下标设为自己的下标
        Arrays.fill(pPath,-1);
        int n=arrayV.length;
        //循环n次松弛结点
        for (int k = 0; k < n; k++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if(arrayE[i][j]!=Integer.MAX_VALUE&&dist[i]+arrayE[i][j]<dist[j]){
                        dist[j]=dist[i]+arrayE[i][j];
                        pPath[j]=i;
                    }
                }
            }
        }
        //判断是否存在负权回路,再松弛一遍还能修改就代表存在
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if(arrayE[i][j]!=Integer.MAX_VALUE&&dist[i]+arrayE[i][j]<dist[j]){
                    return false;
                }
            }
        }
        return true;
    }

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

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

相关文章

[UE][UE5]在UE中画一个线框球,网格连接画球,高阶画球并操控

[UE][UE5]在UE中画一个线框球&#xff0c;网格连接画球&#xff0c;高阶画球并操控1.材质法2.绘制调试球体3.网格连接4.高阶画球并操控1.材质法 缺点&#xff1a;是实体的&#xff0c;只能欺骗视觉&#xff0c;实际还是一个实体体积球&#xff0c;往里放东西显示不出来放进去的…

Python中ArcPy基于矢量要素批量将栅格影像切割为多个小部分

本文介绍基于Python中ArcPy模块&#xff0c;基于具有多个面要素的要素类&#xff0c;批量分割大量栅格图像的方法。 首先明确一下我们的需求。现在需要基于一个面要素类&#xff0c;对一个栅格遥感影像加以分割&#xff1b;如下图所示。这个面要素类中有3个部分&#xff0c;我们…

MMdetection框架速成系列 第02部分:整体算法流程+模型搭建流程+detection训练与测试核心组件+训练部分与测试部分的核心算法

MMdetection框架速成系列 第02部分1 MMDetection是什么1.1 模型分类2. 整体算法流程3 detection训练核心组件3.1 Backbone3.2 Neck3.3 Head3.4 Enhance3.5 BBox Assigner3.9 BBox Sampler3.10 BBox Encoder3.11 Loss3.12 Training tricks4 detection测试核心组件4.1 BBox Decod…

python源码打包exe、exe反编译

一、python3打包为exe文件 这里有个hello.py文件 step1&#xff1a;安装pyinstaller包 pip install pyinstaller step2&#xff1a;在cmd中进入hello.py文件所在路径。可以直接在hello.py文件路径下直接进入cmd step3&#xff1a;打包生成exe文件&#xff0c;使用如下命令&…

Linux网络协议之IP协议(网络层)

Linux网络协议之IP协议(网络层) 文章目录Linux网络协议之IP协议(网络层)1.IP协议基本概念2.IPV4协议格式3.分片与组装4.IP网段划分4.1 IP地址组成4.2 IP地址分类4.3 特殊的IP地址4.4 IP地址的数量限制4.5 私网IP地址与公网IP地址5.对路由的了解1.IP协议基本概念 IP协议全称为“…

第四章 vi和vim 编辑器-[实操篇]

一&#xff1a;vi 和 vim 的基本介绍 所有的 Linux 系统都会内建vi文本编辑器。 Vim 具有程序编辑的能力&#xff0c;可以看做是 Vi的增强版本&#xff0c;可以主动的以字体颜色辨别语法的正确性&#xff0c;方便程序设计。代码补完&#xff0c;编译及错误跳转等方便编程的功…

Linux安装【入门学习适用】

Linux安装安装1、安装VMware激活码&#xff1a;ZF3R0-FHED2-M80TY-8QYGC-NPKYF2、安装CentOS3、FinalShell安装4、FinalShell的使用安装 1、安装VMware VMware-workstation-full-16.2.4无脑下一步即可 激活码&#xff1a;ZF3R0-FHED2-M80TY-8QYGC-NPKYF 2、安装CentOS 1、…

TensorFlow笔记之多元线性回归

文章目录前言一、数据处理二、TensorFlow1.x1.定义模型2.训练模型3.结果可视化4.模型预测5.TensorBoard可视化三、TensorFlow2.x1.定义模型2.训练模型3.结果可视化4.模型预测总结前言 记录使用TensorFlow1.x和TensorFlow2.x完成多元线性回归的过程。 一、数据处理 在此使用波…

dll修复工具哪个比较好?好的修复工具怎么选择

最近有小伙伴咨询小编&#xff0c;问dll修复工具的选择&#xff0c;因为他的电脑经常出现dll缺失&#xff0c;一缺失就打开不了各种软件程序&#xff0c;非常的让他烦恼&#xff0c;所以今天小编就来给大家详细的说说dll修复工具哪个比较好&#xff1f;要怎么去选择。 一.什么…

36 氪发布《研发项目管理软件应用指南》,ONES 入选典型厂商案例

近日&#xff0c;36氪企服点评发布了《研发项目管理软件应用指南》&#xff08;下称「指南」&#xff09;。36氪企服点评致力于帮助每个需求企业服务的人做出正确的决策&#xff0c;携手每个企服行业者为大众提供更高的价值与服务。在该指南中&#xff0c;36氪企服点评综合了海…

大数据系列——ClickHouse表引擎与分布式查询

目录 一、ClickHouse的表引擎 1、MergeTree的创建方式与存储结构 2、ReplacingMergeTree 二、数据分片与分布式查询 三、Clickhouse-ETL常见业务使用 一、ClickHouse的表引擎 表引擎体系&#xff0c;包括合并树、外部存储、内存、文件、接口和其他6大类20多种表引擎。而在…

全流量回溯分析为您解决应用性能问题(一)

前言 信息中心老师反应&#xff0c;用户反馈办公系统有访问慢的情况&#xff0c;需要通过流量分析系统来了解系统的运行情况&#xff0c;此报告专门针对系统的性能数据做了分析。 信息中心已部署NetInside流量分析系统&#xff0c;使用流量分析系统提供实时和历史原始流量&am…

【网络安全篇】浅谈web应用程序的安全风险

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;浅谈web应用程序的安全风险 ✅创作者&#xff1a;贤鱼 ⏰预计时间&#xff1a;25分钟 &#x1f389;个人主页&#xff1a;贤鱼的个人主页 &#x1f525;专栏系列&#xff1a;网络安全 &#x1f341;贤鱼的个人社区&#xf…

使用FastJson进行驼峰下划线相互转换写法及误区

PropertyNamingStrategy 有四种序列化方式。 CamelCase策略&#xff0c;Java对象属性&#xff1a;personId&#xff0c;序列化后属性&#xff1a;persionId – 实际只改了首字母 大写变小写 PascalCase策略&#xff0c;Java对象属性&#xff1a;personId&#xff0c;序列化后属…

说透IO多路复用模型

在说IO多路复用模型之前&#xff0c;我们先来大致了解下Linux文件系统。在Linux系统中&#xff0c;不论是你的鼠标&#xff0c;键盘&#xff0c;还是打印机&#xff0c;甚至于连接到本机的socket client端&#xff0c;都是以文件描述符的形式存在于系统中&#xff0c;诸如此类&…

springboot项目打war包 部署到Tomcat

1、SpringBoot项目Pom文件修改 <!-- 打war包配置 --><packaging>war</packaging><!-- 打war包配置 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-war-plugin</artifactId><version>…

英美TOP名校对IB的申请要求汇总

英美TOP名校对IB的申请要求汇总 英国大学剑桥大学 IB要求 40-42分&#xff08;满分45&#xff09;&#xff0c;HL要求为776分。 学校可能要求申请者的某些科目成绩为7&#xff0c;视不同专业和学院而定。 对任何要求数学的专业&#xff0c;申请者需选Analysis and Approaches&a…

Google SEM和谷歌SEO的区别

很多人对Google SEM和Google SEO概念很模糊。米贸搜整理如下。看图: Google SEM和SEO的关系 在上图中&#xff0c; 最上面的部分属于Google SEM&#xff0c;即Google Ads广告推广&#xff0c;是一种按效果付费的广告&#xff1b; 底层属于Google SEO&#xff0c;也就是Googl…

前端基础_配置IIS服务器

配置IIS服务器 在应用程序完全离线之前&#xff0c;还需要正确地提供清单文件。清单文件必须有扩展名.manifest和正确的mime-type。 如果使用Apache这样的通用Web服务器&#xff0c;需要找到在AppServ/Apache2.2/conf文件夹中的mine.types文件并向其添加“text/cache-manifes…

React学习02-React面向组件编程

React 开发者工具 推荐使用Chrome或Edge浏览器&#xff0c;安装React Developer Tools&#xff08;Facebook出品&#xff09;。 安装完成后&#xff0c;访问使用React编写的页面时&#xff0c;图标会高亮&#xff08;开发环境为红色有debug标识&#xff0c;生产环境为蓝色&…