概述
图的存储形式中链表是通过数组加LinkedList(不一定是LinkedList,可以自己写链,也可以选择其他的集合数据结构),邻接表采用的是二维数组的结构。
链表存储形式的相关实现
数据存储结构与基础操作
初始化数据存储结构
private static LinkedList[] arrayLists;
public static void createDatastruct(int count){
/**
*@description:根据图中点的数量初始化一个链表形式的数据结构
*/
arrayLists = new LinkedList[count];
for (int i = 0; i < count; i++) {
arrayLists[i] = new LinkedList<Integer>();
}
}
添加边信息:
private static Comparator arrayListsSort = new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
if(o1.intValue()>o2.intValue()){
return 1;
}else if(o1.intValue()<o2.intValue()){
return -1;
}else{
return 0;
}
}
};
public static void insertEdge(int v1,int v2){
/**
*@description:添加边并且保持每个节点关联节点的顺序性
*/
arrayLists[v1].add(v2);
arrayLists[v1].sort(arrayListsSort);
arrayLists[v2].add(v1);
arrayLists[v2].sort(arrayListsSort);
}
展示数据结构:
public static void showDatas(LinkedList<Integer>[] linkedLists){
/**
*@description: 输出图链表形式的存储,以及将链表型的数据转换为邻接表的形式并输出
*@params: 图链表形式的存储
*/
System.out.println("图的链接表输出形式:");
for (int i = 0; i < arrayLists.length; i++) {
System.out.printf(i+" ");
for (Object temp : arrayLists[i]){
System.out.printf("==>"+temp.toString());
}
System.out.println();
}
//链表型的数据转换为邻接表
int[][] linkedListsToArrays = new int[linkedLists.length][linkedLists.length];
for (int i = 0; i < arrayLists.length; i++) {
for (Object temp : arrayLists[i]){
linkedListsToArrays[i][(Integer)temp] = 1;
}
}
System.out.println("图的邻接表输出形式:");
for (int i = 0; i < linkedListsToArrays.length; i++) {
System.out.println(Arrays.toString(linkedListsToArrays[i]));
}
}
链表形式无向图实现深度优先遍历:
相信这里一定是大家懵逼的地方,这里也是我懵逼的地方但是好赖自己磨了出来,大概的实现思路是这样的:
这个数据结构,设置了自动排序,每个节点添加一个新的连接那么就会在linkedLists[i]的这个集合中做一个优先级的排序,我们没访问一个节点都会将这个节点做一个输出,并且将这个节点与其他节点相关联的关系进行一个删除,这也是delectCheckedNodeEvery()函数的作用。每一次的回溯就是这条路无路可走的时候,这时候我们还是要删除掉这个已经访问过了的节点的相关节点,并且通过return这个关键字进行一个回溯。
public static void delectCheckedNodeEvery(LinkedList<Integer>[] linkedLists,int startNode){
for(LinkedList linkedList:linkedLists){
for(Iterator<Integer> linkedListIterator = linkedList.iterator(); linkedListIterator.hasNext();){
if(linkedListIterator.next().equals(startNode)){
linkedListIterator.remove();
}
}
}
}
public static void dfs(LinkedList<Integer>[] linkedLists,int startNode){
/**
*@description: 对链表形式数据的深度优先排序;当进入当前的这个方法时候说明上一个startNode已经执行完了
*/
System.out.printf(startNode+"->");
while (linkedLists[startNode].size()>0){
delectCheckedNodeEvery(linkedLists,startNode);
dfs(linkedLists,linkedLists[startNode].removeFirst());
}
delectCheckedNodeEvery(linkedLists,startNode);
return;
}
链表形式无向图实现广度优先遍历:
不管是链表形式的还是连接图形式的数据结构,广度优先的遍历都是比较简单的,简单的说 就是我们是一层一层的数据遍历,bfs(LinkedList<Integer>[] linkedLists,int startNode)中的startNode相当于是根节点,对他的的处理我们调用了一次checkedNodeDetail,如果抛开根节点,那接下来我们进入到一层层的数据,这一层层的数据我们是通过while来实现的。对于第一层数据的遍历那实际就是linkedLists[0]中的所有节点,当第一层结束后那就开始第二层,第二层又应该是怎么开始呢,这时候
public static void checkedNodeDetail(LinkedList<Integer>[] linkedLists,LinkedList<Integer> checkedNode,int startNode){
/**
*@description: 将startNode存入到checkNode,删除数组中所有链表中的这个点,并将这个数值输出
*@params: 待处理的链表数组
* @params: 执行的辅助优先队列,存储的是已经访问过了的节点
* @params: 已经访问过了的节点
*/
checkedNode.addLast(startNode);
delectCheckedNodeEvery(linkedLists,startNode);
System.out.println(startNode+"==>");
}
public static void bfs(LinkedList<Integer>[] linkedLists,int startNode){
/**
*@description: 对链表形式的数据进行广度优先排序,
*/
//数据初始化
LinkedList<Integer> checkedNode = new LinkedList<>();
//对初始节点的一个处理
checkedNodeDetail(linkedLists,checkedNode,startNode);
while(!checkedNode.isEmpty()){
Integer indexNode = checkedNode.removeFirst();
while (!linkedLists[indexNode].isEmpty()){
checkedNodeDetail(linkedLists,checkedNode,linkedLists[indexNode].removeFirst());
}
}
}
类中用来测试的主方法:
public static void main(String[] args) {
//定义图中点的个数
final int elementsCount =6;
createDatastruct(elementsCount);
//初始化图
insertEdge(0,1);
insertEdge(0,2);
insertEdge(0,5);
insertEdge(1,3);
insertEdge(1,4);
insertEdge(2,3);
insertEdge(4,5);
//数据展示
// int temp =delectDoubleNode(arrayLists,0);
showDatas(arrayLists);
// System.out.println("现在正在进行的是dfs遍历:");
// dfs(arrayLists,0);
System.out.println("现在进行的是bfs遍历:");
bfs(arrayLists,0);
}
邻接表存储形式的相关实现
上面的链表的实现完全是自己实现的,下边的这个是网上老师的一个实现,他对现有的数据结构没有修改,采用的是另外用一个标记数组来标记一个节点是否被访问了,在上面的实现中我才用的是置零的方式。这种方式会修改元数据结构,可以采用的就是把原来的数组拷贝一份,我们用拷贝的操作。
如果我们采用直接复制的形式的话,那拷贝依旧是无效的,因为他们引用的依旧是同一个地方的变量,这时候需要进行克隆,当然也可以采用一个一个复制进去的方式。有关克隆的文章是:
https://blog.csdn.net/lmlzww/article/details/105718674
package map;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
public class Graph {
private static ArrayList<String> varTextList;
private static int[][] edges;
private static int numOfEdges;
//定义给数组boolean[],记录某个节点是否被访问
private static boolean isVisited[];
public static void main(String[] args) {
int n =5;
String VerTexValue [] ={"A","B","C","D","E"};
//创建图对象
Graph graph = new Graph(n);
//循环添加顶点
for (String temp:VerTexValue){
graph.insertVertex(temp);
}
//添加边
graph.insertEdge(0,1,1);
graph.insertEdge(0,2,1);
graph.insertEdge(1,2,1);
graph.insertEdge(1,3,1);
graph.insertEdge(0,4,1);
graph.showGraph();
System.out.println("深度遍历测试");
dfs();
System.out.println("广度优先遍历");
bfs();
}
//初始化构造器
public Graph(int n){
//初始化我们的矩阵和varTextList
edges = new int[n][n];
//用来表示、存储节点
varTextList = new ArrayList<>(n);
numOfEdges =0;
isVisited = new boolean[5];
}
//返回节点的个数
public static int getNumOfVertex(){
return varTextList.size();
}
//返回边的数目
public static int getNumOfEdges(){
return numOfEdges;
}
//放回节点i对应的数据
public static String getValueByIndex(int i){
return varTextList.get(i);
}
//获得对应点的权重
public static int getWeight(int v1,int v2){
return edges[v1][v2];
}
//显示图对应的矩阵
public static void showGraph(){
for (int[] link:edges){
System.out.println(Arrays.toString(link));
}
}
//插入节点
public static void insertVertex(String vertex){
varTextList.add(vertex);
}
public static void insertEdge(int v1,int v2,int weight){
edges[v1][v2]=weight;
edges[v2][v1]=weight;
numOfEdges++;
}
//得到第一个链接节点的下标
public static int getFirstNeighbor(int index){
for (int i = 0; i < varTextList.size(); i++) {
if(edges[index][i]>0){
return i;
}
}
return -1;
}
//根据前一个林接节点的下标来获取下一个临界点
public static int getNextNeighbor(int v1,int v2){
for (int j = v2+1; j <varTextList.size() ; j++) {
if(edges[v1][j]>0){
return j;
}
}
return -1;
}
//Depth First Search
private static void dfs(boolean[] isVisited,int i){
//首先我们访问该节点并输出
System.out.print(getValueByIndex(i)+"->");
//设置该节点已经访问过
isVisited[i] = true;
//查找节点i的第一个临界点
int w = getFirstNeighbor(i);
while (w!=-1){
if(!isVisited[w]){
//没有被访问过
dfs(isVisited,w);
}
//如果w的这个节点已经被访问过那么就去找下一个邻接点
w = getNextNeighbor(i,w);
}
}
//对dfs进行一个重载,遍历我们所有的节点并进行dfs
public static void dfs(){
//遍历所有的节点,进行dfs
for (int i = 0; i < getNumOfVertex(); i++) {
if(!isVisited[i]){
dfs(isVisited,i);
}
}
}
//对一个节点进行广度优先遍历的算法
private static void bfs(boolean[] isVisited,int i){
int u;//表示队列的头结点对应的下标
int w;//邻接点w
LinkedList queue = new LinkedList();
//访问这个节点
System.out.println(getValueByIndex(i)+"=>");
//标记为已经访问过
isVisited[i] = true;
//将节点加入队列
queue.addLast(i);
while( !queue.isEmpty()){
//取出队列头结点的下标
u = (int) queue.removeFirst();
//得到第一个邻接点的下标
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 static void bfs(){
isVisited = new boolean[varTextList.size()];
for (int i = 0; i < getNumOfVertex(); i++) {
if(!isVisited[i]){
bfs(isVisited,i);
}
}
}
}