算法学习4_搜索

深度优先搜索- 不撞南墙不回头
深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次.

下图是一个无向图,如果我们从A点发起深度优先搜索(以下的访问次序并不是唯一的,第二个点既可以是B也可以是C,D),则我们可能得到如下的一个访问过程:A->B->E(没有路了!回溯到A)->C->F->H->G->D(没有路,最终回溯到A,A也没有未访问的相邻节点,本次搜索结束).

060828381f30e924b08eba2e4c086e061d95f782.jpg

简要说明深度优先搜索的特点:每次深度优先搜索的结果必然是图的一个连通分量.深度优先搜索可以从多点发起.如果将每个节点在深度优先搜索过程中的"结束时间"排序(具体做法是创建一个list,然后在每个节点的相邻节点都已被访问的情况下,将该节点加入list结尾,然后逆转整个链表),则我们可以得到所谓的"拓扑排序",即topological sort.


题二、有九张(1-9)的扑克牌,放入九个盒子里面,使得 _ _ _ + _ _ _ = _ _ _ 成立,
现在使用深度优先搜索优化一下之前的方法,之前使用枚举算法,时间复杂度太高

int a[10], book[10], n , total = 0;// C语言全局变量在没有赋值的时候,默认为0
void dfsTwo(int step); // 深度优先搜索
char* getDateTime(void);
int main(int argc, const char * argv[]) {
    char * time = getDateTime();
    printf("时间 ====%s \n",time);
    dfsTwo(1); // 首先在第一个盒子里面
    printf("总共有%d种方法",total/2);
    printf("时间 ====%s \n",time);
    getchar();
}
void dfsTwo(int step) // 深度优先搜索
{
    int i = 0;
    if (step ==10) { // 如果是第十个个盒子,说明之前的九个盒子里面都是有扑克牌的
        // 判断是否满足等式  _ _ _ + _ _ _ = _ _ _
        
        if ((100*a[1] + 10*a[2] + a[3])+(100*a[4] + 10*a[5] + a[6])==(100*a[7] + 10*a[8] + a[9]) ) {
            // 如果满足条件, 解加一,并打印这个解
            total ++;
            printf("%d%d%d+%d%d%d=%d%d%d \n",a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9]);
        }
        return;
    }
    // 如果不是第十个盒子,哪应该放那张牌
    // 按照 1 - 9 的顺序尝试
    for (i = 1; i < 10; i ++) {
        
        // 判断扑克牌是否在手中
        if (book[i] == 0) {
            
            // 开始使用手中的扑克牌
            a[step] = i;
            book[i] = 1;
            
            // 第step个盒子已经放好了扑克牌,走到下面一个盒子里面
            dfsTwo(step + 1);
            book[i] = 0; // 收回手中的牌,才可以继续操作
        }
    }
}

char* getDateTime()
{
    static char nowtime[20];
    time_t rawtime;
    struct tm* ltime;
    time(&rawtime);
    ltime = localtime(&rawtime);
    strftime(nowtime, 20, "%Y-%m-%d %H:%M:%S", ltime);
    return nowtime;
}

题三、深度优先算法 最短路径
看下列地图,可以的到点a 到点b的最短距离
0 表示空地, 1 表示障碍物 其中 a 和 b 也是在空地里面 ,求a(0,0) 到 b(3,2) 的最短距离 这里坐标为 (行,列)
5行 4列
0 0 1 0
0 0 0 0
0 0 1 0
0 1 0 0
0 0 0 1

int n3 ,m3 ,p3 ,q3,min3 = 99999999;
int a3[51][51], book3[51][51];
void dfsThree(int x,int y, int step);
char* getDateTime(void);

