java数据结构与算法5---前缀树&贪心策略

java数据结构与算法5---前缀树&贪心策略


介绍前缀树 

前缀树:计算机科学中,trie,又称前缀树字典树,是一种有序,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定(边)。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。如下图:


前缀树算法

题目一: arr2中有哪些字符,是arr1中出现的?请打印。

题目二: arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印。

题目三:arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印arr2中出现次数最大的前缀。

思路:这三题是从前缀树整体出发来考虑,涉及到前缀树的插入、节点的数量记录、前缀树的删除、前缀树的查询、前缀查询。

代码实现如下:

//前缀树算法
public static class TrieNode {
		public int path;
		public int 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;
		}
	}

介绍贪心策略

贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。

解决最优解问题一般都涉及到贪心算法,贪心算法最关键的是贪心策略。


贪心策略实战例题 

题目四:一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的 金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30 60. 金条要分成10,20,30三个部分。 如果, 先把长度60的金条分成10和50,花费60 再把长度50的金条分成20和30,花费50 一共花费110铜板。但是如果, 先把长度60的金条分成30和30,花费60 再把长度30金条分成10和20,花费30 一共花费90铜板。输入一个数组,返回分割的最小代价

思路:最小代价题是Huffman树的拓展,也就是Huffman树的逆向思维。先简单介绍一下Huffman树---给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。而这题要先顺着分割的金条来考虑,利用Huffman Tree来组合,最后反着来输出即可。

代码实现如下:

/*
*Huffman Tree变形算法实现
*大根堆小根堆的生成
*/
public static int lessMoney(int[] arr) {
	PriorityQueue<Integer> pQ = new PriorityQueue<>();//优先级队列就是堆,且默认是小根堆
	for (int i = 0; i < arr.length; i++) {
		pQ.add(arr[i]);
	}
	int sum = 0;
	int cur = 0;
	while (pQ.size() > 1) {
		cur = pQ.poll() + pQ.poll();//依次抛出最小值并相加计算最低花费
		sum += cur;
		pQ.add(cur);
	}
	return sum;
}

//小根堆比较器
public static class MinheapComparator implements Comparator<Integer> {
	@Override
	public int compare(Integer o1, Integer o2) {
		return o1 - o2; // < 0  o1 < o2  负数
	}
}

//大根堆比较器
public static class MaxheapComparator implements Comparator<Integer> {
	@Override
	public int compare(Integer o1, Integer o2) {
		return o2 - o1; // <   o2 < o1
	}
}

public static void main(String[] args) {
	// solution
	int[] arr = { 6, 7, 8, 9 };
	System.out.println(lessMoney(arr));

	int[] arrForHeap = { 3, 5, 2, 7, 0, 1, 6, 4 };

	// min heap---默认小priorityQueue是小根堆
	PriorityQueue<Integer> minQ1 = new PriorityQueue<>();
	for (int i = 0; i < arrForHeap.length; i++) {
		minQ1.add(arrForHeap[i]);
	}
	while (!minQ1.isEmpty()) {
		System.out.print(minQ1.poll() + " ");
	}
	System.out.println();

	// min heap use Comparator
	PriorityQueue<Integer> minQ2 = new PriorityQueue<>(new MinheapComparator());
	for (int i = 0; i < arrForHeap.length; i++) {
		minQ2.add(arrForHeap[i]);
	}
	while (!minQ2.isEmpty()) {
		System.out.print(minQ2.poll() + " ");
	}
	System.out.println();

	// max heap use Comparator
	PriorityQueue<Integer> maxQ = new PriorityQueue<>(new MaxheapComparator());
	for (int i = 0; i < arrForHeap.length; i++) {
		maxQ.add(arrForHeap[i]);
	}
	while (!maxQ.isEmpty()) {
		System.out.print(maxQ.poll() + " ");
	}
}

题目五:输入: 参数1,正数数组costs:参数2,正数数组profits:参数3,正数k:参数4,正数m:参数5,costs[i]表示i号项目的花费 ,profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润), k表示你不能并行、只能串行的最多做k个项目, m表示你初始的资金。说明:你每做完一个项目,马上获得的收益,可以支持你去做下 一个项目。输出: 你最后获得的最大钱数。(提示:m>cost[i]才能做i项目)

思路:题目要求的是最大钱数,我们就需要在满足能做的项目中找去利润最大的项目先做。于是采用小根堆与大根堆配合来完成任务,我们只需要在满足m>cost[i]且项目总数n<k的条件下,利用小根堆将满足的项目加入大根堆中,抛出最大值后,项目总数n++,m+利润;由于m的更新就会导致有新的项目被激活加入大根堆,然后再抛出最大值直至结束。

代码实现如下:

//大小根堆结合解决最大利润问题
public static class Node {
	public int p;
	public int c;

	public Node(int p, int c) {
		this.p = p;
		this.c = c;
	}
}

public static class MinCostComparator implements Comparator<Node> {
	@Override
	public int compare(Node o1, Node o2) {
		return o1.c - o2.c;
	}
}

