IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 数据结构(图+部分算法) -> 正文阅读

[数据结构与算法]数据结构(图+部分算法)

图是一种数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边。 结点也可以称为顶点。

邻接矩阵

邻接矩阵是表示图形中顶点之间相邻关系的矩阵,对于n个顶点的图而言,矩阵是的row和col表示的是1…n个点。

在这里插入图片描述

邻接表

1)邻接矩阵需要为每个顶点都分配n个边的空间,其实有很多边都是不存在,会造成空间的一定损失.

2)邻接表的实现只关心存在的边,不关心不存在的边。因此没有空间浪费,邻接表由数组+链表组成


在这里插入图片描述

基本用法

package graph;

import java.util.ArrayList;
import java.util.Arrays;

public class Graph {

    private ArrayList<String>vertexList;//存储顶点集合
    private int[][]edges;//存储图对应的邻接矩阵
    private int numOfEdges;//表示边的数目

    //构造器
    public Graph(int n){
        //初始化矩阵和vertexList
        edges=new int[n][n];
        vertexList=new ArrayList<String>(n);
        numOfEdges=0;
    }

    //图中常用的方法
    //返回结点的个数
    public int getNumOfVertex(){
        return vertexList.size();
    }
    //得到边的数目
    public int getNumOfEdges(){
        return numOfEdges;
    }
    //返回结点i(下标)对应的数据
    public String getValueByIndex(int i){
        return vertexList.get(i);
    }

    //返回v1和v2的权值
    public int getWeight(int v1,int v2){
        return edges[v1][v2];
    }

    //显示图对应的矩阵
    public void showGraph(){
        for(int[]link:edges){
            System.out.println(Arrays.toString(link));
        }
    }


    //插入结点
    public void insertVertex(String vertex){
        vertexList.add(vertex);
    }

    //添加边

    /**
     *
     * @param v1 表示点的下标即第几个顶点
     * @param v2
     * @param weight
     */
    public void insertEdge(int v1,int v2,int weight){
        edges[v1][v2]=weight;
        edges[v2][v1]=weight;
        numOfEdges++;
    }

    public static void main(String[] args) {

        int n=5;
        String[] vertexValue ={"A","B","C","D","E"};

        Graph graph = new Graph(n);

        for(String value:vertexValue){
            graph.insertVertex(value);
        }

        //添加边
        graph.insertEdge(0,1,1);
        graph.insertEdge(0,2,1);
        graph.insertEdge(1,2,1);

        graph.showGraph();

    }
}

图的遍历

深度优先

深度优先遍历基本思想

图的深度优先搜索(Depth First Search) 。

1)深度优先遍历,从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点, 可以这样理解:每次都在访问完当前结点后首先访问当前结点的第一个邻接结点

2)我们可以看到,这样的访问策略是优先往纵向挖掘深入,而不是对一个结点的所有邻接结点进行横向访问。

3)显然,深度优先搜索是一个递归的过程

深度优先遍历算法步骤

1)访问初始结点v,并标记结点v为已访问。

2)查找结点v的第一个邻接结点w。

3)若w存在,则继续执行4,如果w不存在,则回到第1步,将从v的下一个结点继续。

4)若w未被访问,对w进行深度优先遍历递归(即把w当做另一个v,然后进行步骤123)。

