集合算法
java的Collections
类以静态方法的形式提供了一些多态算法。
这些算法绝大多数处理List
实例,也有个别能处理任意集合的实例。下面我们简要的介绍下。
排序
sort
算法对列表的元素按升序进行排序。它有两种处理形式:
- 一种是接收一个列表,并根据其元素的自然顺序进行排序。
- 第二种是接收一个列表,以及额外的一个
Comparator
对象,用该对象对元素进行排序。
sort
方法使用优化后的归并排序(merge sort)算法,不仅快,而且稳定:
- 快:它能保证运行的时间复杂度是
O(nlog(n))
,几乎和快排(quicksort)一样快。但是后者不是稳定排序且不能保证O(nlog(n ))
的时间复杂度。 - 稳定:他不会重排相等的元素,当你对同一个列表根据不同的属性进行二次排序时,这一点很重要。比如,某用户对收件箱的消息先根据日期排序,然后再根据发件人排序。用户的正常预期是给定发件人的信息仍能保持日期排序。只有第二次排序是稳定的才能保证这一点。
示例1: 将程序的参数按照字母顺序排序:
import java.util.*;
public class Sort {
public static void main(String[] args) {
List<String> list = Arrays.asList(args);
Collections.sort(list);
System.out.println(list);
}
}
示例2: 下面的代码找出所有符合要求的同字母异序词,并以列表的形式保存词组,然后对列表进行排序,按照词组长度倒序输出。这里使用Comparator
参数自定义排序规则来实现这个效果。它接收两个参数,一是词典文件路径,二是最小词组大小
词典文件:https://docs.oracle.com/javase/tutorial/collections/interfaces/examples/dictionary.txt
import java.util.*;
import java.io.*;
public class Anagrams {
public static void main(String[] args) {
int minGroupSize = Integer.parseInt(args[1]);
Map<String, List<String>> m = new HashMap<>();
try{
Scanner s = new Scanner(new File(args[0]));
while (s.hasNext()) {
String word = s.next();
String alpha = alphabetize(word);
// 类似python字典的setdefault方法
m.computeIfAbsent(alpha, k -> new ArrayList<>()).add(word);
}
} catch (IOException e) {
System.out.println(e);
System.exit(1);
}
// 找出词组长度满足要求的
List<List<String>> candidates = new ArrayList<>();
for(List<String> l : m.values()) {
if (l.size() >= minGroupSize)
candidates.add(l);
}
// 根据词组长度倒序排序
Collections.sort(candidates, new Comparator<List<String>>() {
public int compare(List<String> o1, List<String> o2) {
return o2.size() - o1.size();
}
});
for(List<String> l : candidates) {
System.out.println(l.size() + ": " + l);
}
}
private static String alphabetize(String s) {
char[] letters = s.toCharArray();
Arrays.sort(letters);
return new String(letters);
}
}
输出:
12: [apers, apres, asper, pares, parse, pears, prase, presa, rapes, reaps, spare, spear]
11: [alerts, alters, artels, estral, laster, ratels, salter, slater, staler, stelar, talers]
10: [least, setal, slate, stale, steal, stela, taels, tales, teals, tesla]
9: [estrin, inerts, insert, inters, niters, nitres, sinter, triens, trines]
9: [capers, crapes, escarp, pacers, parsec, recaps, scrape, secpar, spacer]
9: [palest, palets, pastel, petals, plates, pleats, septal, staple, tepals]
9: [anestri, antsier, nastier, ratines, retains, retinas, retsina, stainer, stearin]
乱序
schuffle
算法和sort
相反,它随机打乱列表。利用它可以实现洗牌,生成测试用例等。此操作有两种形式:
- 一种是接收一个列表并使用默认等随机源
- 另一种是调用时额外提供一个
Random
对象作为随机源。
以下示例打乱程序的参数:
import java.util.*;
public class Shuffle {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (String a : args)
list.add(a);
Collections.shuffle(list, new Random());
System.out.println(list);
}
}
以上也可以简化为:
import java.util.*;
public class Shuffle {
public static void main(String[] args) {
List<String> list = Arrays.asList(args);
Collections.shuffle(list); // //使用默认的随机源
System.out.println(list);
System.out.println(Arrays.toString(args));
}
}
注意,这里利用Arrays
类的静态工厂方法asList
将数组处理为List
视图。这个方法不会拷贝数组,对List视图的修改会写入数组,反之亦然。List
视图不是通用的List
实现,因为它没有实现add
和remove
方法:数组不可调整大小。
常用数据操作
Collections
类提供了5个常用的算法来处理列表对象的常用数据操作:
reverse
反转列表中元素的顺序fill
用指定的值覆写列表的每个元素,可用户重新初始化一个列表copy
接收两个参数,一个目标列表,一个源列表,将源列表的元素拷贝到目标列表并进行覆盖。目标列表长度至少要和源列表一样,如果目标列表更长,多余的元素不会受到影响。swap
交换列表中指定位置的元素addAll
将所有指定元素拷贝到集合中,要添加的元素可以单独指定,也可以作为一个数组
示例:
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3,4));
Collections.reverse(list);
System.out.println(list); // [4, 3, 2, 1]
Collections.fill(list, 0);
System.out.println(list); // [0, 0, 0, 0]
Collections.copy(list, new ArrayList<>(Arrays.asList(8, 9)));
System.out.println(list); // [8, 9, 0, 0]
Collections.swap(list, 0, list.size() -1);
System.out.println(list); // [0, 9, 0, 8]
Collections.addAll(list, 10, 11);
System.out.println(list); // [0, 9, 0, 8, 10, 11]
查找
binarySearch
二分查找算法从一个有序的列表中查找指定的元素。这个算法有两种形式:
- 一种是接收一个列表和要搜索的元素。这种形式假定列表是有序的(按其元素的自然顺序升序排序)。
- 第二种形式接收一个额外
Comparator
对象,并假定列表是有序的(根据指定的Comparator对象按升序排序)。注意,这里是告诉binarySearch
,要查找的列表是根据指定的Comparator
对象排序的,而不是让binarySearch
先对列表作排序。
可以在调用binarySearch
前,先调用sort
对列表进行排序。
二分查找法的返回值是int类型,如果返回值>=0,说明找到了,且返回值是元素对应的索引。
如果小于0,说明没找到,返回值是(-(insertion_index) - 1)
,其中insertion_index
是元素的预期插入位置的索引,采用这种形式是为了确保未找到时的返回值<0
下面演示二分查找的使用,并在查找不存在的情况下将元素插入列表中合适的位置:
List<Integer> list = new ArrayList<>(Arrays.asList(3,6,2,9,5,8,1,7));
Collections.sort(list);
int pos = Collections.binarySearch(list, 4); // -4
if (pos < 0)
list.add(-pos-1, 4);
为了演示第二种形式,下面故意写啰嗦一点,和上面是等价的:
List<Integer> list = new ArrayList<>(Arrays.asList(3,6,2,9,5,8,1,7));
Comparator c = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
Collections.sort(list, c);
int pos = Collections.binarySearch(list, 4, c);
if (pos < 0)
list.add(-pos-1, 4);
构成
frequency
频率和disjoint
不相交算法:
frequency
统计指定元素在集合中出现的次数disjoint
判断两个集合是否不包含共同的元素
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,3));
int freq = Collections.frequency(list, 3);
System.out.println(freq); // 2
boolean isDisjoint1 = Collections.disjoint(list, new ArrayList<>(Arrays.asList(2, 3)));
boolean isDisjoint2 = Collections.disjoint(list, new ArrayList<>(Arrays.asList(4, 5)));
System.out.println(isDisjoint1); // false
System.out.println(isDisjoint2); // true
极值查找
min
查找集合中的最小元素max
查找集合中的最大元素
以上两种方法都有两种形式,一种是只接收一个集合,默认按照元素的自然排序查找。第二种是额外接收一个Comparator
对象,按照指定排序查找。