算法设计与分析 前缀树与贪心算法

简介: 算法设计与分析 前缀树与贪心算法

前缀树与贪心算法

前缀树

前缀树介绍

例题:一个字符串类型的数组arr1,另一个字符串类型的数组arr2。

(1)arr2中有哪些字符,是在arr1中出现的

(2)arr2中有哪些字符,是在arr1中的某个字符串的前缀出现

(3)arr2中出现次数最多的前缀

  • 经典前缀树:字符串写在“路”上
  • image.png

数据结构

前缀树节点
    public static class TrieNode{
        public int pass; //表示形成前缀树时,该节点经过的次数
        public int end; //该节点是多少个字符串的结尾节点
        public TrieNode[] nexts; //类似于HashMap<char, Node> nexts; 当字符特别多的时候可以使用HashMap
        public TrieNode(){
            //构造函数初始化
            pass = 0;
            end = 0;
            //nexts[0] == null 没有走向'a'的路
            //nexts[0] != null 有走向'a'的路
            //……
            //next[25] != null 有走向'z'的路
            nexts = new TrieNode[26];
        }
    }
    public static class Trie{
        //头节点
        private TrieNode root;
        public Trie(){
            //初始化
            root = new TrieNode();
        }
    //插入,删除,查找……方法
   }

9c5484319b884b348b9786a8b0feacd6.png

image.png

使用场景

  1. 字符串的查找
  2. 部分字符的在字符串中作为前缀的使用次数

主要方法

添加字符串
    public static class Trie{
        //头节点
        private TrieNode root;
        public Trie(){
            //初始化
            root = new TrieNode();
        }
        public void insert(String word){
            //沿途的pass++,结束节点的end++
            if (word == null){
                return;
            }
            //将字符串转化为字符数组
            char[] chars = word.toCharArray();
            TrieNode node = root;
            root.pass++; //通过根节点
            int index = 0;
            for (char ch : chars) {
                //遍历字符数组
                index = ch - 'a'; //由字符,对应到路
                if (node.nexts[index] == null) {
                    //判断该路是否存在
                    node.nexts[index] = new TrieNode();
                }
                node = node.nexts[index]; //移动node
                node.pass++;
            }
            node.end++; //最后一个节点
        }
    }
检查字符串加入次数
        public int search(String word){
          //查看结束节点的end
            if (word== null){
                return 0;
            }
            char[] chars = word.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (char ch : chars){
                index = ch - 'a';
                if (node.nexts[index] == null){
                    //不存在该字符串
                    return 0;
                }
                node = node.nexts[index];
            }
            return node.end;
        }
检查以pre为前缀的字符串个数
        public int prefixNumber(String pre){
          //查看结束节点的pass
            if (pre == null){
                return 0;
            }
            char[] chars = pre.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (char ch : chars){
                index = ch - 'a';
                if (node.nexts[index] == null){
                    //不存在该前缀
                    return 0;
                }
                node = node.nexts[index];
            }
            return node.pass;
        }
删除字符串
        public void delete(String word){
            //沿途节点的pass--,结束节点的end--
            //如果沿途节点--pass == 0,直接释放该节点及其以后节点
            if (search(word) == 0){
                return;
            }
            char[] chars = word.toCharArray();
            TrieNode node = root;
            root.pass--;
            int index = 0;
            for (char ch : chars){
                index = ch - 'a';
                if (--node.nexts[index].pass == 0){
                  //java写法,C++要遍历
                    node.nexts[index] = null;
                }
                node = node.nexts[index];
            }
            node.end--;
        }

贪心算法

概述

  • 在某一个标准下,优先考虑满足标准的样本,最后考虑最不满足标准的样本,最终得到一个答案的算法,叫作贪心算法
  • 也就是说,不从整体最优上加以考虑,所做出的是某种意义上的局部最优解
  • 局部 —?--->整体最优

题目一:会议室安排(任务安排)

image.png

  • 贪心策略一:先安排开始时间早的?错误:只可能是局部最优不可能全局最优
  • 贪心策略二:先安排持续时间短的?错误:若短时间的任务同时于多个别的任务时间重合则会导致会议室使用效率下降‘
  • 贪心策略三:先安排结束时间早的?正确
    public static class Program{
        //项目类
        public int start; //开始时间
        public int end; //结束时间
        public Program(int start, int end){
            //初始化
            this.start = start;
            this.end = end;
        }
    }
    public static class ProgramComparator implements Comparator<Program>{
        //比较器
       @Override
        public int compare(Program o1, Program o2){
            return o1.end - o2.end;
        }
    }
    public static int bestArrange(Program[] programs, int timePoint){
        //timePoint:开始时间 + 目前时间
        //按照结束时间对项目进行排序
        Arrays.sort(programs, new ProgramComparator());
        int result = 0; //记录项目个数
        for (Program program : programs){
            //按照结束时间的次序遍历 
            if (timePoint <= program.start){
                //当目前的时间小于项目开始的时间
                result++;
                timePoint = program.end;
            }
        }
        return result;
    }

题目二:分割金条(哈夫曼编码)

image.png

  • 贪心策略:将数组中的数字组成哈夫曼树,整个哈夫曼树就是分割的最小代价
  • 技巧:使用小根堆构造哈夫曼树
    public static int lessMoney(int[] arr){
        //使用小根堆构造哈夫曼树
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        for (int i : arr){
            //将数组加入小根堆
            heap.add(i);
        }
        int sum = 0;
        while (!heap.isEmpty()){
            int temp = heap.poll() + heap.poll();
            sum += temp;
            heap.add(temp);
        }
        return sum;
    }