5)查找结点v的w邻接结点的下一个邻接结点,转到步骤3。

    //定义一个数组boolean[],记录某个结点是否被访问过
    private boolean [] isVisited;

    //构造器
    public Graph(int n){
        //初始化矩阵和vertexList
        edges=new int[n][n];
        vertexList=new ArrayList<String>(n);
        numOfEdges=0;
        isVisited=new boolean[n];
    }

    //得到第一个邻接结点的下标w
    public int getFirstNeighbor(int index){
        for(int j=0;j<vertexList.size();j++){
            if(edges[index][j]>0){
                return j;
            }
        }
        return -1;
    }

    //根据前一个邻接结点的下标来获取下一个邻接结点
    public int getNextNeighbor(int v1,int v2){
        for(int j=v2+1;j<vertexList.size();j++){
            if(edges[v1][j]>0){
                return j;
            }
        }
        return -1;
    }

    //深度优先的遍历算法
    //i 第一次就是 0
    private void dfs(boolean[]isVisited,int i){
        //首先我们访问结点,输出
        System.out.print(getValueByIndex(i)+"->");
        //将给结点设置为已经访问
        isVisited[i]=true;
        //查找结点i的第一个邻接结点w
        int w=getFirstNeighbor(i);
        while(w!=-1){//说明有
            if(!isVisited[w]){
                dfs(isVisited,w);
            }
            //如果w结点已经被访问过
            w=getNextNeighbor(i,w);
        }
    }

    //对dfs 进行一个重载,遍历我们所有的结点,并进行dfs
    public void dfs(){
        //遍历所有的结点,进行dfs[回溯]
        for (int i = 0; i < getNumOfVertex(); i++) {
            if(!isVisited[i]){
                dfs(isVisited,i);
            }
        }
    }

广度优先

广度优先遍历基本思想

图的广度优先搜(Broad First Search) 。

类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来访问这些结点的邻接结点

广度优先遍历算法步骤

1)访问初始结点v并标记结点v为已访问。

2)结点v入队列

3)当队列非空时,继续执行,否则算法结束。

4)出队列,取得队头结点u。

5)查找结点u的第一个邻接结点w。

6)若结点u的邻接结点w不存在,则转到步骤3;否则循环执行以下三个步骤:

6.1 若结点w尚未被访问,则访问结点w并标记为已访问。

6.2 结点w入队列

6.3 查找结点u的继w邻接结点后的下一个邻接结点w,转到步骤6。

//对一个结点进行广度优先遍历的方法

    private void bfs(boolean[] isVisited,int i){
        int u;//表示队列的头结点对应的下标
        int w;//邻接结点w
        //队列,结点访问的顺序
        LinkedList<Integer> queue = new LinkedList<>();
        //访问结点,输出结点信息
        System.out.print(getValueByIndex(i)+"=>");
        //标记为以访问
        isVisited[i]=true;
        //将结点加入队列
        queue.addLast(i);

        while(!queue.isEmpty()){
            //取出队列的头结点
            u=queue.removeFirst();
            //得到第一个邻接结点的下标w
            w=getFirstNeighbor(u);
            while(w!=-1){//找到
                //是否访问过
                if(!isVisited[w]){
                    System.out.print(getValueByIndex(w)+"=>");
                    //标记已经访问
                    isVisited[w]=true;
                    //入队
                    queue.addLast(w);
                }

                //以u前驱结点,找w后面的下一个邻接结点
                w=getNextNeighbor(u,w);//体现出广度优先
            }
        }

    }

    //遍历所有结点,都进行广度优先
    public void bfs(){
        for (int i = 0; i < getNumOfVertex(); i++) {
            if(!isVisited[i]){
                bfs(isVisited,i);
            }
        }
    }

二分查找(非递归)

package binarySearch;

public class BinarySearch {
    public static void main(String[] args) {

        int[] arr = new int[]{1, 3, 8, 10, 11, 67, 100};
        int i = binarySearch(arr, 10);
        System.out.println(i);

        int i1 = binarySearch(0, arr.length, 10, arr);
        System.out.println(i1);

    }

    /**
     * @param arr    待查找的数组,arr是升序排序
     * @param target 需要查找的数
     * @return 返回对应的下标,-1 表示没找到
     */
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        while (left <= right) {//说明继续查找
            int mid = (left + right) / 2;
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] > target) {
                right = mid - 1;//需要向左边查找
            } else {
                left = mid + 1;//需要向右边查找
            }
        }
        return -1;
    }

    public static int binarySearch(int left, int right, int target, int[] arr) {
        int mid = (left + right) / 2;
        if (left > right) return -1;
        if(arr[mid]==target)return mid;

        if (arr[mid] > target)
            return binarySearch(mid + 1, right, target, arr);
        else
            return binarySearch(left, mid - 1, target, arr);
    }

}


分治算法

分治(Divide-and-Conquer§)算法设计模式如下:

if |P|≤n0

  then return(ADHOC(P))

