Java中一些常用的数据结构

Jun 24, 2013


一、ArrayList与LinkedList的性能比较

ArrayList长于随机访问元素,但是在List中间插入和移除元素时较慢
LinkedList通过较低的在List中间进行的插入和删除操作,提供优化的顺序访问。在随机访问方面相对于ArrayList比较慢
以上是<<Java编程思想>>一书对这两种类型的比较
   
现在我们从源码中看一下为什么这两者会产生性能上的差异:

public class ArrayList<E> extends ...
{
    ...
	private transient Object[] elementData;
	...
	E elementData(int index) {
		return (E) elementData[index];
    }

	public E get(int index) { //随机读取
		rangeCheck(index); //检查是否越界

		return elementData(index); //直接通过数组下标返回数据,效率很高
	}
	
	public E set(int index, E element) { //随机修改
		rangeCheck(index);

		E oldValue = elementData(index);
		elementData[index] = element; //直接给数组元素赋值
		return oldValue;
	}
	...
	
	//中间插入
	public void add(int index, E element) {
		rangeCheckForAdd(index);

		ensureCapacityInternal(size + 1);  // Increments modCount!!
		//System.arraycopy()方法在此处的作用是移位操作,对性能影响较大
		System.arraycopy(elementData, index, elementData, index + 1,
						 size - index);
		elementData[index] = element;
		size++;
	}
	
	//中间删除
	public E remove(int index) {
		rangeCheck(index);

		modCount++;
		E oldValue = elementData(index);

		int numMoved = size - index - 1;
		if (numMoved > 0) //如果删除位置不是最后一个节点,则需要移位操作
			System.arraycopy(elementData, index+1, elementData, index,
							 numMoved);
		elementData[--size] = null; // Let gc do its work

		return oldValue;
    }
}

public class LinkedList<E> extends ...
{
	...
	private static class Node<E> { //可以看出LinkedList采用的是双链表结构
		E item;
		Node<E> next;
		Node<E> prev;

		Node(Node<E> prev, E element, Node<E> next) {
			this.item = element;
			this.next = next;
			this.prev = prev;
		}
	}
	...
	public E get(int index) { //随机读取需要对链表进行遍历,性能不如ArrayList
		checkElementIndex(index);
		return node(index).item; //node(index)方法需要遍历链表
	}
	...
	public E set(int index, E element) { //随机修改
		checkElementIndex(index);
		Node<E> x = node(index); //node(index)方法需要遍历链表
		E oldVal = x.item;
		x.item = element;
		return oldVal;
	}
	...
	Node<E> node(int index) {
		// assert isElementIndex(index);
		if (index < (size >> 1)) { //如果index的位置在前半部分,则从头开始遍历到index位置
			Node<E> x = first;
			for (int i = 0; i < index; i++)
				x = x.next;
			return x;
		} else { //否则就从后开始遍历
			Node<E> x = last;
			for (int i = size - 1; i > index; i--)
				x = x.prev;
			return x;
		}
	}
	
	//中间插入
	public void add(int index, E element) {
		checkPositionIndex(index);

		if (index == size)
			linkLast(element);
		else
			linkBefore(element, node(index)); //node(index)方法虽然会遍历链表,但是不需要进行移位操作,性能损耗少
	}
	...
	//中间删除
	public E remove(int index) {
		checkElementIndex(index);
		return unlink(node(index));
	}
	...
	//删除找到的节点
	E unlink(Node<E> x) {
		// assert x != null;
		final E element = x.item;
		final Node<E> next = x.next;
		final Node<E> prev = x.prev;

		if (prev == null) {
			first = next;
		} else {
			prev.next = next;
			x.prev = null;
		}

		if (next == null) {
			last = prev;
		} else {
			next.prev = prev;
			x.next = null;
		}

		x.item = null;
		size--;
		modCount++;
		return element;
	}
}
总结:从上面两个类的比较可以看出,在随机访问方面,ArrayList类直接根据数组下标访问数组,而LinkedList需要遍历链表找到节点,从而ArrayList的随机访问性能优于LinkedList;而在中间插入和删除方面,ArrayList需要进行移位操作,而LinkedList只需要遍历链表,从而ArrayList的中间插入和删除性能不如LinkedList,但是需要记住的是,这个比较只是一般情况的比较,如果插入和删除的位置靠近链表的末尾,则ArrayList的移位操作只需要操作很少的一部分数据,效率可能会比LinkedList要好。

