• 一个经典的字母排列算法


    最近在研究一个问题,自己尝试些写了一个算法:

    问题描述:给出一段字符,比如[a,b,c,d……],输出任意长度大于n的字符组合

      1. 分析:首先确立数学模型。这个问题的本质是排列问题,即:AL2 + AL3 + …… + ALL。既然是排列问题,就应该按照排列的思维来进行处理这个问题。首先不去分析具体的实现细节,遵循着从整体到局部的思想,先确立程序,然后在确立算法与数据结构。从上面的描述中,可以看出,排列是分层级的,比如长度为2的层级的排列,长度为3的层级的排列……程序与数学模型的一个区别就是,程序要实现具体细节,而数学模型是一种抽象。所以说,上面的排列公式,除了分析出层级之外,重要的是要考虑层级之间的关联。在实现具体细节的时候,发现,每一次层级的组合结果都是下一个层级要组合的原始数据。层级与层级之间的组合,对于程序来讲,要用递归算法来实现,这一点是毋庸置疑的。从整个过程来看,必须要有第一次组合产生的结果,作为整个组合的原始数据,以便进行后面层级的组合。
      2. 确立程序;
      3. 确立数据结果与算法;
      4. 代码:
      5. package com.txq.letter.combine;

        import java.lang.ref.SoftReference;
        import java.util.ArrayList;
        import java.util.Collections;
        import java.util.HashSet;
        import java.util.List;
        import java.util.Queue;
        import java.util.Set;
        import java.util.concurrent.ConcurrentLinkedDeque;

        /**
        * 输出长度>len的字符串的任意组合,比如String s = "abc",输出为"ab","ba","ac","ca"……字符的排列

        * @author TongXueQiang
        * @date 2016/03/01
        * @since JDK 1.7
        */
        public class LetterCombation {
        // 存放字符的原始队列
        private static Queue<Character> queue = new ConcurrentLinkedDeque<Character>();
        // 组合过程中产生的第一个结果集
        private static Queue<List<String>> firstResult = new ConcurrentLinkedDeque<List<String>>();
        // 最终结果集
        private static Set<String> finalResult = new HashSet<String>();
        // 组合的层级数,从2开始
        private static int level = 2;

        /**
        * 任意字母组合

        * @param word
        * @param len
        * @return
        */
        public Set<String> outputLetterCombina(String word, int len) {
        if (word == null || word.equals("")) {
        return null;
        }
        // 1.把word加入到原始队列中
        init(word);
        // 2.产生第一次结果集
        firstResult = outputFirstCombination();
        // 3.循环进行下一层级的组合,并得到最终结果集
        finalResult = outputCombination(firstResult, level, word);
        // 4.去除不符合期望的字符组合
        return removeUnexpectedLetterComb(finalResult, len);
        }

        /**
        * 去除不符合期望的字符串
        * @param finalResult2
        * @return
        */
        private Set<String> removeUnexpectedLetterComb(Set<String> result, int len) {
        List<String> deleteList = new ArrayList<String>();
        for (String s : result) {
        if (s.length() <= len) {
        deleteList.add(s);
        }
        }
        result.removeAll(deleteList);
        return result;
        }

        /**
        * 产生原始队列

        * @param word
        */
        public Queue<Character> init(String word) {
        if (word == null || word.equals("")) {
        return null;
        }

        for (int i = 0;i < word.length();i++) {
        Character c = Character.valueOf(word.charAt(i));
        if (c.equals(' ') || c.equals(' ') || c.equals(' ') || c.equals(' ')) {
        continue;
        }
        queue.add(c);
        }

        return queue;
        }

        /**
        * 倒置字符串

        * @param word
        * @return
        */
        public String reverse(String word) {
        StringBuffer result = new StringBuffer();
        for (int i = word.length() - 1; i >= 0; i--) {
        result.append(word.charAt(i));
        }
        return result.toString();
        }

        /**
        * 倒置字符串,比如abc,倒置后为cab,abcd,倒置后为dabc……

        * @param word
        * @return
        */
        public String reverseCombination(String word) {
        char s[] = word.toCharArray();
        List<String> ss = new ArrayList<String>();

        StringBuffer sb = new StringBuffer();
        SoftReference<StringBuffer> srf = new SoftReference<StringBuffer>(sb);

        for (int i = 0; i < s.length - 1; i++) {
        sb.append(s[i]);
        }
        // 把除最后一个字符意外的全部字符串加载到list中
        ss.add(sb.toString());
        sb = null;

        sb = new StringBuffer();
        sb.append(s[s.length - 1]);
        // 把最后一个字符加载到list中
        ss.add(sb.toString());

        Collections.reverse(ss);// 倒置处理
        sb = null;

        sb = new StringBuffer();
        for (String s0 : ss) {
        sb.append(s0);
        }

        // 输出最后结果
        return sb.toString();
        }

        /**
        * 输出长度为2的字母组合,作为第一个结果集,以备后续的组合使用
        * @return
        */
        public Queue<List<String>> outputFirstCombination() {
        StringBuffer sb = null;
        List<String> cell = null;

        SoftReference<List<String>> srf = new SoftReference<List<String>>(cell);
        SoftReference<StringBuffer> srf0 = new SoftReference<StringBuffer>(sb);

        // 1.依次取出第一个字符,与剩下的字符组合
        char ch = queue.poll();

        for (char cha : queue) {
        cell = new ArrayList<String>();
        sb = new StringBuffer();
        sb.append(ch).append(cha);

        // 加入到cell中
        cell.add(sb.toString());
        cell.add(reverse(sb.toString()));

        // 把cell加入到首个结果集中
        firstResult.add(cell);

        sb = null;
        cell = null;
        }

        // 递归终止条件
        if (queue.size() != 1) {
        outputFirstCombination();
        }

        return firstResult;
        }

        /**
        * 输出组合,循环对输入的结果集中的每个cell处理,产生新的cell,然后把新的cell加载到中间结果集中,最后返回最后结果

        * @param handleResult
        * @param level
        * @return
        */
        public Set<String> outputCombination(Queue<List<String>> inputResult, int level, String word) {
        // 定义新的中间结果集
        Queue<List<String>> middleResult = new ConcurrentLinkedDeque<List<String>>();
        SoftReference<Queue<List<String>>> srf = new SoftReference<Queue<List<String>>>(middleResult);

        StringBuffer sb = null;

        // 1.把handleResult加入到最终结果集中
        finalResult = addToFinalResult(inputResult);

        // 2.清空队列
        queue.clear();

        // 3.对输入的结果集进行处理,进行下一层级的组合
        List<String> cell = inputResult.poll();

        while (cell != null) {
        // 新的cell
        List<String> newCell = null;

        // ①.初始化队列
        queue = init(word);

        // ②.从cell中取出第一个字符串,然后去除原始队列中与之匹配的字符串
        removeStrFromOriginalQueue(cell);

        // ③.cell与原始队列中剩下的字符串进行组合,产生新的cell
        originalQueueToCellCombination(newCell, cell, middleResult, sb);

        // ④.清空队列
        queue.clear();

        // ⑤.下一个单元
        cell = inputResult.poll();
        }

        inputResult = null;
        ++ level;// 4.层级叠加

        // 5.递归终止条件
        if (level != word.length()) {
        outputCombination(middleResult, level, word);
        }

        // 6.处理最后的中间结果集
        addToFinalResult(middleResult);
        middleResult = null;

        return finalResult;
        }

        /**
        * cell与原始队列中剩下的字符串进行组合,产生新的cell

        * @param newCell
        * @param cell
        * @param middleResult
        * @param sb
        */
        private void originalQueueToCellCombination(List<String> newCell, List<String> cell,
        Queue<List<String>> middleResult, StringBuffer sb) {
        SoftReference<List<String>> srf = new SoftReference<List<String>>(newCell);
        SoftReference<StringBuffer> srf0 = new SoftReference<StringBuffer>(sb);

        for (char c : queue) {
        newCell = new ArrayList<String>();
        for (String s : cell) {
        sb = new StringBuffer();
        sb.append(s).append(c);
        newCell.add(sb.toString());
        newCell.add(reverseCombination(sb.toString()));
        sb = null;// 不用的对象
        }
        // 把newCell加载到中间结果集中
        middleResult.add(newCell);
        newCell = null;
        }
        }

        /**
        * 从cell中取出第一个字符串,在原始队列中移除与之匹配的字符串

        * @param cell
        */
        private void removeStrFromOriginalQueue(List<String> cell) {
        String firstWord = cell.get(0);
        for (int i = 0; i < firstWord.length(); i++) {
        queue.remove(firstWord.charAt(i));
        }
        }

        /**
        * 输出到最终结果集中

        * @param middleResult
        */
        public Set<String> addToFinalResult(Queue<List<String>> middleResult) {
          for (List<String> cell : middleResult) {
        finalResult.addAll(cell);
        }
        return finalResult;
        }
        }

        程序在运行时,原始数据[abcdefgh]产生的排列,输出有109592个字符串,运行有点缓慢,再加一个字符i,输出结果通过计算预计有100多万个字符串,程序几乎无法运行。运行时,输入以下参数:-Xms1700m -Xmx1700m -Xmn900m  -XX:PermSize=128m -XX:MaxPermSize=128m -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5  -XX:CMSInitiatingOccupacyFraction=85 -Xloggc:E:/gc.log。通过GC日志以及VisualVM监视,看到,当数据量非常大的时候,程序几乎都浪费在GC和FullGC上面。原因是内存不足导致,内存不足的原因是程序本身的问题。 一个好的算法会考虑时间复杂度和空间复杂度。在程序运行时,占用太多的内存,导致内存不足,会导致过多的GC和Full GC,也就是说,当数据量达到一定程度的时候,程序的绝大部分时间都浪费在GC上,性能十分低下。在写算法的时候,应该极力避免在循环体中频繁初始化对象,尤其是比较大的对象。这是经验问题。改变数据结构,减少内存占有量,减少GC时间,这就是优化的方向!
      6. 目前在中文分词领域里面,IK分词的性能是比较低下的,IK分词采用的数据结构是:当子节点小于3的用对象数组来存储,当大于3的时候,用HashMap来存储,以节约内存。但是,当字典中的汉字非常多的时候,维护太多的HashMap也不是一件很好的事情。采用三叉树这种数据结构,有效地解决了内存溢出问题。目前solr的搜索推荐系统的核心思想就是基于三叉树的前缀匹配算法,比如TSTLookup,JaSPellLookup。
  • 相关阅读:
    js数字格式化(加千分位逗号)
    [css]解决iframe在ios设备上无法滚动
    判断当前是否在微信浏览器环境
    TortoiseGit 提交代码每次需要输入用户名和密码?
    如何编写jQuery插件
    函数作用域
    HTTP动词
    如何减少全局变量污染?
    mysql表大字段最大长度限制设置
    update left join 多表关联更新
  • 原文地址:https://www.cnblogs.com/txq157/p/5239936.html
Copyright © 2020-2023  润新知