[算法&数据结构]深度优先搜索(Depth First Search)

深度优先 搜索(DFS, Depth First Search)
从一个顶点v出发,首先将v标记为已遍历的顶点,然后选择一个邻接于v的尚未遍历的顶点u,如果u不存在,本次搜素终止。如果u存在,那么从u又开始一次DFS。如此循环直到不存在这样的顶点。

算法核心代码如下:

    void dfs(int step){

        // 判断边界是否成立

        // 尝试每一种可能
        for(int i=0;i<n;i++){
            // 
            // 继续执行下一步
            dfs(step + 1)
            // 取消已被使用标记
        }

    }

全排列

下面我们利用一个简单基本案例来学习全排列

A 手中有3张牌,分别是1,2,3 那么请问这三张牌能组成多少位不重复三位数字?

DFS算法分析

  • 首先我们假设有三个桶,桶里面可以存放牌,那么我们来到第一个桶,我们手里有3张牌,按照顺序,我我们可以放入1,然后完成当前操作,标记第一张牌已经被使用,来到第二个桶里面,开始尝试,尝试放入第一张牌,此时1已经被使用了,所以我们尝试2,那么第二个桶也已经被放入数据了,2被标记为使用,接着来到第3个桶,此时,我们尝试放入1,1被使用,无法放入,尝试放入2,2也被使用,尝试放入3,OK,3 放入成功,此时三个桶放入完成,完成一次全排列,即1,2,3

  • 好,回到第3个桶,的时候我们没有其他牌可以放了,1,2已经被使用,3正在桶里呢,我们继续回到2号桶,同时标记3号牌未被使用,回到2号桶,此时2号桶已经尝试了1,2 那么我们继续尝试3号牌,3号牌刚刚被收回,可以放入,此时2号桶放入3号牌,继续第3个桶,同样的依次尝试所有的可能,1号牌不行,2号可以,此时完成了全排列1,3,2

  • 继续,回到3号桶,没有可用的了,回到2号桶,3号牌也被用了,也没有了,继续回到1号桶,此时1号桶放入的是1,那么我们收回,继续放入2号牌,来到2号桶,此时手中有1,3号,我们放入1,来到3号桶,1,2已被使用,我们只能放入3,又完成一次全排列2,1,3

  • 依次类推....

那么我们看下代码,这里提供了C语言版本的和Java语言版本的,原理是一样的

C语言版本

代码

#include <stdio.h>

// 定义扑克牌长度 3
#define PLAY_CARD_SIZE 3
// 定义数组
int numbers[] = {1,2,3};

// 标记数字是否被使用
int status[] = {0, 0, 0};

// 定义位置
int location[] = {0,0,0};

/**
 * 声明dfs方法
 * @param step  当然位置
 */
void dfs(int step);

int main(){
    dfs(0);
    return 0;
}


void dfs(int step){
    // 判断搜索临界条件
    if (step == 3){
        for (int i = 0; i < 3; ++i) {
            printf("%d,",location[i]);
        }
        printf("\n");
        // 完成此次全排列
        return;
    }

    for (int j = 0; j < 3; ++j) {
        if(status[j] == 0){
            location[step] = numbers[j];
            status[j] = 1;
            dfs(step + 1);
            status[j] = 0;
        }
    }
}

输出结果

1,2,3,
1,3,2,
2,1,3,
2,3,1,
3,1,2,
3,2,1,

Java语言版本

代码

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

/**
 * 深度优先搜寻算法
 */
public class DFS2 {

    // 定义扑克牌的数量
    static int PLAY_CARD_SIZE = 3;
    // 存放扑克牌的集合
    static List<PlayCard> playCards = new ArrayList<>();
    // 存放扑克牌的位置
    static String[] numbers = new String[PLAY_CARD_SIZE];

    // 初始化扑克牌 1,2,3
    static {
        for (int i = 0; i < PLAY_CARD_SIZE; i++) {
            playCards.add(new PlayCard(String.valueOf(i + 1), false));
        }
    }

    // 程序入口
    public static void main(String[] args) {
        dfs(0);
    }

    private static void dfs(int startIndex) {

        if (startIndex == playCards.size()){
            for (int i = 0;i<numbers.length;i++){
                System.out.print(numbers[i]+",");
            }
            System.out.println("");
            System.out.println("-------------");
            return;
        }

        for (int i = 0; i < playCards.size(); i++) {
            if(!playCards.get(i).used){
                playCards.get(i).used = true;
                numbers[startIndex] = playCards.get(i).code;
                dfs(startIndex + 1);
                playCards.get(i).used = false;
            }
        }


    }

    // 封装的实体类,为了方便定义为public
    static class PlayCard {
        public PlayCard(String code, boolean used) {
            this.code = code;
            this.used = used;
        }

        // 扑克牌编号 ,即1,2,3
        public String code;

        // 扑克牌是否已被使用
        public boolean used;
    }

}

输出结果

1,2,3,
1,3,2,
2,1,3,
2,3,1,
3,1,2,
3,2,1,

等式求解

想起来以前有个题目,计算恒等式,题目是a[0] * 100 + a[1] * 10 + a[2] +a[3] * 100 + a[4] * 10 + a[5] == a[6] * 100 + a[7] * 10 +a[8] 问a的组合有多少种?

Ps:a是0-9组成的,不可重复

下面我们有DFS来实现这个题目,记得在以前,肯定是写九个for循环嵌套,现在我们尝试利用上面的全排列来判断,此时的输出(边界条件)修改为上面的等式,代码不做过多的阐述了。没有了9层循环的样子。。。。

代码

public class DFS3 {

    static int PLAY_CARD_SIZE = 9;
    static List<Number> playCards = new ArrayList<>();
    static int[] a = new int[PLAY_CARD_SIZE];

    static {
        for (int i = 1; i <= PLAY_CARD_SIZE; i++) {
            playCards.add(new Number(i, false));
        }
    }


    public static void main(String[] args) {
        dfs(0);
    }

    private static void dfs(int startIndex) {

        if (startIndex == playCards.size()) {
            if (checkNumber()) {
                for (int i=0;i<PLAY_CARD_SIZE;i++){
                    System.out.print(a[i]+",");
                }
                System.out.println();
            }
            return;
        }

        for (int i = 0; i < playCards.size(); i++) {
            if (!playCards.get(i).used) {
                playCards.get(i).used = true;
                a[startIndex] = playCards.get(i).code;
                dfs(startIndex + 1);
                playCards.get(i).used = false;
            }
        }
    }

    /**
     * 判断搜索边界
     *
     * @return
     */
    private static boolean checkNumber() {
        if(a[0] * 100 + a[1] * 10 + a[2] +a[3] * 100 + a[4] * 10 + a[5] ==  a[6] * 100 + a[7] * 10 +a[8])
            return true;
        return false;
    }

    static class Number {
        public Number(int code, boolean used) {
            this.code = code;
            this.used = used;
        }

        public int code;
        public boolean used;
    }

}

输出结构

输出结构也是蛮多的,这里摘录几个,可以自己测试下

1,2,4,6,5,9,7,8,3,
...
2,1,4,5,6,9,7,8,3,
...
3,1,4,6,5,8,9,7,2,
...
4,1,5,2,7,8,6,9,3,
...
5,9,6,2,4,1,8,3,7,
...
6,9,5,1,4,2,8,3,7,
...
7,8,4,1,5,2,9,3,6,

总结

DFS 是一个非常有意思的算法,在图解中和BFS也属于非常重要的算法了,多多理解,多多学习

posted @ 2019-02-15 10:46  燕归来兮  阅读(1246)  评论(0编辑  收藏  举报