败者树的Java实现

来源于:http://www.oschina.net/code/snippet_118649_13903

关于败者树,http://blog.163.com/yangjun1988422@126/blog/static/474129172011711103313483/ 有讲解

这里再贴一遍整理后的代码:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

/**
 * 败者树,对<b>多个有序</b>的数据源进行归并排序<br>
 * 原理:2个子结点比较后的败者(较大值)放入它们的父结点,而胜者(较小值)送到它们父结点的父节点去再作比较
 * 
 * @author Anthony
 * @param <T>
 */
public class LoserTree<T> {
	/**
	 * 数据源列表,为叶子节点提供数据,数据源的输出顺序<b>必须有序</b>
	 */
	private Iterator<T>[] datas;
	/**
	 * 叶子节点,存的是实际的数据<br>
	 * 叶子节点和数据源是一一对应的,即第一个叶子节点记录第一个数据源的当前数据
	 */
	private Object[] leafs;
	/**
	 * 非叶子节点,记录叶子节点的下标, 根据节点的值可以定位到叶子节点所指向的数据<br>
	 * 根据这个数组得到有序的数据
	 */
	private int[] nodes;

	/**
	 * 叶子节点数据的比较对象
	 */
	private Comparator<T> comparator;

	/**
	 * 构造方法,按照元素的Comparable接口实现进行排序
	 * 
	 */
	public LoserTree(List<Iterator<T>> branches) {
		this(branches, new Comparator<T>() {
			@SuppressWarnings("unchecked")
			@Override
			public int compare(T o1, T o2) {
				return ((Comparable<T>) o1).compareTo(o2);
			}
		});
	}

	/**
	 * 构造方法, 指定数据源分支的迭代器和元素比较对象<br>
	 * 迭代器的输出必须有序并且与Comparator对象的比较结果保持一致
	 * 
	 * @param brs
	 * @param comparator
	 */
	@SuppressWarnings("unchecked")
	public LoserTree(List<Iterator<T>> brs, Comparator<T> comparator) {
		this.datas = brs.toArray(new Iterator[0]);
		this.comparator = comparator;
		this.init();
	}

	/**
	 * 初始化构建败者树<br>
	 */
	private void init() {
		int size = this.datas.length;
		this.leafs = new Object[size];
		this.nodes = new int[size];

		// 为叶子节点赋值
		for (int i = 0; i < size; i++) {
			this.put(i);
		}

		// 找到叶子节点中最小值的下标
		int winner = 0;
		for (int i = 1; i < size; i++) {
			if (loser(i, winner)) {
				winner = i;
			}
		}
		// 非叶子节点全部初始化为最小值对应的叶子节点下标
		Arrays.fill(nodes, winner);

		// 从后向前依次调整非叶子节点
		for (int i = size - 1; i >= 0; i--)
			adjust(i);
	}

	/**
	 * 设置第index个叶子节点的下一个数据<br>
	 * 如果数据源已结束,则设置为null
	 * 
	 * @param index
	 */
	private void put(int index) {
		Iterator<T> branch = this.datas[index];
		this.leafs[index] = branch.hasNext() ? branch.next() : null;
	}

	/**
	 * 获取第index个叶子节点的当前数据
	 * 
	 * @param index
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private T get(int index) {
		return (T) leafs[index];
	}

	/**
	 * 判断index1对应的节点是否<b>小于</b>index2对应的节点
	 * 
	 * @param index1
	 * @param index2
	 * @return
	 */
	private boolean loser(int index1, int index2) {
		T t1 = (T) get(index1);
		T t2 = (T) get(index2);
		if (t1 == null)
			return false;
		if (t2 == null)
			return true;
		// 这里, 当叶节点数据相等时比较分支索引是为了实现排序算法的稳定性
		int n = comparator.compare(t1, t2);
		return n != 0 ? n < 0 : index1 < index2;
	}

	/**
	 * 调整第index个叶子节点<br>
	 * 具体调整过程为: 叶子节点和父节点比较, 败者(较大值)留在父节点位置, 胜者(较小值)继续和祖父节点比较,直至最终
	 * 
	 * @param index
	 */
	private void adjust(int index) {
		int size = this.datas.length;
		int t = (size + index) / 2; // 父节点
		while (t > 0) {
			// 如果当前值比父节点要大,那么留在父节点位置;否则继续和祖父节点比较
			if (loser(nodes[t], index)) {
				int temp = nodes[t];
				nodes[t] = index;
				index = temp;
			}
			t /= 2;
		}
		nodes[0] = index; // 根节点始终为最小值
	}

	/**
	 * 依次读取数据源的数据进行归并排序, 返回排序后的数据列表<br>
	 * 
	 * @return 从小到大的顺序
	 */
	public List<T> merge(int bufferSize) {
		List<T> list = new ArrayList<T>();
		T top = null;
		while ((top = get(nodes[0])) != null) {
			list.add(top); // 取得最小值
			put(nodes[0]); // 索引对应的数据被取出,需要从数据源中再读入一个
			adjust(nodes[0]); // 根据新插入的叶子节点重新调整树
			if (list.size() == bufferSize)
				break;
		}
		return list;
	}
}

  

posted on 2014-03-25 16:10  AnthonyViking  阅读(508)  评论(0编辑  收藏  举报

导航