深度优先搜索(DFS) + DFS的应用:字符串的排列问题

27 篇文章 1 订阅
14 篇文章 0 订阅

深度优先搜索

深度优先搜索属于图算法的一种,是一个针对图和树的遍历算法。其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。 深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。

搜索算法是利用计算机的高性能来有目的的穷举一个问题的部分或所有的可能情况,从而求出问题的解的一种方法。

基本思路:

深度优先遍历图的方法是,从图中某顶点v出发:
(1)访问顶点v;
(2)依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
(3)若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。
(4)然后原路退回,看有没有节点的子节点是还没有被访问过的,有就继续重复(1),没有就继续退回(或许图2更有普遍性)。图2通过深度优先搜索获得的顶点的遍历次序为:V1 -> V2 -> V4 -> V8 -> V5 -> V3 -> V6 -> V7深度优先搜索也是一个不断回溯的过程,简单来说就是走不通再退回。

举例:

在这里插入图片描述图1在这里插入图片描述图2

比如上图1(从百科拿来的),没有方向,现在打算从A节点开始深度优先搜索,从A开始有三个路径分别是B,C,D,深度优先搜索的路径可能是A>B>E到头了,返回A继续搜索,然后A>C>F>H>G>D,回溯到A没有未遍历的节点了,DFS结束。

穷举:

在我们遇到的一些问题当中,有些问题我们不能够确切的找出数学模型,即找不出一种直接求解的方法,解决这一类问题,我们一般采用搜索的方法解决。搜索就是用问题的所有可能去试探,按照一定的顺序、规则,不断去试探,直到找到问题的解,试完了也没有找到解,那就是无解,试探时一定要试探完所有的情况(实际上就是穷举);

深度优先算法思路:

先定义一个数组存放产生的所有状态;

  1. 把初始状态放入数组中,设为当前的状态;
  2. 扩展当前的状态,产生一个新的状态放入数组中,同时把新产生的状态设为当前状态;
  3. 判断当前状态是否和前面的重复,如果重复则回到上一个状态,产生它的另一个状态;
  4. 判断当前状态是否为目标状态,如果是目标,则找到了一个解答, 结束算法;
  5. 若数组为空,说明无解。

DFS应用:全排列问题

题目描述:
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]

分析:
DFS的理论理解倒是好理解,不过这个全排列问题是着实的费脑子。
参考了下这位大神的思路:This。这个例子是打算全排列{x1,x2,x3,x4}这四个值。

在这里插入图片描述
其中呢,Q(1,3)就是下标为1和下标为3之间的全排列输出。然后就是一个递归的思路去实现,具体可以看下他的文章。

代码实现:

//全排列问题

#include<iostream>
#include<vector>
#include<string>
#include<set>
using namespace std;


class Solution {
public:
	//边生成边剪枝
    vector<string> ans;//定义结果数组
    vector<char> c_char;//存放字符串的字符数组
    
	vector<string> Permutation(string s) 
	{
        // string 转换成 vector<char> 数组
        for(int i = 0; i < s.size(); i++)
        {
            c_char.push_back(s[i]);
        }
        dfs(0);//从第0个开始固定
        return ans;
    }
    
	void dfs(int x)//dfs只负责搜索
    {
        if(x == c_char.size() - 1)//递归固定到第到x==len-1个数时,将字符串入结果数组栈
        {
            string res(c_char.begin(),c_char.end());
            ans.push_back(res);
            return;
        }
        set<char> st;//初始化一个set用于排除重复的字符串
        for(int i = x; i < c_char.size(); i++)
        {
            if(st.count(c_char[i])) continue;//重复则减枝(终止了本次的循环) set.count()查找set中某个键值是否已经出现
            st.insert(c_char[i]);//不重复则继续添加元素
            swap(c_char[i], c_char[x]);//将c[i]固定到c[x]位【这里的c_char[i]用来临时放每次固定的值,每次一个字符串固定完成就将其入到结果数组里】
            dfs(x + 1);//递归开始固定第x+1个字符
            swap(c_char[i], c_char[x]);//等每次res入了结果数组之后才通过return; 来恢复之前的交换
             }
    }    
};


int main()
{
	string a = "abc";
	Solution x;
	vector<string> r = x.Permutation(a);
	for(int i=0;i<r.size();i++)
	{
		cout<<r[i]<<endl;
	}
	return 0;
}

笔记:
count() 用来查找set中某个某个键值出现的次数。这个函数在set并不是很实用,因为一个键值在set只可能出现0或1次,这样就变成了判断某一键值是否在set出现过了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值