数据结构与算法--图论最短路径算法应用-词阶求解

最短路径案例

  • 词梯应用,在一个词梯中,每个单词均由前一个单词改变一个字母而得到。例如,我们通过一系列单字母替换而得到zero转换为five,如下:five:zero,hero,here,hire,fire,five
  • 我们可以看成是一个无权最短路径问题,其中每一个单词都是一个顶点,如果两个单词可以通过单字母替换而互相转换,那么他们之间就有边
  • 假设我们有一个词典,由于n个不同的单词组成,大部分单词在6~11个字母之间,我们原始数据将这些单词存储在一个字符串数组中。
算法分析
  • 首先我们需要一个比较方法,用来对比两个单词直接是否只有一个字符的不同,在这我们同时考虑增加一个字符,删除一个字符的情况,如下代码:
  /*** 比较是否只有一个字符不同的单词*/public static boolean oneCharOff(String word1, String word2) {if (word1 == null || word2 == null || word1.length() != word2.length()) {return false;}if (word1 == word2) {return false;}for (int i = 0; i < word1.length(); i++) {if (word1.charAt(i) != word2.charAt(i)) {if (word1.substring(i + 1, word1.length()) != word2.substring(i + 1, word2.length())) {return false;}}}return true;}/*** 比较是否只添加/删除一个字符得到* */public static boolean oneCharAdd(String word1, String word2){if(word1 == null || word2 == null || Math.abs(word1.length() - word2.length()) != 1){return false;}String largeWord = word1.length() > word2.length() ? word2 : word1;String shortWord = word1.length() > word2.length() ? word1 : word2;Integer count = 0;Integer largePis = 0;for (int i = 0; i < shortWord.length(); i++) {if(shortWord.charAt(i) != largeWord.charAt(largePis)){count ++;if(count > 1){return false;}if(shortWord.charAt(i) != largeWord.charAt(++largePis)){return false;}}largePis++;}return true;}
  • 首先我们需要对数据进行处理,得到一个Map,其中key是单词,value是该key单词通过1个字母替换能够从关键字变换成的一系列单词。我们用两个for循环来对数据进行一次遍历,得到一个单词的key对应的数组:
/*** 修改Map中数组对象元素*/public static void update(Map<String, List<String>> map, String key, String value) {List<String> list = map.get(key);if (list == null) {list = new ArrayList<>();map.put(key, list);}list.add(value);}/*** 方法一: 时间复杂度 O(N^2)* 处理basicWords 数组,最终 产出Map<String, List<String>>* 获取以单词为key, 值修改一个字符可组成的新的单词为数组的value值的Map对象* base数组基数89000, 运行75秒*/public static Map<String, List<String>> computeAdjacentWords(List<String> basicWords) {Map<String, List<String>> adjMap = new HashMap<>();for (int i = 0; i < basicWords.size(); i++) {for (int j = i + 1; j < basicWords.size(); j++) {if (oneCharOff(basicWords.get(i), basicWords.get(j)) || oneCharAdd(basicWords.get(i), basicWords.get(j))) {update(adjMap, basicWords.get(i), basicWords.get(j));update(adjMap, basicWords.get(j), basicWords.get(i));}}}return adjMap;}
  • 以上算法可以得到我们需要的数据结构,基础词典转换成我们需要的邻接表,其中key相当于我们的所有节点,key对应的value值是邻接节点,这样我们就可以通过这个邻接表来基础上用最短路径算法得到我们需要的词梯信息
  • 算法的缺点在于速度太慢,词语基数过大时候,费时比较多,时间复杂度O(N^2),我们可以对以上算法进行优化,
  • 预处理词典信息,将转换为Map字典,key值是关键字的长度,value是该长度的词语数组,也就是我们先通过字符串长度对他进行归类,然后继续用之前的做法将他转成我们需要的邻接表Map,如下代码:
