Java基础复习笔记04数据结构-线性表

1.       线性表

线性表是数据结构的一种逻辑结构,其实所有的逻辑数据结构都可以用2类物理实现方式去实现,一个是物理存储连续的顺序结构,另一个就是物理存储不连续的链式结构。线性表是指有n个元素组成的有序序列,这n个元素具有相同的结构。

2.       线性表的操作

线性表的主要操作是增加元素、删除索引处元素、在索引处添加元素、查找索引处元素、替换索引处元素、清空所有元素。而对于顺序结构和链表结构这两种不同实现方式,底层的算法也会有比较大的差异。

3.       使用场景

其实线性表的使用场景非常多,从宏观来说,比如我们从数据库查询多条记录出来需要封装一个集合List对象来承载着众多的业务Bean元素,之后传给MVC的控制C层。之后C层在以某种展现形式传给视图V层。那么装载着众多业务记录信息的容器就是线性表。从微观上来讲,比如实现数据结构的栈结构、或者是对象池的底层、有序排序的数据域等等比较复杂的结构,底层都是离不开线性表的支持。

4.       线性表的顺序实现——顺序表

顺序的存储结构实质上就是利用数组进行元素的存储,笔者简单地实现了一个顺序链表。线性表的顺序实现就是利用对象数组。
Java基础复习笔记04数据结构-线性表
 看如下代码

 

package dateStructer.list;

/**
 * 自实现的arrayList
 * @author liuyan
 *
 * @param <E>
 */
public class MyArrayList<E> implements List<E> {
	
	//默认的数组长度
	private final int DefSize = 16;
	
	//临时变量数组
	private Object[] objects;
	
	//记录实实在在的元素个数
	private int elementSize;

	public MyArrayList() {
		objects = new Object[DefSize];
	}
	
	/**
	 * 增加元素,实际上就是往最后一位出入数据
	 */
	@Override
	public boolean add(E e) {
		add(elementSize, e);
		return true;
	}
	
	/**
	 * 按位索引插入元素
	 */
	@Override
	public void add(int index, E element) {

		if (index == elementSize) {
			objects[index] = element;
			elementSize++;
			return;
		}

		for (int i = elementSize - 1; i >= 0; i--) {

			if (i == index) {

				int movedSize = elementSize - i - 1;

				System.arraycopy(objects, index + 1, objects, index, movedSize);
				objects[index] = element;
				elementSize++;
			}

		}

	}
	
	/**
	 * 清除所有元素
	 */
	@Override
	public void clear() {
		for (int i = 0; i < elementSize; i++) {
			objects[i] = 0;
		}
		elementSize = 0;
	}
	
