前缀树和贪心算法

1.前缀树

力扣地址

Trie树,又叫字典树、前缀树(Prefix Tree)、单词查找树或键树,是一种多叉树结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高

Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的

Trie树也有它的缺点,Trie树的内存消耗非常大。

基本结构如下图 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BzEz8Kg8-1631554179776)(C:%5CUsers%5Cliuyuansong%5CDesktop%5C%E7%AC%94%E8%AE%B0%5C%E5%89%8D%E7%BC%80%E6%A0%91%E5%92%8C%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95.assets%5C1617808294645.png)]

上图是一棵Trie树,表示了关键字集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。

Trie树的基本性质:
①根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
②从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
③每个节点的所有子节点包含的字符互不相同,相同了当然可以复用。
④从第一字符开始有连续重复的字符只占用一个节点,比如上面的to,和ten,中重复的单词t只占用了一个节点。

要想实现前缀树,需要三个变量,pass,end,nexts。

int pass 表示通过该节点的次数,可以判断所有加入的字符串中有几个是以’传进来的pre’为前缀的
int end 表示以该节点为结尾的字符串有多少个
TrieNode[] nexts;//存放下一个节点的数组,之所以为节点数组结构,不是直接下一个节点,是因为该字符后面可能不止连了一个字符

eg: next[0] ==null 没有走向’a’的路 next[0] !=null 有走向’a’的路
eg: next[26] ==null 没有走向’z’的路 next[26] !=null 有走向’z’的路

public class TrieTree {
   
    //树节点
    public static class TrieNode {
   
        int pass;  //通过该节点的次数,可以判断一个字符串的前缀走过了多少次
        int end; //以该节点为结尾的字符串有多少个
        TrieNode[] nexts;//存放下一个节点的数组,之所以为节点数组结构,不是直接下一个节点,是因为该字符后面可能不止连了一个字符
        //eg: next[0] ==null 没有走向'a'的路 next[0] !=null 有走向'a'的路
        //eg: next[26] ==null 没有走向'z'的路 next[26] !=null 有走向'z'的路
		
        TrieNode() {
   //构造函数
            pass = 0;
            end = 0;
            nexts = new TrieNode[26];   //初始化数组最多26个字母
        }
    }

    public static class Trie{
   
        private TrieNode root;
        public Trie(){
   
            root = new TrieNode(); //初始化根节点,不包含数据 让它变成傀儡节点
        }

        //建立前缀树的过程
        public void insert(String word){
   
            //字符串为空 不处理
            if (word == null) return; 
            char[] chs = word.toCharArray();
            //定义一个节点指向root
            TrieNode node = root;
            //一旦要开始在root上挂东西,延展路了,就先++,这样root上的pass值就是输入了多少个单词
            node.pass++;
            for (char ch : chs) {
   
             //把字符转换成对应走哪条路 这里减去'a'的ASCII,这样a的index = 0,b的index = 1,以此类推
                int index = ch - 'a';
                //如果该节点下面没有这个节点,也就是没有这条路了嘛,就新建一条路
                if (node.nexts[index] == null)
                    node.nexts[index] = new TrieNode();
                
                /*如果有,就复用,然后移到该next指定的节点上
                  总结就是:有节点直接移动到下一个(向下延伸),没有的话新建一个再移动*/
                node = node.nexts[index];  
                
                node.pass++; //每延伸一次,沿途节点属性上的pass++
            }
            node.end++;  //循环结束后在尾节点end++ 表示以该节点结尾的单词有多少个
        }

        //查找单词加入过几次
        public int search(String word){
   
            if (word == null) return 0;
            char[] chs = word.toCharArray();
            TrieNode node = root;
            for (char ch : chs) {
   
                int index = ch - 'a';  //把字符a,b,c..转换成对应的0,1,2,3..
                //当下一个点没有对应上(成null)了,就返回0  比如有个路是abc,你要查abcd,这个路没有(只要中间断了),返回0
                if (node.nexts[index] == null)   return 0;
                
                node = node.nexts[index];
            }
            return node.end; //走到最后的end就是加入的次数
        }

        //删除一条路  就是把沿途节点上的pass--,到最后一个节点了,再end--
        public void delete(String word){
   
            if (search(word)!=0){
    //先查一遍,有这条路才可能继续删除
                char[] chs = word.toCharArray();
                TrieNode node =root;
                node.pass--; //先让根节点pass-- 因为删除了一个单词嘛
                for (char ch : chs) {
   
                    int index = ch - 'a';
                    
                    /*然后把对应的节点的pass进行 -1的操作,减完之后看看是否等于0,如果为0了,说明那个节点没有人走向它了,就需要设为null,把后面的都断开*/
                    if (--node.nexts[index].pass == 0) {
   
                        node.nexts[index] = null;
                        return;
                    }
                    node = node.nexts[index];
                }
                //所有的都循环完,node到底了,要记得把end--
                node.end--;
            }
        }

        //所有加入的字符串中,有几个是以'pre'为前缀的
        public int preNums(String pre){
   
            if (pre == null) return 0;
            char[] chs = pre.toCharArray();
            TrieNode node = root;
            for (char ch : chs) {
   
                int index = ch - 'a';  //把字符a,b,c..转换成对应的0,1,2,3..
                if (node.nexts[index] == null) {
     //下一个点没有对应上(成null)了,就返回0
                    // 比如有个路是abc,你要查前缀是abcd,这个路没有(只要中间断了),返回0
                    return 0;
                }
                node = node.nexts[index];
            }
            //走到最后的pass就是以pre为前缀的路的个数
            return node.pass;
        }
    }
}

2.贪心算法

贪心算法的基本思路:
1.建立数学模型来描述问题。
2.把求解的问题分成若干个子问题。
3.对每一子问题求解,得到子问题的局部最优解。
4.把子问题的解局部最优解合成原来解问题的一个解。

贪心策略适用的前提是:局部最优策略能导致产生全局最优解,具有传递性。

贪心策略在实现时,经常使用到的技巧:

1,根据某标准建立一个比较器来排序

2,根据某标准建立一个比较器来组成堆

贪心算法的一般框架:

选择函数select:即贪心策略,这是贪心法的关键,它指出哪个候选对象最有希望构成问题的解,选择函数通常和目标函数有关。例如,在找零钱问题中,贪心策略就是在候选集合中选择面值最大的货币。
约束函数constraint:检查解集合中加入一个候选对象是否满足约束条件。例如,在找零钱问题中,约束函数是每一步选择的货币和已付出的货币相加不超过应找零钱。

Greedy(C) //C是问题的输入集合即候选集合
{
   
    S={
    }; //初始解集合为空集
    while (not solution(S)) //集合S没有构成问题的一个可行解
    {
   
      x=select(C); //在候选集合C中做贪心选择
      if constraint(S, x) //判断集合S中加入x后是否满足约束条件
           S=S+{
   x};
           C=C-{
   x};
     }
     return S;
}

由所有解元素组合成问题的一个可行解;

1.字符串的最小的字典序

描述:给定一个字符串类型的数组strs,找到一种拼接方式,使得把所有字符串拼起来之后形成的字符串具有最小的字典序。

/*比如  abc > aaa  c < cda   c >ba
  错的想法: a,b  a<=b,  a放前 否则b放前  反例: b,ba  如果按照这个意思这么想 拼接后应该是 bba  但实际上是bab最小
  正确: a,b   a.b<=b.a  a放前,否则b放前  (.是拼接的意思)
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值