/*** 方法二* 处理basicWords 数组,最终 产出Map<String, List<String>>* 获取以单词为key, 值修改一个字符可组成的新的单词为数组的value值的Map对象* base数组基数89000, 运行16秒*/public static Map<String, List<String>> computeAdjacentWords_v1(List<String> basicWords) {Map<String, List<String>> adjMap = new HashMap<>();Map<String, List<String>> lengthMap = new HashMap<>();//先分类,按字符串长度分类for (String basicWord : basicWords) {update(lengthMap, String.valueOf(basicWord.length()), basicWord);}for (List<String> strings : lengthMap.values()) {for (int i = 0; i < strings.size(); i++) {for (int j = i + 1; j < strings.size(); j++) {if (oneCharOff(strings.get(i), strings.get(j)) || oneCharAdd(strings.get(i), strings.get(j))) {update(adjMap, strings.get(i), strings.get(j));update(adjMap, strings.get(j), strings.get(i));}}}}return adjMap;}
  • 以上算法我们先按长度分组后对每个组进行运算。

  • 我们将这个Map代表一个图的邻接表表示方法,在此基础上我们只需要编写一个案例用来运行单源最短路径算法,然后在输出单词序列
    我们通过findChain方法利用上面Map邻接表信息和两个要被链接的单词,同时返回一个Map,改Map中,关键字是单词,而相应的值是从first开始的最短词梯上的关键字签名的那个单词

  • 如上面举例中的那个,如果开始单词是zero,关键字five的值是fire, 关键字fire的值是hire, 关键字hire的值是here,等等,只要我们得到这样一个Map链的结构,我们就能从后向前回溯出我们需要的词梯信息,实现方法如下:

 /*** 有权重图最短路径算法 实现词梯* @param first 词梯开始单词* @Param second 词梯结束单词*/public static List<String> findChain(List<String> baseWords, String first, String second) {//处理基础数据,得到对应 权重为1 的图基本数据结构 用Map表示的邻接表Map<String, List<String>> adjacentWords = computeAdjacentWords_v1(baseWords);LinkedList<String> q = new LinkedList<>();Map<String, String> previousWords = new HashMap<>();q.add(first);while (!q.isEmpty()) {String current = q.removeFirst();List<String> currentArray = adjacentWords.get(current);if (currentArray != null && currentArray.size() > 0) {for (String adjWord : currentArray) {//==null 相当于 之前know 的节点属性为以及遍历过,则无需在处理避免 有圈图if(previousWords.get(adjWord) == null){//通过map,一层及一层及剥离,最终得到 second--X--Y--Z--first的map链previousWords.put(adjWord, current);q.add(adjWord);}}}}previousWords.put(first, null);return getChainFromPreviousMap(previousWords, first, second);}//得到词梯队列信息public static List<String> getChainFromPreviousMap( Map<String, String> previousWords, String first, String second){LinkedList<String> q = null;if(previousWords.get(second) != null){q = new LinkedList<>();for(String str = second; str != null ; str = previousWords.get(str)){q.addFirst(str);}}return q;}
  • 如上实现中findChain假设first是合法的单词,通过邻接表中的信息得到对应的邻接单词,将邻接单词都构造成指向first的一个Map,并且意见处理过的邻接单词不会再次处理,Map相同key存储一次。
  • getChainFromPreviousMap使用prev Map和 second,他是Map中的一个关键字并返回用于形成词梯的那些单词,通过prev向后遍历map,使用linkedList插表头的形式得到正确的排序词梯。
  • 如上得到最终的答案,关键步骤是我们在预处理的阶段,我们利用图论的最短路径算法解决问题首先需要建立一个邻接表模型,才能完成后面的Dijkstra算法求解最短路径问题

上一篇:数据结构与算法–图论,最短路算法,拓扑排序算法
下一篇:数据结构与算法–图论-深度优先搜索及其应用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310466.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

谁说.NET不适合搞大数据、机器学习和人工智能

SciSharp StackSciSharp STACK: https://scisharp.github.io/SciSharp/基于.NET的开源生态系统&#xff0c;用于数据科学、机器学习和AI。SciSharp将所有主要的ML/AI框架从Python引入.NET.特点为.NET开发者.NET开发者使用他们所了解和喜爱的工具可以最高效的工作。我们的使命是…

C++ 泛型编程 实现红黑树RBTree

代码如下: #include <iostream> #include <ctime> using namespace std;enum COLOR {BLACK,RED };template<typename T> struct RBTreeNode {RBTreeNode<T> * _parent;RBTreeNode<T> * _left;RBTreeNode<T> * _right;T _val;COLOR _color…

数据结构与算法--图论-深度优先搜索及其应用

深度优先搜索 深度优先搜索&#xff08;depth-first search&#xff09; 是对先序遍历&#xff08;preorder traversal&#xff09;的推广&#xff0c;我们从某个顶点v开始处理v&#xff0c;然后递归的遍历所有与v邻接顶点。如果这个过程是对一棵树进行&#xff0c;那么&#…

.NET Core技术研究-主机

前一段时间&#xff0c;和大家分享了 ASP.NET Core技术研究-探秘Host主机启动过程但是没有深入说明主机的设计。今天整理了一下主机的一些知识&#xff0c;结合先前的博文&#xff0c;完整地介绍一下.NET Core的主机的设计和构建启动过程。一、什么是主机主机是一个封装了应用资…

数据结构与算法--贪婪算法

贪婪算法 贪婪算法分阶段地工作。在每个阶段&#xff0c;可以认为所做决定是最好的&#xff0c;而不考虑将来的后果。通常这意味着选择的是某个局部最优。这种“当前能获得的最优就拿”的策略是这类算法的名字来源。当算法终止时候&#xff0c;我们希望的到累积的局部最优解就…

[C++STL]C++ 实现map容器和set容器

代码如下: #pragma once #include <iostream> using namespace std;enum COLOR {BLACK, RED };template<class V>//迭代器声明&#xff0c;定义在后面 struct RBTreeIterator;template<typename V> struct RBTreeNode {RBTreeNode<V> * _parent;RBTre…

多角度让你彻底明白yield语法糖的用法和原理及在C#函数式编程中的作用

如果大家读过dapper源码&#xff0c;你会发现这内部有很多方法都用到了yield关键词&#xff0c;那yield到底是用来干嘛的&#xff0c;能不能拿掉&#xff0c;拿掉与不拿掉有多大的差别&#xff0c;首先上一段dapper中精简后的Query方法&#xff0c;先让大家眼见为实。private s…

C++泛型编程实现哈希表(闭散列---线性探测)

代码如下: #include <iostream> #include <vector> using namespace std;enum STATE {EXIST,DELETE,EMPTY };template<typename K,typename V> struct HashNode {pair<K, V> _kv;STATE _state EMPTY; };template<typename K,typename V> class…

数据结构与算法--贪婪算法2

近似装箱问题 解决装箱问题&#xff08;bin packing problem&#xff09;的算法。也可以用贪婪算法来完成 给定N项物品&#xff0c;大小为s1&#xff0c;s2&#xff0c;s3…sn&#xff0c;所有的大小满足0 < si < 1。问题是要把这些物品装到最小数目的箱子中&#xff0c…

哪种开源许可证最适合商业化?

选择最佳开源许可证是为新项目所做的最重要的决定之一。大多数开发者会选用 MIT、BSD 或 Apache 等流行的宽松许可证&#xff08;permissive license&#xff09;。对于商业项目而言&#xff0c;这种选择不错&#xff0c;因为这能减少用户对项目的抵触情绪。当应用于开源项目时…

C++泛型编程实现哈希表(开散列法)

代码如下: #include <iostream> #include <vector> using namespace std;template<typename K> struct HashNode {typedef HashNode<K> Node;K _val;Node * _next;HashNode(const K & val):_val(val),_next(nullptr){} };template<typename K&…

数据结构与算法--分治算法-最大子序列和问题

分治算法 用于设计算法的一种常用技巧–分治算法&#xff08;divide and conquer&#xff09;。分治算法由两部分组成&#xff1a; 分(divide)&#xff1a;递归然后借机较小的问题&#xff08;基础情况除外&#xff09;治(conquer)&#xff1a;然后从子问题的解构建原问题的解…

请把我不会,换成我可以学

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份有位读者跟我说起自己的烦恼&#xff1a;“我到公司已经接近四年了&#xff0c;领导经常让我做一些岗位职责以外的事情。这些东西我都不会&#xff0c;还非让我做。并且一直没有职位上的改变&#xff0c;我怎…

[C++STL]C++实现unordermap容器和unorderset容器

代码如下: #include <iostream> #include <vector> using namespace std;template<typename K,typename V,typename KeyOfValue> class HashTable;//声明template<typename V> struct HashNode {typedef HashNode<V> Node;V _val;Node * _next;…

数据结构与算法--链表实现以及应用

数据结构与算法–链表实现以及应用 链表是面试时候使用最频繁的一种数据结构。链表的结构简单&#xff0c;他由指针将若干个节点链接成链状结构。链表的创建&#xff0c;插入&#xff0c;删除&#xff0c;查询操作都只有几行代码可以完成&#xff0c;代码量比较少&#xff0c;…

还不会docker+k8s?2020年,就要面对现实了...

docker的前世今生2010年&#xff0c;几个年轻人&#xff0c;在美国旧金山成立了一家名叫“dotCloud”的公司。这家公司主要提供基于PaaS的云计算技术服务。具体来说&#xff0c;是和LXC有关的容器技术。后来&#xff0c;dotCloud公司将自己的容器技术进行了简化和标准化&#x…

数据结构与算法--重建二叉树

二叉树 树在实际编程中经常遇到地一种数据结构。上一篇中我们解释了二叉树及其原理&#xff0c;从中可以知道&#xff0c;树地操作会涉及到很多指针地操作&#xff0c;我们一般遇到地树相关地问题差不多都是二叉树。二叉树最重要地莫过于遍历&#xff0c;即按照某一顺序访问树…

3分钟掌握Quartz.net分布式定时任务的姿势

长话短说&#xff0c;今天聊一聊分布式定时任务&#xff0c;我的流水账笔记&#xff1a;ASP.NET CoreQuartz.Net实现web定时任务AspNetCore结合Redis实践消息队列细心朋友稍一分析&#xff0c;就知道还有问题&#xff1a;水平扩展后的WebApp的Quartz.net定时任务会多次触发&…

C++ 实现布隆过滤器(BloomFilter)

代码如下: #include <iostream> #include <vector> using namespace std;class BitMap { public:BitMap(size_t range) :_bit(range / 32 1) {}void set(const size_t num){int idx num / 32;//idx 数组下标int bitIdx num % 32;_bit[idx] | 1 << bitIdx…

数据结构与算法--利用栈实现队列

利用栈实现队列 上一节中说明了栈的特点 后进先出&#xff0c;我们用数组的方式实现了栈的基本操作api&#xff0c;因此我们对栈的操作是不考虑排序的&#xff0c;每个api的操作基本都是O(1)的世界&#xff0c;因为不考虑顺序&#xff0c;所以找最大&#xff0c;最小值&#x…