前言

关于算法与具体实现结构之间的关系,我感觉倪升武的博客解释的很好。
这里引用一段:

前面讨论的数据结构都有一个框架,这个框架都是由相应的算法设定的。比如说,二叉树是那样一个形状,就是因为那样的形状使他更容易搜索数据和插入新数据,树的边表示了从一个节点到另一个节点的快捷方式。而图通常有一个固定的形状,这是因为由物理或抽象的问题所决定的。———–倪升武的博客

哎,最近成了倪升武的追随者了,不过这位博主写的好。大家可以去读一读。

关于图的一些概念

关于图的很多定义,有从数学角度的,有从工程角度的。当然作为程序员,良好甚至强悍的基础是必须的,在此我们不去讨论那些,这里为了叙述的方便,我们只是从一个使用者的角度来不是很严谨的、大概的提一下关于图的一些定义和一些相关概念。

  • 有向图:顾名思义,顶点之间的连线是有方向的。与之相对的是无向图。
  • 连通图:从图中任何一个顶点都可以通过边到达另一个顶点。与之相对的是非连通图。
  • 图的生成树:是图的一个极小连通子图。含有图中全部顶点,但只有足以构成一棵树的n-1条边。一棵有n个顶点的生成树有且仅有n-1条边。如果一个图有n个顶点和小于n-1条边,那么必定是非连通图。
    关于图的定义概念还有好多,譬如入度、出度、邻接点等等。具体定义读者可以自行查找。

图的存储结构

图共有2种存储结构,分别为二维数组表示法和邻接表表示法。
另外,为了讨论的方便,我们下面均默认以无向图来进行讨论。

1.二维数组表示法

这种方法感觉是用的最多的一种方法。如果读者有关于树的操作的基础,那么以这种方法来理解图的相关操作便很容易了。

节点ABC
A011
B101
C110

上表说明图共有3个顶点A、B、C,A与B互联,那么将表中对应的值定为1,否则定为0。

2.邻接表表示法

邻接表是一个组中元素为链表的数组,每个单独的列表表示有哪些顶点与当前顶点邻接。如下表。A的邻接顶点有B、C、D。

顶点包含邻接顶点的链表
AB->C->D
BA->D
CA
DA->B

图的2种搜索

下面我先给出图的定义类的基本成员变量和一些基本方法:

public class Graph {
    private final int MAX_VERTS = 20;//限制图包含的最大的顶点的数量
    private Vertex vertexArray[];   //存储顶点的数组
    private int adjMat[][]; //存储是否有边的矩阵数组, 0表示没有边,1表示有边
    private int nVerts; //图含有的顶点个数
    private StackX stack;   //深度搜索时用来临时存储的栈。这个类的定义在下面有说明
    private QueueX queue;   //广度搜索时用来临时存储的队列。类定义下面有说明

        public Graph() {//构造器
        vertexArray = new Vertex[MAX_VERTS];
        adjMat = new int[MAX_VERTS][MAX_VERTS];
        nVerts = 0;
        for(int i = 0; i < MAX_VERTS; i++) {
            for(int j = 0; j < MAX_VERTS; j++) {
                adjMat[i][j] = 0;
            }
        }
        stack = new StackX();
        queue = new QueueX();
    }

    public void addVertex(char lab) {//向无向图中添加一个顶点
        vertexArray[nVerts++] = new Vertex(lab);
    }

    public void addEdge(int start, int end) {//向无向图添加一条边
        adjMat[start][end] = 1;
        adjMat[start][end] = 1;
    }

    public void displayVertex(int v) {
        System.out.print(vertexArray[v].label);
    }
}

在上面的代码中,作者用了自定义的StackX和QueueX类,其实都和JDK中常用的类的实现差不多。

public class QueueX {
    private final int SIZE = 20;
    private int[] queArray;
    private int front;
    private int rear;

    public QueueX() {
        queArray = new int[SIZE];
        front = 0; 
        rear = -1;
    }

    public void insert(int j) {
        if(rear == SIZE-1) {
            rear = -1;
        }
        queArray[++rear] = j;
    }

    public int remove() {
        int temp = queArray[front++];
        if(front == SIZE) {
            front = 0;
        }
        return temp;
    }

    public boolean isEmpty() {
        return (rear+1 == front || front+SIZE-1 == rear);
    }
}
public class QueueX {
    private final int SIZE = 20;
    private int[] queArray;
    private int front;
    private int rear;