	/**
	 * 判断集合是否包含了某个元素
	 */
	@Override
	public boolean contains(Object o) {
		for (Object object : objects) {
			if (object != null && object.equals(o)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 获得某位置索引的元素
	 */
	@Override
	public E get(int index) {

		for (int i = 0; i < elementSize; i++) {
			if (i == index) {
				return (E) objects[index];
			}
		}

		return null;
	}

	/**
	 * 实现元素定位
	 */
	@Override
	public int indexOf(Object o) {

		for (int i = 0; i < elementSize; i++) {
			if (o.equals(objects[i])) {
				return i;
			}
		}
		return -1;
	}
	
	/**
	 * 是否是空集合
	 */
	@Override
	public boolean isEmpty() {

		if (elementSize == 0) {
			return true;
		}
		return false;
	}

	@Override
	public int lastIndexOf(Object o) {
		if (objects == null || objects.length == 0) {
			return -1;
		}

		for (int i = elementSize - 1; i >= 0; i--) {
			if (objects[i] == o) {
				return i;
			}
		}
		return -1;
	}
	
	/**
	 * 删除某个元素
	 */
	@Override
	public boolean remove(Object o) {

		for (int i = 0; i < elementSize; i++) {

			if (o.equals(objects[i])) {
				objects[i] = null;

				int movedSize = elementSize - i - 1;
				System.arraycopy(objects, i + 1, objects, i, movedSize);
				elementSize--;
				return true;
			}

		}

		return false;
	}
	
	/**
	 * 删除某个索引下的元素
	 */
	@Override
	public E remove(int index) {
		for (int i = 0; i < elementSize; i++) {

			if (objects[i].equals(objects[index])) {
				objects[i] = null;

				int movedSize = elementSize - i - 1;
				System.arraycopy(objects, i + 1, objects, i, movedSize);
				elementSize--;
				return (E) objects[index];
			}

		}

		return null;
	}
	
	/**
	 * 对已有位置设置新的元素值
	 */
	@Override
	public E set(int index, E element) {

		for (int i = 0; i < elementSize; i++) {

			if (i == index) {
				objects[index] = null;
				objects[index] = element;
				return element;
			}

		}

		return null;
	}

	@Override
	public int size() {
		// TODO Auto-generated method stub
		return elementSize;
	}

	@Override
	public String toString() {

		StringBuffer str = new StringBuffer("[");

		for (int i = 0; i < elementSize; i++) {

			str.append("[" + objects[i].toString() + "],");

		}

		if (elementSize > 0) {
			return str.substring(0, str.lastIndexOf(",")) + "]";
		}

		return str.append("]").toString();
	}

 实际上实现Java标准的List接口还需要实现其他一些方法,只不过因为篇幅原因,在此只能实现一些核心方法,而且说实在的,根本谈不上健壮性,更不可能投入使用,线程安全也存在问题,这只是演示一下用数组实现顺序线性表的核心算法罢了。所以说学习数据结构实际上是考验算法功底。一个数据结构的事先是否最优,完全是底层算法的实现是否最优。顺序线性表最大的特点就是物理上存储空间连续,内存资源使用比较节省、但是进行增加、删除元素的时候就得让别的元素挪挪地方了,用时间换取了空间的连续性,显得有点牵一发而动全身。如果是查找某个元素操作的时候就是需要遍历整体集合。

简单测试代码如下:

	public static void main(String[] args) {
		MyArrayList<String> list = new MyArrayList<String>();
		list.add("1");
		list.add("2");
		list.add("3");
		System.out.println(list);
		list.remove("3");
		System.out.println(list);
		list.add("3");
		System.out.println(list);
		System.out.println(list.contains("2"));
		System.out.println(list.isEmpty());
		list.clear();
		System.out.println(list);
		System.out.println(list.isEmpty());
	}

 执行结果

[[1],[2],[3]]
[[1],[2]]
[[1],[2],[3]]
true
false
[]
true

 5.       线性表的非顺序实现——链式表

相对于数组的顺序存储,还可以定义一个比较特殊的链表结构,每个链表节点在内存中不一定是一块连续的区域,链表节点记录了与自身节点相关的下一个节点的位置信息。

如果链表节点仅仅记录了与其下一个Next节点的位置信息,而没有记录上一个Prev节点的信息,那么这叫做单向链表结构。

如果此节点不仅仅记录了下一个节点的信息,还记录了上一个节点的信息,那么这个情况叫做双向链表结构。我们就用双向链表实现Java标准的List<E>接口。(实际上Java提出了一堆标准,实际上就是接口,而sun自己为自己定义的标准接口还提供了实现类,咱们一般用的就是基于sun提出标准的sun自己的实现类)。
Java基础复习笔记04数据结构-线性表
 如下代码

/**
 * 自己实现的linkedList
 * @author liuyan
 * @param <E>
 */
public class MyLinkedList<E> implements List<E> {

	/**
	 * 双向链表结构
	 */
	public class LinkNode {

		// 真正的数据域
		private E date;

		// 记录上一个节点
		private LinkNode prevLinkNode;

		// 记录下一个节点
		private LinkNode nextLinkNode;

		public LinkNode() {

		}

		public LinkNode(E date, LinkNode prevLinkNode, LinkNode nextLinkNode) {
			this.date = date;
			this.prevLinkNode = prevLinkNode;
			this.nextLinkNode = nextLinkNode;
		}
	}

	// 结点个数
	private int nodeSize;

	// 头结点
	private LinkNode headNode;

	// 尾巴节点
	private LinkNode tailNode;

	public MyLinkedList() {
		headNode = null;
		tailNode = null;
	}

	/**
	 * 采用尾端元素增加法,增加新元素
	 */
	@Override
	public boolean add(E element) {
		if (nodeSize == 0) {
			headNode = new LinkNode(element, null, tailNode);
		} else {

			if (tailNode == null) {
				tailNode = new LinkNode(element, headNode, null);
				headNode.nextLinkNode = tailNode;
				nodeSize++;
				return true;
			}

			LinkNode linkNode = tailNode;
			tailNode = new LinkNode(element, linkNode, null);
			linkNode.nextLinkNode = tailNode;

		}
		nodeSize++;
		return true;
	}

	/**
	 * 根据索引号查找节点
	 * 
	 * @param index
	 * @return
	 */
	public LinkNode findLinkNodeByIndex(int index) {

		LinkNode linkNodeNowTemp = headNode;

		for (int i = 0; i < nodeSize; i++) {

			if (i == index) {
				return linkNodeNowTemp;
			}

			linkNodeNowTemp = linkNodeNowTemp.nextLinkNode;
		}
		return null;
	}
	
	/**
	 * 按索引位置添加元素
	 */
	@Override
	public void add(int index, E element) {

		if (nodeSize == 0) {
			add(element);
		}

		// 按照元素先建立新的node
		LinkNode linkNodeNew = new LinkNode(element, null, null);

		// 找到索引处的节点
		LinkNode linkNodeNowTemp = findLinkNodeByIndex(index);

		// 找出索引处节点的上一个node
		LinkNode linkNodePrev = linkNodeNowTemp.prevLinkNode;

		// 上一个节点的下一个节点指向新node
		linkNodePrev.nextLinkNode = linkNodeNew;

		// 新节点的上一个节点指向原位置节点上一个节点
		linkNodeNew.prevLinkNode = linkNodePrev;

		// 新节点的下一个节点指向原位置节点
		linkNodeNew.nextLinkNode = linkNodeNowTemp;

		// 原节点的上一个节点指向新节点
		linkNodeNowTemp.prevLinkNode = linkNodeNew;
		
		nodeSize ++;
	}

	/**
	 * 清除所有Node元素资源
	 */
	@Override
	public void clear() {

		LinkNode linkNodeNowTemp = headNode;

		for (int i = 0; i < nodeSize; i++) {

			if (linkNodeNowTemp != tailNode && linkNodeNowTemp != headNode) {
				linkNodeNowTemp = linkNodeNowTemp.nextLinkNode;
				linkNodeNowTemp.prevLinkNode.nextLinkNode = null;
				linkNodeNowTemp.prevLinkNode.prevLinkNode = null;
				linkNodeNowTemp.prevLinkNode.date = null;
				linkNodeNowTemp.prevLinkNode = null;
			} else if (linkNodeNowTemp == tailNode) {
				linkNodeNowTemp.prevLinkNode = null;
			} else if (linkNodeNowTemp == headNode) {
				linkNodeNowTemp.nextLinkNode = null;
			}

		}
		headNode = null;
		tailNode = null;
		nodeSize = 0;

	}
	
	/**
	 * 是否包含此元素
	 */
	@Override
	public boolean contains(Object object) {

		LinkNode linkNodeNowTemp = headNode;

		for (int i = 0; i < nodeSize; i++) {

			if (object == linkNodeNowTemp.date) {
				return true;
			}

			linkNodeNowTemp = linkNodeNowTemp.nextLinkNode;
		}

		return false;
	}

	@Override
	public E get(int index) {

		LinkNode linkNode = findLinkNodeByIndex(index);

		if (linkNode != null) {
			return linkNode.date;
		}

		return null;
	}

	@Override
	public boolean isEmpty() {
		return nodeSize == 0;
	}

	/**
	 * 删除元素
	 */
	@Override
	public boolean remove(Object o) {

		LinkNode linkNodeNowTemp = headNode;

		for (int i = 0; i < nodeSize; i++) {

			if (linkNodeNowTemp.date == o) {

				if (linkNodeNowTemp != tailNode && linkNodeNowTemp != headNode) {
					LinkNode linkNewPrev = linkNodeNowTemp.prevLinkNode;
					LinkNode linkNewNext = linkNodeNowTemp.nextLinkNode;
					linkNewPrev.nextLinkNode = linkNewNext;
					linkNewNext.prevLinkNode = linkNewPrev;

					linkNodeNowTemp.nextLinkNode = null;
					linkNodeNowTemp.prevLinkNode = null;
					linkNodeNowTemp.date = null;
					linkNodeNowTemp = null;
					nodeSize--;
					return true;
				} else if (linkNodeNowTemp == tailNode) {
					tailNode = linkNodeNowTemp.prevLinkNode;

					linkNodeNowTemp.prevLinkNode = null;
					linkNodeNowTemp.date = null;
					linkNodeNowTemp = null;
					nodeSize--;
					return true;
				} else if (linkNodeNowTemp == headNode) {
					headNode = linkNodeNowTemp.nextLinkNode;

					linkNodeNowTemp.nextLinkNode = null;
					linkNodeNowTemp.date = null;
					linkNodeNowTemp = null;
					nodeSize--;
					return true;
				}

			}

			linkNodeNowTemp = linkNodeNowTemp.nextLinkNode;
		}

		return false;
	}

	/**
	 * 删除位置标记下的元素
	 */
	@Override
	public E remove(int index) {

		LinkNode linkNodeNowTemp = headNode;

		for (int i = 0; i < nodeSize; i++) {

			if (index == i) {
				LinkNode linkNewPrev = linkNodeNowTemp.prevLinkNode;
				LinkNode linkNewNext = linkNodeNowTemp.nextLinkNode;
				linkNewPrev.nextLinkNode = linkNewNext;
				linkNewNext.prevLinkNode = linkNewPrev;

				linkNodeNowTemp.nextLinkNode = null;
				linkNodeNowTemp.prevLinkNode = null;
				linkNodeNowTemp.date = null;
				linkNodeNowTemp = null;
				break;
			}

			linkNodeNowTemp = linkNodeNowTemp.nextLinkNode;
		}

		return null;
	}

	@Override
	public int size() {
		// TODO Auto-generated method stub
		return nodeSize;
	}

	@Override
	public String toString() {

		StringBuffer str = new StringBuffer("[");
		LinkNode linkNode = null;
		for (int i = 0; i < nodeSize; i++) {

			linkNode = findLinkNodeByIndex(i);

			str.append("[" + linkNode.date + "],");

		}

		if (nodeSize > 0) {
			return str.substring(0, str.lastIndexOf(",")) + "]";
		}

		return str.append("]").toString();
	}
}

 和顺序实现一样,实现了核心方法,测试代码相同。

6.       总结

这次总结了线性表的数据结构,并且用数组和双向链表节点实现了类似ArrayListLinkedList的功能。主要是实现了核心的方法。为了节省资源,一般使用ArrayList存储,但是添加、删除元素的时候就比较费时间,还得移动剩余元素的物理位置,使之物理连续。而双向链表的实现也不是什么省油的灯,占用资源比数组大很多,但是作为经常修改的元素集合,双向链表还是比顺序链表有性能上的优势的。