题目三:投资问题

image.png

  • 贪心策略:使用初始资金的大小先挑选出初始资金大于花费的项目,再从这些项目中挑选出利润最大的项目优先去做,每次做完项目积累到资金后立刻重新挑选
  • 技巧:先建立costs花费数组的小根堆(类似于锁)解锁花费小于资金的项目到profits建立的大根堆中,从大根堆中选取项目
    public static class Project{
        public int cost; //花费
        public int profit; //利润
        public Project(int cost, int profit){
            //初始化
            this.cost = cost;
            this.profit = profit;
        }
    }
    public static class minCostComparator implements Comparator<Project>{
        //花费的比较器
        @Override
        public int compare(Project o1, Project o2){
            return o1.cost - o2.cost;
        }
    }
    public static class maxProfitComparator implements Comparator<Project>{
        //利润的比较器
        @Override
        public int compare(Project o1, Project o2){
            return o2.profit - o1.profit;
        }
    }
    public static int maxMoney(int k, int m, int[] costs, int[] profits){
        //花费的小根堆
        PriorityQueue<Project> minCosts= new PriorityQueue<>(new minCostComparator());
        //利润的大根堆
        PriorityQueue<Project> maxProfits = new PriorityQueue<>(new maxProfitComparator());
        for (int i = 0; i < costs.length; i++){
            //项目加入花费的小根堆
            minCosts.add(new Project(costs[i], profits[i]));
        }
        for (int i = 0; i < k; i++){
            //完成k个项目
            while (!minCosts.isEmpty() && minCosts.peek().cost < m){
                //解锁满足条件的项目到利润的大根堆
                maxProfits.add(minCosts.poll());
            }
            if (maxProfits.isEmpty()){
                //能够实现的项目已经全部完成
                return m;
            }
            //选择利润中最大的一个
            m += maxProfits.poll().profit;
        }
        return m;
    }

题目四:数据流求中位数(大根堆与小根堆的结合)

image.png

  • 思路:加入数据时一分为二,较大的一部分,较小的一部分,便可以随时得到中位数
  • 技巧:
    (1)准备一个大根堆和一个小根堆
    (2)每次加入数字时先与大根堆的堆顶进行元素的比较(当前元素 <= 大根堆堆顶)
    (3)若是则进入大根堆,若否则进入小根堆
    (4)进行大小根堆规模的比较,若规模的大小差值达到2则将规模较大的堆顶加入规模较小的堆
    (5)重复2,3,4则较小的部分在大根堆中,较大的部分在小根堆中,中位数便可通过两个堆顶获得
    public static class maxInt implements Comparator<Integer>{
        //大根堆的比较器
        @Override
        public int compare(Integer o1, Integer o2){
            return o2 - o1;
        }
    }
    public static int getMedian(int[] nums){
        //数据流num返回中位数
        //小根堆
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        //大根堆
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new maxInt());
        //第一个元素进入大根堆
        maxHeap.add(nums[0]);
        for (int i = 1; i < nums.length; i++){
            //遍历数组
            if (nums[i] <= maxHeap.peek()){
                //判断加入大根堆还是小根堆
                maxHeap.add(nums[i]);
            }
            else {
                minHeap.add(nums[i]);
            }
            if (maxHeap.size() - minHeap.size() == 2){
                //大根堆的规模比小根堆的规模大 2
                minHeap.add(maxHeap.poll());
            }
            else if (minHeap.size() - maxHeap.size() == 2){
                //小根堆的规模比大根堆的规模大 2
                maxHeap.add(minHeap.poll());
            }
        }
        if (minHeap.size() == maxHeap.size()){
            //nums数组为偶数个
            return (minHeap.poll() + maxHeap.poll())/ 2;
        }
        else if (minHeap.size() - maxHeap.size() == 1){
            //小根堆多一个
            return minHeap.poll();
        }
        else {
            //大根堆多一个
            return maxHeap.poll();
        }
    }


目录
相关文章
|
2月前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
92 4
|
3天前
|
缓存 算法 搜索推荐
Java中的算法优化与复杂度分析
在Java开发中,理解和优化算法的时间复杂度和空间复杂度是提升程序性能的关键。通过合理选择数据结构、避免重复计算、应用分治法等策略,可以显著提高算法效率。在实际开发中,应该根据具体需求和场景,选择合适的优化方法,从而编写出高效、可靠的代码。
15 6
|
26天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
59 1
|
1月前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
53 2
|
2月前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
2月前
|
算法 Java C++
【贪心算法】算法训练 ALGO-1003 礼物(C/C++)
【贪心算法】算法训练 ALGO-1003 礼物(C/C++)
【贪心算法】算法训练 ALGO-1003 礼物(C/C++)
|
2月前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
3月前
|
算法 搜索推荐 开发者
别再让复杂度拖你后腿!Python 算法设计与分析实战,教你如何精准评估与优化!
在 Python 编程中,算法的性能至关重要。本文将带您深入了解算法复杂度的概念,包括时间复杂度和空间复杂度。通过具体的例子,如冒泡排序算法 (`O(n^2)` 时间复杂度,`O(1)` 空间复杂度),我们将展示如何评估算法的性能。同时,我们还会介绍如何优化算法,例如使用 Python 的内置函数 `max` 来提高查找最大值的效率,或利用哈希表将查找时间从 `O(n)` 降至 `O(1)`。此外,还将介绍使用 `timeit` 模块等工具来评估算法性能的方法。通过不断实践,您将能更高效地优化 Python 程序。
70 4
|
3月前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
65 1
|
2月前
|
算法
PID算法原理分析及优化
【10月更文挑战第6天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。