public static class MaxProfitComparator implements Comparator<Node> {
	@Override
	public int compare(Node o1, Node o2) {
		return o2.p - o1.p;
	}
}
/*
*求最大利润:利用大根堆&小根堆
*小根堆:按cost排序
*大根堆:按profit排序
*在小根堆中将cost低的激活放入大根堆中,从大根堆中取出profit最大来做
*随着profit的完成W增大,促使可能有新低cost加入大根堆中
*/
public static int findMaximizedCapital(int k, int W, int[] Profits, int[] Capital) {
	Node[] nodes = new Node[Profits.length];
	for (int i = 0; i < Profits.length; i++) {
		nodes[i] = new Node(Profits[i], Capital[i]);
	}

	PriorityQueue<Node> minCostQ = new PriorityQueue<>(new MinCostComparator());
	PriorityQueue<Node> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
	for (int i = 0; i < nodes.length; i++) {
		minCostQ.add(nodes[i]);
	}
	for (int i = 0; i < k; i++) {
		while (!minCostQ.isEmpty() && minCostQ.peek().c <= W) {
			maxProfitQ.add(minCostQ.poll());
		}
		if (maxProfitQ.isEmpty()) {
			return W;
		}
		W += maxProfitQ.poll().p;
	}
	return W;
}

题目六:一个数据流中,随时可以取得中位数

思路:待续。。。。。

代码实现如下:

//实时中位数查询
public static class MedianHolder {
	private PriorityQueue<Integer> maxHeap =
             new PriorityQueue<Integer>(new MaxHeapComparator());
	private PriorityQueue<Integer> minHeap =
             new PriorityQueue<Integer>(new MinHeapComparator());

	private void modifyTwoHeapsSize() {
		if (this.maxHeap.size() == this.minHeap.size() + 2) {
			this.minHeap.add(this.maxHeap.poll());
		}
		if (this.minHeap.size() == this.maxHeap.size() + 2) {
			this.maxHeap.add(this.minHeap.poll());
		}
	}

	public void addNumber(int num) {
		if (this.maxHeap.isEmpty()) {
			this.maxHeap.add(num);
			return;
		}
		if (this.maxHeap.peek() >= num) {
			this.maxHeap.add(num);
		} else {
			if (this.minHeap.isEmpty()) {
				this.minHeap.add(num);
				return;
			}		
			if (this.minHeap.peek() > num) {
				this.maxHeap.add(num);
			} else {
				this.minHeap.add(num);
			}
		}
		modifyTwoHeapsSize();
	}

	public Integer getMedian() {
		int maxHeapSize = this.maxHeap.size();
		int minHeapSize = this.minHeap.size();
		if (maxHeapSize + minHeapSize == 0) {
			return null;
		}
		Integer maxHeapHead = this.maxHeap.peek();
		Integer minHeapHead = this.minHeap.peek();
		if (((maxHeapSize + minHeapSize) & 1) == 0) {
			return (maxHeapHead + minHeapHead) / 2;
		}
		return maxHeapSize > minHeapSize ? maxHeapHead : minHeapHead;
	}
}

public static class MaxHeapComparator implements Comparator<Integer> {
	@Override
	public int compare(Integer o1, Integer o2) {
		if (o2 > o1) {
			return 1;
		} else {
			return -1;
		}
	}
}

public static class MinHeapComparator implements Comparator<Integer> {
	@Override
	public int compare(Integer o1, Integer o2) {
		if (o2 < o1) {
			return 1;
		} else {
			return -1;
		}
	}
}

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

思路:是分析发现使用普通的字符串比较方法并不可靠,例如当ba与b进行比较时,传统的比较方法会认为b较小排在前,最终形成bba的拼接方式;通过例子可以发现bba并不是最小字典序,bab才是最小字典序,显然传统的比较方法不可行。既然我们是在找一种拼接方法使拼接后的strs最小,于是就应该来比较拼接之后的字符串来得到最低字典序。

代码实现如下:

//采用拼接后比较形成最低字典序
public static class MyComparator implements Comparator<String> {
	@Override
	public int compare(String a, String b) {
		return (a + b).compareTo(b + a); //拼接比较
	}
}

public static String lowestString(String[] strs) {
	if (strs == null || strs.length == 0) {
		return "";
	}
	//加入比较器后进行比较排序
	Arrays.sort(strs, new MyComparator());
	String res = "";
	for (int i = 0; i < strs.length; i++) {
		res += strs[i];
	}
	return res;
}

题目八:一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。 给你每一个项目开始的时间和结束的时间(给你一个数组,里面是一个个具体的项目),你来安排宣讲的日程,要求会议室进行的宣讲的场次最多,返回这个最多的宣讲场次。

思路:为了满足宣讲场次最多,需要从结束时间来看,如果结束时间没有重复交叠就互不影响可以都宣讲,当然前提是不存在包含关系---一场宣讲包含于另一场宣讲。分析完毕后,我们只需要按end时间来排序数组,然后依次比较进行即可。

代码实现如下:

//会议室宣讲问题
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 start) {
	Arrays.sort(programs, new ProgramComparator());
	int result = 0;
	for (int i = 0; i < programs.length; i++) {
		if (start <= programs[i].start) {
			result++;
			start = programs[i].end;
		}
	}
	return result;
}

这里推荐一个贪心算法&动态规划讲解较好的csdn博客链接:https://blog.csdn.net/u013309870/article/details/75193592 


我将在java数据结构与算法6---递归&动态规划中介绍与递归&动态规划

敬请关注! 点赞+关注不迷路哟!

                                                                                                  谢谢阅读               ---by 知飞翀

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值