Collections(四)——算法

泛型集合接口的一个重要优势就是你可以只实现一次算法。比如,考虑一个找出序列中最大值的算法。以前,人们使用循环实现。

if (a.length == 0) throws new NoSuchElementException();
T largest = a[0];
for(int i = 0;i != a.length;i++)
	if(largest.compareTo(a[i]) < 0)
		largest = a[i];

同时,为了找到一个ArrayList中的最大值,你需要稍微改动一下代码

if (v.size() == 0) throws new NoSuchElementException();
T largest = v.get(0);
for(int i = 1;i < v.size();i++)
	if(largest.compareTo(v.get(i)) < 0)
		largest = v.get(i);

那么找到一个链表中的最大值呢?你不应该随机读取链表,你需要使用迭代器,因此你需要修改你的代码。

if(l.isEmpty()) throws new NoSuchElementException();
Iterator<T> iter = l.iterator();
T largest = iter.next();
while(iter.hasNext())
{
	T next = iter.next();
	if(largest.compareTo(next) < 0)
		largest = next;
}

这些循环看起来很难受,而且容易出错。如果集合类中只有一个元素怎么办?如果一个是空的怎么办呢?你也不想每次都调试这段代码,而且需要实现一堆函数。

static <T extends Comparable> T max(T[] a);
static <T extends Comparable> T max(ArrayList<T> a);
static <T extends Comparable> T max(LinkedList<T> a);

这就是集合类接口出现的原因,想想我们为了实现这个算法,需要哪些接口?你需要随机获取函数set和get吗?你不需要。在链表算法中,我们只用到了迭代器。因此,你可以对任何实现了Collection接口的类实现同一个算法。

public static <T extends Comparable> max(Collections<T> a)
{
	if(a.isEmpty()) throws new NoSuchElementException;
	Iterator<T> iter = c.iterator();
	T largest = iter.next();
	while(iter.hasNext()
	{
		T next = iter.next();
		if(largest.compareTo(next) < 0)
			largest = next;
	}
	return largest;
}

你可以通过这个函数,找到链表,列表或者数组的最大值。
这就是接口的强大之处,实际上,C++的标准模板库里面实现了很多有用的算法,都是使用泛型技术实现的,Java中没有那么多,但是一些基本的都有:排序,二分搜索,和一些实用算法。

排序和重排

以前人们需要自己编写排序算法,现在多数编程语言自带排序函数,Java也不例外。
sort方法可以用于所有实现了List接口的类。

List<String> staff = new LinkedList<String>();
// fill collection.
Collections.sort(staff);

这个方法需要集合类中存储的元素实现了Comparable接口,如果没有实现,你需要将一个Comparator对象作为第二个参数传入函数中,我们在之前已经讲过了类似的问题。比如下面的排序方法。

Comparator<Item> itemComparator = new 
Comparator<Item>()
{
	public int compare(Item a,Item b)
	{
		return a.partNumber - b.partNumber;
	}
}
Collections.sort(items,itemComparator);

如果你想降序排序,你可以直接使用Collections.reverseOrder()方法,他会返回一个降序比较器。

Collections.sort(staff,Collections.reverseOrder());

你可能会问,Java里面是怎么排序的?如果你看过算法书,那你应该了解基本的排序算法,但是有些算法需要随机读取数值,而某些List不支持或不推荐随机读取。你可以在算法书中找到这些。Java排序算法先将数据复制到一个数组中,然后使用一种混合排序算法的变体对数组进行排序,最后把值复制回来。
这种混合排序算法比其他语言实现的快速排序算法慢一些,但是它有一个优点,它是稳定的,不会调换元素间的位置。这在大多数情况下都是需要的。比如你先按名字排序了,然后按薪酬排序,但是在第二次排序时,如果排序算法不稳定,那么第一次排序的结果将不会被保留。
那么什么类型的对象可以排序呢?根据官方文档,你的对象应该可修改,但不必可变容。他们的含义是:

  • 如果对象支持set方法,那么它是可修改的。
  • 如果变量支持add和remove操作,那么它是可变容的。
    shuffle方法执行与排序相反的操作,它将数组随机打乱。
ArrayList<Card> cards = ...;
Collections.shuffle(cards);

如果你的对象没有实现RandomAccess接口,那么shuffle方法会先把值复制到数组,然后再打乱,然后复制回来。

二分查找

为了找到一个值,你通常需要遍历一个数组。但是如果数组是有序的,你可以使用二分查找。Collections中的binarySearch方法实现了这种查找方式。向函数中传入的参数包括一个实现了List接口的对象,需要查找的值,如果你的对象是按照自己的比较方式排序的,那么你应该将Comparator对象传入。

i = Collections.binarySearch(c,element);
i = Collections.binarySearch(c,element,comparator);

如果返回值为正数,那么他就是这个元素所在的下表,如果为负数,那么他是这个元素应该插入的位置

insertionPoint = -i - 1;

之所以不是-i,是因为当i为0时会产生歧义。因此,你应该这样插入

if(i < 0)
	c.add(-i - 1,element);

之后数组仍然是有序的。
最后,二分查找需要随机获取能力,因此,如果你将链表对象传入二分查找函数,那么你失去了所有优势,他会退化为线性查找。

简单算法

Collections类实现了一些简单的算法,无论是查找最大值还是复制,还是填充值,你可以直接使用他们。

static <T extends Comparable<? super T> min(Collection<T> c);
static <T extends Comparable<? super T> max(Collection<T> c);
static <T> min(Collection<T> elements,Comparator<? super T> c);
static <T> max(Collection<T> elements,Comparator<? super T> c);

static <T> void copy(List<? super T> to, List<T> from);

static <T> void fill(List<? super T> l, T value);

static <T> void addAll(Collection<? super T> c, T... values);

static <T> void replaceAll(List<T> l, T oldValue,T newValue);

static int indexOfSubList(List<?> l, List<?> s);
static int lastIndexOfSubList(List<?> l,List<?> s);

static void swap(List<?> l, int i, int j );
static void reverse(List<?> l);

static void rotate(List<?> l, int d); // 将数组循环移位d次。
static int frequency(Collection<?> c, Object c); // 统计c出现的次数。
static boolean disjoint(Collection<?> c1, Collection<?> c2); // 如果两个数组没有公共元素,返回true,否则返回false。

编写自己的算法

如果你需要编写自己的算法,你也应该使用接口,而不是直接使用具体类的函数。

传统集合类

下面的介绍自Java存在以来就存在的集合类,主要包括:Hashtable,Properties,Stack和Vector,以及BitSet类。

Hashtable

Hashtable和HashMap的功能基本一致,主要区别是,Hashtable是同步的,如果你不需要同步功能,你就应该使用HashMap。

Enumerations

Enumeration接口的用法和迭代器一样,主要有两个方法,hasMoreElements和nextElements,他们就是对Iterator接口的hasNext和next方法的模拟。

属性映射

属性映射是映射的特殊形式。他有三个特征。

  • 键值都是字符串
  • 可以保存到文件,从文件读取。
  • 默认使用二级表
    Java平台实现了叫做Properties的属性映射。
    Properties映射通常用在存储映射。

Stack

Stack类模拟栈结构,只能在顶部删除(pop)或添加(push)元素。

BitSet

BitSet类用于存储位序列(事实上他不是数学意义上的集合(Set),说成序列更合适)。如果用于存储为序列,它通常比ArrayList更高效。你可以对每一位置零或置一。

结语

现在我们结束了Java类框架的介绍,你可以发现,Java有很多方便的集合类可供使用。在最后一章,我们将会介绍并发编程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值