int main(int argc, const char * argv[]) {

    int i , j , startX ,startY;
    
    // 读入n 和 m  n为行 m为列
    printf("请输入 行 和列:");
    scanf("%d %d",&n3, &m3);
    
    // 读入迷宫
    for (i = 0; i < n3; i ++ ) {
        printf("输入迷宫:\n");
        for (j = 0; j < m3; j ++) {
            scanf("%d",&a3[i][j]);
        }
    }

    // 读入 起点 和终点
    printf("输入起点坐标");
    scanf("%d%d",&startX,&startY);  // 设置起点坐标为 (startX,startY)
    printf("输入终点坐标");
    scanf("%d%d",&p3,&q3); // 设置终点坐标为 (p,q)
    
    
    // 从起点开始搜索
    book3[startX][startY] = 1; // 标记起点已经在路径上,防止再重复行走
    dfsThree(startX, startY, 0); // 第一个参数 为起点x ,第二个为起点y ,第三个为初始化步数0
    
    printf("输出最短步数 ,%d\n",min3);
    getchar();
}

// 得到 A点 到b点的最少步数
void dfsThree(int x,int y, int step) // 深度优先搜索
{
    int next [4][2] = {
        {0,1},      // 向右走
        {1,0},      // 向下走
        {0,-1},     // 向左走
        {-1,0}      // 向上走
    };
    int tx = 0, ty = 0,k;
    // 判断是否达到b点位置
    printf("x ==%d y ==%d  p3=== %d q3====%d \n",x,y,p3,q3);
    if (x == p3 && y == q3) {
       
        // 更新最小步数
        if (step < min3) {
             printf("输出最短步数 ,%d\n",min3);
            min3 = step;
        }
        return;
    }
    
    // 枚举四种走法
    for (k = 0; k < 4; k ++) {
        // 计算下一个点的坐标
        tx = x + next[k][0];  // x = 0 , y = 1 是因为是二维数组 [4][2] {0,1} 其中下标为0 为x 下标为1的为y。 k 数组是为长度为4
        ty = y + next[k][1];
        
        
        // 判断是否越界
        if ( tx < 0 || tx > n3 || ty < 0 || ty > m3) {
            continue;
        }
        // 判断改点是否是障碍物或者是已经走过的点
        if (a3[tx][ty] == 0 && book3[tx][ty] == 0) { // 0 为空地 1 表示障碍物
            // 不是障碍物 或者已经走过的点继续
            book3[tx][ty] = 1; //标记这个点已经走过
            dfsThree(tx, ty, step + 1);
            book3[tx][ty] = 0;
        }
    }
    return;
}

char* getDateTime()
{
    static char nowtime[20];
    time_t rawtime;
    struct tm* ltime;
    time(&rawtime);
    ltime = localtime(&rawtime);
    strftime(nowtime, 20, "%Y-%m-%d %H:%M:%S", ltime);
    return nowtime;
}

广度优先搜索 - 层层递进
广度优先搜索算法(Breadth First Search),又称为"宽度优先搜索"或"横向优先搜索",简称BFS。

它的思想是:从图中某顶点v出发,在访问了a之后依次访问a的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

题三、广度优先算法 最短路径
看下列地图,可以的到点a 到点b的最短距离
0 表示空地, 1 表示障碍物 其中 a 和 b 也是在空地里面 ,求a(0,0) 到 b(3,2) 的最短距离 这里坐标为 (行,列)
5行 4列
0 0 1 0
0 0 0 0
0 0 1 0
0 1 0 0
0 0 0 1
换句话说,广度优先搜索遍历图的过程是以a为起点,由近至远,依次访问和va路径相通且路径长度为1,2...的顶点。
1、从 (0,0)开始搜索,以a点代替
a 0 1 0
0 0 0 0
0 0 1 0
0 1 0 0
0 0 0 1

-》
2、第二层搜索 从(0,0)搜索,右方和下方 以 b点 代替
a b 1 0
b 0 0 0
0 0 1 0
0 1 0 0
0 0 0 1

-》
3、第三层搜索 ,(0,1)这个b点右边是障碍物,无法搜索,可以往下搜索(1,1),(1,0)这个点搜索右方发现已经搜索完成了(每个点需要搜索一次),跳过,搜索(1,0)下方的点 (2,0),以c代替
a b 1 0
b c 0 0
c 0 1 0
0 1 0 0
0 0 0 1