    public QueueX() {
        queArray = new int[SIZE];
        front = 0; 
        rear = -1;
    }

    public void insert(int j) {
        if(rear == SIZE-1) {
            rear = -1;
        }
        queArray[++rear] = j;
    }

    public int remove() {
        int temp = queArray[front++];
        if(front == SIZE) {
            front = 0;
        }
        return temp;
    }

    public boolean isEmpty() {
        return (rear+1 == front || front+SIZE-1 == rear);
    }
}

在开始讨论2种搜索方式之前,我们这里还需要定义一个方法,这个方法是在二维数组中横向寻找值为1的顶点,也就是与当前顶点有连接的顶点。返回值是找到的顶点的索引位置,-1表示未找到。

    //returns an unvisited vertex adj to v
        public int getAdjUnvisitedVertex(int v) {
            for(int i = 0; i < nVerts; i++) {
                if(adjMat[v][i] == 1 && vertexArray[i].wasVisited == false) {//v和i之间有边,且i没被访问过
                    return i;
                }
            }
            return -1;
        }

2种搜索方法:

  • 深度优先搜索:抓住一个顶点,以其为根,然后尽可能的往下查找,即纵向查找。
  • 广度优先搜索:横向搜索。
    对于下面的代码需要说明一下,与代码原作者沟通过,我们进行的搜索都是基于连通图的。对于非连通图,遍历就会在遍历完成一个连通子图之后断掉。

1.深度优先搜索

直接上代码

    // 深度优先搜索
    public void depthFirstSearch()
    {
        // 从存储顶点的数组的第一个顶点开始;
        vertexArray[0].wasVisited = true;
        displayVertex(0);
        stack.push(0);

        while (!stack.isEmpty())//stack中存储的是遍历的路径,就跟遍历一棵二叉树差不多。栈中最底层是我们最早开始搜索的顶点(对比树的根节点),栈顶处的顶点对比叶子节点。
        {
            int v = getAdjUnvisitedVertex(stack.peek());//根据当前顶点来寻找与其有连接的另一个顶点,v为其位置
            if (v == -1)//如果根据当前顶点并未找到与其连接的下一个顶点,说明已经到图的末梢,那么将该顶点出栈,返回其“父顶点”再次进行查找操作
            {
                stack.pop();
            } else
            {
                vertexArray[v].wasVisited = true;//找到顶点,将遍历标志位置为true
                displayVertex(v);//输出顶点
                stack.push(v);//入栈,为了下次操作以该顶点为当前顶点进行查找工作
            }
        }

        //stack 此时为空,这里改进?(后来与博主沟通交流,这里是还原遍历状态...汗T_T,看来自己还是过于粗心...)
        for(int i = 0;i<nVerts;i++)
        {
            vertexArray[i].wasVisited = false;
        }

    }

3.广度优先搜索

    public void breadthFirstSearch() {
        vertexArray[0].wasVisited = true;
        displayVertex(0);
        queue.insert(0);
        int v2;

        while(!queue.isEmpty()) {
            int v1 = queue.remove();//这个remove()只是抽象意义上的remove,实际只是将队列内部游标向后移动了一位。然后将后一位作为队列头部。这里是正确的。
            while((v2 = getAdjUnvisitedVertex(v1)) != -1) {
                vertexArray[v2].wasVisited = true;
                displayVertex(v2);
                queue.insert(v2);
            }
        }
        //同上面一样,同样是还原遍历状态T_T...
        for(int i = 0; i < nVerts; i++) {
            vertexArray[i].wasVisited = false;
        }
    }

明天跟代码作者沟通交流之后,再补一下关于最小生成树的问题。

图的最小生成树,即图中的n个顶点用n-1条边来连接在一起。其实跟遍历情况差不多。这里给出DFS的生成方式。

public void minSpanningTree() {
    vertexArray[0].wasVisited = true;
    stack.push(0);
    while(!stack.isEmpty()) {
        int currentVertex = stack.peek();//这里与DFS处理有点小不同
        int v = getAdjUnvisitedVertex(currentVertex);
        if(v == -1) {
            stack.pop();
        }
        else {
            vertexArray[v].wasVisited = true;
            stack.push(v);
            displayVertex(currentVertex); //from currentV,这里是不同点
            displayVertex(v); //to v
            System.out.print(" ");
        }
    }
    //stack is empty, so we're done
    for(int j = 0; j < nVerts; j++) {
        vertexArray[j].wasVisited = false;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值