二叉排序树的实现:

public class BinarySortTree {

private Node root = null;

private static class Node{
	private int key;
	private Node left;
	private Node right;
	private Node parent;
	public Node() {
		left = null;
		right = null;
		parent = null;
	}
}

public void insert(int key)
{
	Node node = new Node();
	node.key = key;
	if (null == root)
	{
		root = node;
		return;
	}
	
	Node parentNode = null;
	Node pNode = root; 
	while (pNode != null)
	{
		parentNode = pNode;
		if (pNode.key > key)
		{
			pNode = pNode.left;
		}
		else if (pNode.key < key)
		{
			pNode = pNode.right;
		}
		else
		{
			return;
		}
	}
	
	if (parentNode.key > key)
	{
		parentNode.left = node;
		node.parent = parentNode;
		return;
	}
	
	if (parentNode.key < key)
	{
		parentNode.right = node;
		node.parent = parentNode;
		return;
	}
	
}

public Node search(int key)
{
	Node pNode = root;
	while (pNode != null && pNode.key != key)
	{
		if (pNode.key > key)
		{
			pNode = pNode.left;
		}
		else
		{
			pNode = pNode.right;
		}
	}
	return pNode;
}

public Node searchMin(Node node)
{
	if (null == root)
	{
		return null;
	}
	Node pNode = node;
	while (pNode.left != null)
	{
		pNode = pNode.left;
	}
	return pNode;
}

public Node searchMax(Node node)
{
	if (null == root)
	{
		return null;
	}
	Node pNode = node;
	while (pNode.right != null)
	{
		pNode = pNode.right;
	}
	return pNode;
}

public Node searchPredecessor(Node p)
{
	if (null == p)
	{
		return null;
	}
	if (p.left != null)
	{
		return searchMax(p.left);
	}
	if (null == p.parent)
	{
		return null;
	}
	
	//下面这一段是关键[begin]
	while (p != null)
	{
		if (p.parent.right == p)
		{
			break;
		}
		p = p.parent;
	}
	//[end]
	return p.parent;
}

public Node searchSuccessor(Node p)
{
	if (null == p)
	{
		return null;
	}
	
	if (p.right != null)
	{
		return searchMin(p.right);
	}
	
	if (p.parent == null)
	{
		return null;
	}
	
	//下面这一段是关键[begin]
	while (p != null)
	{
		if (p.parent.left == p)
		{
			break;
		}
		p = p.parent;
	}
	//[end]
	return p.parent;
}

public boolean delete(int key)	
{
	Node node = search(key);
	if (null == node)
	{
		return false;
	}
	
	//1.如果节点是叶子节点,则直接删除
	if (null == node.left && null == node.right)
	{
		if (null == node.parent) //如果二叉排序树中只有一个节点,则删除
		{
			root = null;
		}
		else
		{
			if (node.parent.left == node)
			{
				node.parent.left = null;
			}
			else
			{
				node.parent.right = null;
			}
		}
	}
	//2.如果只有右子树,则删除该节点,并且将右子树上移
	else if (null == node.left && node.right != null)
	{
		node.right.parent = node.parent;
		if (null == node.parent)
		{
			root = node.right;
		}
		else if (node.parent.left == node)
		{
			node.parent.left = node.right;				
		}
		else
		{
			node.parent.right = node.right;
		}
		node = null;
	}
	//3.如果只有左子树,则删除该节点,并且将左子树上移
	else if (null == node.right && node.left != null)
	{
		node.left.parent = node.parent;
		if (null == node.parent)
		{
			root = node.left;
		}
		else if (node.parent.left == node)
		{
			node.parent.left = node.left;
		}
		else
		{
			node.parent.right = node.left;
		}
		node = null;
	}
	//4.被删除的结点既有左孩子,又有右孩子,则找到该节点的后继节点,用后继节点替代该节点的位置,并删除后继节点
	//注意:此处的后继节点肯定是没有左子树的,因为如果有左子树的话,后继节点就应该在左子树里面找,而不是现在找
	//到的节点了
	else
	{
		Node successor = searchSuccessor(node);
		delete(successor.key);
		node.key = successor.key;
	}
	return true;
}