...
就这样一直层层搜索直到搜索完成所有到点(3,2)的路线,结束搜索,遇到障碍物“1”跳过,最后变成下面这个地图
a b 1 f
b c d e
c d 1 f
d 1 h g
e f g 1

完整demo如下

// 广度优化搜索
struct note
{
    int x; // 横坐标
    int y; // 纵坐标
    int f; // 父亲在队列的的编号 ,需要输出路径才需要使用
    int s; // 步数
};
char* getDateTime(void);

int main(int argc, const char * argv[]) {
    struct note queB[2501]; // 因为地图大小不会超过 50 * 50 所以 队列扩展也不会超过2500个
    int aB[51][51], bookB[51][51];
    // 定义一个表示方向的数组
    int nextB [4][2] = {
        {0,1},      // 向右走
        {1,0},      // 向下走
        {0,-1},     // 向左走
        {-1,0}      // 向上走
    };
    int headB ,tailB;
    int iB,jB,kB,nB,mB,startX,startY,pB,qB,txB,tyB,flagB;
    
    printf("请输入行和列:");
    scanf("%d %d",&nB,&mB);
    for (iB = 0; iB < nB; iB ++) {
        printf("输入当前行地图");
        for (jB = 0; jB < mB; jB++) {
            scanf("%d",&aB[iB][jB]);
        }
    }
    
    printf("输入开始坐标startX,startY 结束坐标 pB qB");
    scanf("%d %d %d %d",&startX,&startY,&pB,&qB);
    
    // 队列初始化
    headB = 0;
    tailB = 0;
    
    // 往队列里面插入迷宫入口坐标
    queB[tailB].x = startX;
    queB[tailB].y = startY;
    queB[tailB].f = 0;
    queB[tailB].s = 0;
    tailB ++;
    bookB[startX][startY] = 1;
    
    
    flagB = 0;// 标记是否达到目标点,0表示没有达到,1表示已经达到了目标点
    // 当队列部位空得时候循环
    while (headB < tailB) {
        
        // 枚举4个方向
        for (kB = 0; kB< 4; kB ++) {
            
            // 计算下一个点的坐标
            txB = queB[headB].x + nextB[kB][0];
            tyB = queB[headB].y + nextB[kB][1];
            
            // 判断是否越界
            if (txB < 0 || txB > nB || tyB < 0 || tyB > mB ) {
                continue;
            }
            
            // 判断是否是障碍物,或者是已经走过的路
            if (aB[txB][tyB] == 0 && bookB[txB][tyB] == 0) {
                // 不是障碍物,并且没有走过的路
                // 标记已经走过的点,广度搜索和深度搜索不同,只需要走过一次不需要bookB数组还原
                bookB[txB][tyB] = 0;
                // 并且把这个点插入到队列里面
                queB[tailB].x = txB;
                queB[tailB].y = tyB;
                queB[tailB].f = headB;  // 打印路径需要,因为点事从head里面扩展出来的
                queB[tailB].s = queB[headB].s + 1;// 步数是父亲步数的 + 1
                tailB ++;
            }
            
            // 如果到达目标点,停止扩展,任务结束,退出循环
            if (txB == pB && tyB == qB) {
                flagB = 1;
                break;
            }
        }
        
        if (flagB == 1) {
            break;
        }
        headB ++; // 当一个点扩展结束之后,需要下一个点开始扩展
    }
    
    // 打印队列中末尾最后的一个点的步数
    // 注意 tail 是指向队尾 的下一个位置,这里需要减一
    //    for (iB = 0; iB < tailB; iB ++) {
    //        printf("%d ",queB[iB].f);
    //    }
    printf("\n最短的步数是:%d\n",queB[tailB - 1].s);
    getchar();
return;
}
char* getDateTime()
{
    static char nowtime[20];
    time_t rawtime;
    struct tm* ltime;
    time(&rawtime);
    ltime = localtime(&rawtime);
    strftime(nowtime, 20, "%Y-%m-%d %H:%M:%S", ltime);
    return nowtime;
}

本人也是刚刚学习,如有错误,请多多指正。
本文部分内容参考 【啊哈!算法】这本书
代码例子

你可能感兴趣的:(算法学习4_搜索)