22.1.18 前缀树,贪心算法,暴力递归,堆

22.1.18 前缀树,贪心算法,暴力递归,堆

1.前缀树

简介:

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

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

  1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。

  2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。

  3. 每个节点的所有子节点包含的字符都不相同。

假设有b,abc,abd,bcd,abcd,efg,hii 这6个单词,我们构建的树就是如下图这样的:

img

 

code:

public static class TrieNode {
public int path;//path表示以某些字母组合开头的单词的个数
public int end;//end可以表示整个单词的个数
public TrieNode[] nexts;

public TrieNode() {
path = 0;
end = 0;
nexts = new TrieNode[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();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';//确定某个字母的下标
if (node.nexts[index] == null) {
node.nexts[index] = new TrieNode();
}
node = node.nexts[index];
node.path++;
}
node.end++;
}

public void delete(String word) {
if (search(word) != 0) {
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (--node.nexts[index].path == 0) {
node.nexts[index] = null;
return;
}
node = node.nexts[index];
}
node.end--;
}
}

       //查询单词的个数
public int search(String word) {
if (word == null) {
return 0;
}
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.nexts[index] == null) {
return 0;
}
node = node.nexts[index];
}
return node.end;
}

       //查询某些字母开头的单词的个数。
public int prefixNumber(String pre)
      {
if (pre == null) {
return 0;
}
char[] chs = pre.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.nexts[index] == null) {
return 0;
}
node = node.nexts[index];
}
return node.path;
}

 

2.贪心算法

(1)贪心算法的核心在于找对贪心策略,一般多涉及排序和容器的使用(比如队列,栈,小根堆和大根堆等等),贪心算法的正确性一般很难通过数学的方法证明,凭直觉产生贪心策略,在通过一个暴力穷举等方法利用对数器来证明贪心算法的正确性。贪心策略通过大量做题和总结来培养感觉。

(2)贪心算法的在笔试时的解题套路

  • 实现一个不依靠贪心策略的解法X,可以用最暴力的尝试

  • 脑补出贪心策略A、贪心策略B、贪心策略C...

  • 用解法X和对数器,去验证每一个贪心策略,用实验的方式得知哪个贪心策略正确

  • 不要去纠结贪心策略的证明

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

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

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

(4)例子:

  • 给定一个字符串类型的数组strs,找到一种拼接方式,使得把所有字符串拼起来之后形成的 字符串具有最小的字典序(在查阅字典的时候谁排在前面谁的字典序就小,例如abc的字典序就小于bcd的字典序,c(把c补成c00)的字典序小于cde的字典序)。

  • code:

    public static class MyComparator implements Comparator<String>
{

public int compare(String a, String b)
{
return (a+b).compareTo(b+a);
}

}

public static String lowestString(String[] args)
{
if(args.length==0 || args == null)
return "";
Arrays.sort(args,new MyComparator());
String res = "";
for(int i=0;i<args.length;i++)
{
res += args[i];
}
return res;
}

public static void main(String[] args)
{
String[] strs1 = { "abc", "ai", "ap", "bw", "jabw" };
System.out.println(lowestString(strs1));

String[] strs2 = { "bb", "a" };
System.out.println(lowestString(strs2));

}

3.暴力递归

(1)n皇后问题

  • 描述:将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能共行,共列,共斜线。

    • 暴力递归解决:

      • 申请一个数组record[]用来记录每一行皇后放在了哪一列,比如record[0]=1,表示棋盘的第0行的第一列放了一个皇后。判断第i行的皇后(假设在j列)和第i+1行的皇后是否共列也可以利用record[]数组,k=0,j==record[k]?,k++,判断两皇后是否共斜线可以利用横纵坐标差的绝对值是否相等来判断。

        • code:

            public static int process1(int n)
        {
        if(n<=1)
        return 0;
        int[] record = new int[n];
        return process2(0,record,n);
        }

        public static int process2(int i,int[] record,int n)
        {
        if(i == n)
        return 1;
        int res = 0;
        for(int j=0;j<n;j++)
        {
        if(isValid(record,i,j))
        {
        record[i] = j;
        res+=process2(i+1,record,n);
        }
        }
        return res;
        }

        public static boolean isValid(int[] record,int i,int j)
        {
        for(int k = 0;k<i;k++)
        {
        if(j == record[k] || Math.abs(record[k]-j) == Math.abs(i-k))
        return false;
        }
        return true;
        }

        public static void main(String[] args)
        {
        System.out.println(process1(4)) ;
        }

       

    • 利用位运算优化(检查放入皇后的位置是否合法,常数时间优化)(32版本,超过32个皇后把int改为long):

      • int型变量upperLim用来申请一个后n为全为1,其余(32-n)位全为0的二进制数,这样便于确定在哪些位置是皇后,而不是在32位上胡试。刚开始列限制二进制位上全为0,因为没有填皇后,每填一个皇后在列限制上更新一个1,直到列限制与初始的upperLim(n皇后都填入对应的二进制情况)相同时,则此方案可行。

      • code

          public static int process1(int n) {
      if (n < 1 || n > 32) {
      return 0;
      }
      int upperLim = n == 32 ? -1 : (1 << n) - 1;//二进制数
      return process2(upperLim, 0, 0, 0);
      }

      public static int process2(int upperLim, int colLim, int leftDiaLim,
      int rightDiaLim)
             //列限制,左斜线限制,右斜线限制
             //1的位置不能放皇后,0的位置可以
      {
      if (colLim == upperLim) //列限制二进制位上的1与初始限制所有位置上的1相同
      {
      return 1;
      }
      int pos = 0;//记录放皇后的二进制位
      int mostRightOne = 0;
      pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim));
      //三个限制用或连接得到的二进制数表示当前行的那些二进制位不能能放皇后
             //colLim | leftDiaLim | rightDiaLim 总限制,1不可以放皇后,0能
             //~(colLim | leftDiaLim | rightDiaLim)) 1可以放皇后,0不可以
             //upperLim & (~(colLim | leftDiaLim | rightDiaLim)) 得到能够选择皇后的位置(可画图分析)
             int res = 0;
      while (pos != 0) {
      mostRightOne = pos & (~pos + 1);//得到候选皇后中最右边的1,其余都为0,从最右边的1开始试皇后,试完后下一句把这个试点更新为0,直到所有的1试完,变成n个0的二进制数。
      pos = pos - mostRightOne;
      res += process2(upperLim, colLim | mostRightOne,
      (leftDiaLim | mostRightOne) << 1,
      (rightDiaLim | mostRightOne) >>> 1);
                 //左移是更新左限制条件,右移更新右限制条件
      }
      return res;
      }

       

4.小根堆和大根堆的组合使用

  • 在贪心策略的实现中(有关花费和利润的问题),可以用小根堆表示花费,用大根堆表示利润。这样便于实现花费最小和利润最大的贪心策略。

  • 用户不断的输入数据,动态的返回这组数据的中位数:

    • 基本步骤:

    (1)判断当前输入的数是否小于等于大根堆堆顶的数

    (2)true 当前数进入大根堆

    false 当前数进入小根堆

    (3)判断大根堆和小根堆元素差的绝对值是否大于等于2

    (4)true 大根堆堆顶元素进入小根堆

    false 继续接收下一个数

     

  •  

posted @ 2022-01-18 20:58  张满月。  阅读(188)  评论(0编辑  收藏  举报