//将P分解为较小的子问题 P1 ,P2 ,…,Pk

for i←1 to k

do yi ← Divide-and-Conquer(Pi)  递归解决Pi

TMERGE(y1,y2,,yk)  合并子问题

return(T)

其中|P|表示问题P的规模;n0为一阈值,表示当问题P的规模不超过n0时,问题已容易直接解出,不必再继续分解。ADHOC§是该分治法中的基本子算法,用于直接解小规模的问题P。因此,当P的规模不超过n0时直接用算法ADHOC§求解。算法MERGE(y1,y2,…,yk)是该分治法中的合并子算法,用于将P的子问题P1 ,P2 ,…,Pk的相应的解y1,y2,…,yk合并为P的解。

分治算法的最佳实现——汉诺塔

package dac;

public class HanoiTower {
    public static void main(String[] args) {
        hanoiTower(5,'A','B','C');
    }

    //汉诺塔的移动方法
    //使用分治算法
    public static void hanoiTower(int num,char a,char b,char c){
        //如果只有一个盘
        if(num==1){
            System.out.println("第一个盘从 "+a+"->"+c);
        }else{
            //如果我们有n>=2情况,我们总是可以看作是两个盘 1、最下面的一个盘2、上面的所有盘
            //1、先把 最上面的所有盘A->B,移动过程中会使用到c
            hanoiTower(num-1,a,c,b);
            //2、把最下边的盘A->C
            System.out.println("第"+num+"个盘从 "+a+"->"+c);
            //3、把B塔的所有盘 从B->C ,移动过程会使用到a塔
            hanoiTower(num-1,b,a,c);
        }
    }


}

动态规划

动态规划算法介绍

1)动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法

2)动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。

3)与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。 ( 即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解 )

4)动态规划可以通过填表的方式来逐步推进,得到最优解.

最佳实践——背包问题

package dynamic;

public class KnapsackProblem {
    public static void main(String[] args) {
        int[]w={1,4,3};//物品的重量
        int[]val={1500,3000,2000};//物品的价值

        int m=4;//背包的容量
        int n=val.length;//物品的个数

        //创建二维数组
        //v[i][j] 表示在前i个物品中能够装入容量为j的背包中的最大价值
        int [][]v=new int[n+1][m+1];
        //为了记录放入商品的情况,定义一个二维数组
        int [][]path =new int[n+1][m+1];

        //初始化第一行和第一列,这里在本程序中,可以不去处理,因为默认就是0
        for (int i = 0; i < v.length; i++) {
            v[i][0]=0;//将第一列设置为0
        }
        for (int i = 0; i < v[0].length; i++) {
            v[0][i]=0;//将第一行设置为0
        }

        //更据得到的公式来动态规划处理
        for (int i = 1; i < v.length; i++) {//不处理第一行 ,i是从1开始
            for(int j=1;j<v[0].length;j++){//不处理第一列 j是从1开始
                if(w[i-1]>j){
                    v[i][j]=v[i-1][j];
                }else{
                    //v[i][j]=Math.max(v[i-1][j],val[i-1]+v[i-1][j-w[i-1]]);
                    if(v[i-1][j]<val[i-1]+v[i-1][j-w[i-1]]){
                        v[i][j]=val[i-1]+v[i-1][j-w[i-1]];
                        //把当前的情况记录到path
                        path[i][j]=1;
                    }else{
                        v[i][j]=v[i-1][j];
                    }
                }
            }
        }

        //输出一下v 看看目前的情况
        for (int i = 0; i < v.length; i++) {
            for (int j=0;j<v[i].length;j++){
                System.out.print(v[i][j]+" ");
            }
            System.out.println();
        }

        System.out.println("======================");
        //输出最后放入的哪些商品

        int i=path.length-1;
        int j=path[0].length-1;
        while(i>0&&j>0){//从path的最后开始找
            if(path[i][j]==1){
                System.out.println("第"+i+"个商品放入到背包");
                j-=w[i-1];
            }
            i--;
        }
    }
}

在这里插入图片描述

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-12-14 16:12:59  更:2021-12-14 16:13:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 16:42:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码