线性表以及java代码实现

251 阅读10分钟

什么是线性表

线性表:由同类型数据元素组成的有序序列的线性结构

  • 表中元素个数为表的长度
  • 线性表没有元素时,称为空表
  • 表起始位置叫做表头,表结束位置称表尾

它有三个特点:相同数据类型、有序、有限

线性表的存储结构分为两种:

  • 顺序存储结构:顺序表
  • 链式存储结构:链表

线性表的抽象数据类型(ADT)

package com.xgc.linearlist;

/**
 * 线性表的抽象数据类型
 * @author xgc
 *
 */
public interface LinearListADT {
	
	/**
	 * 
	 * @return 返回线性表的长度
	 */
	public int size();
	
	/**
	 * 
	 * @return 返回线性表是否为空
	 */
	public boolean isEmpty();
	
	/**
	 * 获取指定索引的数据元素
	 * @param i 索引
	 * @return 指定索引的数据元素
	 * @throws Exception 
	 */
	public Object get(int i) throws Exception;
	
	/**
	 * 将元素添加到线性表的指定索引处
	 * @param i 索引
	 * @param e 要添加的元素
	 * @return 是否添加成功
	 * @throws Exception 
	 */
	public void add(int i, Object o) throws Exception;
	
	/**
	 * 将元素添加到线性表的末尾
	 * @param o 要添加的元素
	 * @return 是否添加成功
	 * @throws Exception 
	 */
	public void add(Object o) throws Exception;
	
	/**
	 * 移除指定索引处的元素
	 * @param i 索引
	 * @return 被移除的元素
	 * @throws Exception 
	 */
	public Object remove(int i) throws Exception;
	
	/**
	 * 查看是否包含该元素
	 * @param o 要查看的元素
	 * @return 是否线性表含有此元素
	 */
	public boolean contains(Object o);
	
	/**
	 * 返回指定元素的索引
	 * @param o 指定元素
	 * @return 指定元素的索引
	 */
	public int indexOf(Object o);
}

顺序表

线性表的顺序存储是指用一组地址连续的存储单元来存储数据元素,使得在逻辑结构上相邻的数据元素存储在相邻的物理存储单元。

采用顺序存储结构的线性表就叫顺序表。

优点:

  1. 节省空间。分配给数据的存储空间全部用来存放数据,维护结点之间的逻辑顺序不需占用额外的存储空间。

  2. 查找效率高。时间复杂度为O(1)。每一个元素对应一个序号,通过该序号可以直接计算出该元素的存储地址。

    假设线性表的每个数据元素需要K个存储单元,并以该元素所占的第一个存储单元的地址作为数据元素的存储地址,那么线性表序号为i的数据元素的存储地址LOC(ai)与序号为i+1的数据元素的存储地址LOC(ai+1)之间的关系为:

    LOC(ai) = LOC(ai+1) + K

    而且线性表序号为i的数据元素的存储地址为:

    LOC(ai) = LOC(a0) + i×K

    其中 LOC(a0)为线性表0号元素的存储地址,也叫做线性表的起始地址。

缺点:

  1. 插入和删除操作需要移动元素,效率低。

    删除和删除操作的复杂度为O(n)

  2. 必须提前分配固定数量的空间,当存储元素过少,会造成空间的浪费。

顺序表的代码实现

package com.xgc.linearlist.linkedlist.singlelinkedlist.withheadnode;

import com.xgc.linearlist.LinearListADT;

/**
 * 单链表,带有头结点
 */
public class SingleLinkedList implements LinearListADT{
	
	//头结点,为了编程方便
	private Node head = new Node();
	//元素个数
	private int size;

	@Override
	public int size() {
		return size;
	}

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

	@Override
	public Object get(int i) throws Exception {
		if(i<0 || i>=size) throw new Exception("索引越界");
		Node p = findNodeParent(i);
		return p.next.data;
	}

	@Override
	public void add(int i, Object o) throws Exception {
		if(i<0 || i>size) throw new Exception("索引越界");
		Node p = findNodeParent(i);
		//插入结点
		Node temp = p.next;
		p.next = new Node(o,temp);
		size++;
	}

	@Override
	public void add(Object o) throws Exception {
		this.add(size, o);
	}

	@Override
	public Object remove(int i) throws Exception {
		if(i<0 || i>=size) throw new Exception();
		Node p = findNodeParent(i);
		Node temp = p.next;
		p.next = p.next.next;
		size--;
		return temp.data;
	}

	@Override
	public boolean contains(Object o) {
		Node p = head;
		for(int i=0; i<size; i++) {
			p = p.next;
			if (o.equals(p.data)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public int indexOf(Object o) {
		Node p = head;
		for(int i=0; i<size; i++) {
			p = p.next;
			if (o.equals(p.data)) {
				return i;
			}
		}
		return -1;
	}
	
	//根据给定的索引,找到该索引位置上的结点的父类
	private Node findNodeParent(int i) {
		Node p = head;
		for(int j=0; j<i; j++) {
			p = p.next;
		}
		return p;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("[");
		Node p = head;
		for (int i = 0; i < size; i++) {
			p = p.next;
			if (i != size-1) {
				sb.append(p.data + ",");
			}else {
				sb.append(p.data);
			}
		}
		sb.append("]");
		return sb.toString();
	}

}

测试

测试实现是否成功:

package com.xgc.linearlist.sequentiallist;

import com.xgc.linearlist.LinearListADT;

public class SequentialListTest {

	public static void main(String[] args) throws Exception {
		LinearListADT list = new ArrayList();
		System.out.println(list.isEmpty());
		
		list.add("xgc");
		list.add("xiaoli");
		System.out.println(list);
		System.out.println(list.isEmpty());
		
		list.remove(1);
		System.out.println(list);
		
		System.out.println(list.contains("xgc"));
		
		System.out.println(list.size());
		
		System.out.println((String)list.get(0));
		
		System.out.println(list.indexOf("xgc"));
		
		//测试扩容是否成功
		list.add("zhangsan");
		list.add("xiaoming");
		list.add("xiaohong");
		list.add("lili");
		System.out.println(list);
	}
}

执行结果如下:

true
[xgc,xiaoli]
false
[xgc]
true
1
xgc
0
[xgc,zhangsan,xiaoming,xiaohong,lili]

链表

跟顺序表不一样,链表是一种在存储单元上非连续、非顺序的存储结构。

数据元素的逻辑顺序是通过链表中的指针的链接顺序来实现的。

链表由结点(链表中的每一个数据元素叫做结点)组成。

链表也分为单链表、循环链表和双向链表。

优点:

  1. 插入和删除效率比顺序表高。
  2. 不会造成存储空间的浪费。只有当有元素的时候,才创建结点

缺点:

  1. 存储密度没有顺序表高。因为它要花费额外的空间去存储指向下一个结点的指针。

  2. 查询的效率比顺序表低。时间复杂度为O(n)

单链表

链表中最简单的就是单链表。

单链表中的结点分为两个域:一个是存储数据的数据域,一个是存储下一个结点地址的指针域。

链表中第一个结点的存储地址叫做头指针。整个链表的存取就是从头指针开始的。

我们先来看看带有头结点的单链表

那么什么是头结点,为什么需要头结点呢?

  • 头结点是链表的第一个结点,放在第一个元素结点(也叫首元结点)的前面。

  • 头结点是为了操作的统一和方便而设立的,其数据域一般没有意义。

  • 有了头结点,对第一个元素的结点的插入和删除的操作就和其他元素结点相同了

  • 头结点不是链表必须的,但是建议加上,因为操作会比较方便。

如果链表中含有头结点,那么头指针指向的就是头结点。如果没有的话,那么指向的就是首元结点了。

我们来看看没有头结点的单链表:

单链表的代码实现

带有头结点

结点类

package com.xgc.linearlist.linkedlist.singlelinkedlist.withheadnode;

/**
 * 单链表的结点
 * @author xgc
 *
 */
public class Node {
	
	//数据域
	Object data;
	//指针域
	Node next;
	
	public Node() {}
	
	public Node (Object data) {
		this(data, null);
	}
	
	public Node (Object data, Node next) {
		this.data = data;
		this.next = next;
	}
	
}

单链表实现

package com.xgc.linearlist.linkedlist.singlelinkedlist.withheadnode;

import com.xgc.linearlist.LinearListADT;

/**
 * 单链表,带有头结点
 */
public class SingleLinkedList implements LinearListADT{
	
	//头指针,为了编程方便
	private Node head = new Node();
	//元素个数
	private int size;

	@Override
	public int size() {
		return size;
	}

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

	@Override
	public Object get(int i) throws Exception {
		if(i<0 || i>=size) throw new ArrayIndexOutOfBoundsException(i);
		Node p = findNodeParent(i);
		return p.next.data;
	}

	@Override
	public void add(int i, Object o) {
		if(i<0 || i>size) throw new ArrayIndexOutOfBoundsException(i);
		Node p = findNodeParent(i);
		//插入结点
		Node temp = p.next;
		p.next = new Node(o,temp);
		size++;
	}

	@Override
	public void add(Object o) {
		this.add(size, o);
	}

	@Override
	public Object remove(int i) {
		if(i<0 || i>=size) throw new ArrayIndexOutOfBoundsException(i);
		Node p = findNodeParent(i);
		Node temp = p.next;
		p.next = p.next.next;
		size--;
		return temp.data;
	}

	@Override
	public boolean contains(Object o) {
		Node p = head;
		for(int i=0; i<size; i++) {
			p = p.next;
			if (o.equals(p.data)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public int indexOf(Object o) {
		Node p = head;
		for(int i=0; i<size; i++) {
			p = p.next;
			if (o.equals(p.data)) {
				return i;
			}
		}
		return -1;
	}
	
	//根据给定的索引,找到该索引位置上的结点的父类
	private Node findNodeParent(int i) {
		Node p = head;
		for(int j=0; j<i; j++) {
			p = p.next;
		}
		return p;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("[");
		Node p = head;
		for (int i = 0; i < size; i++) {
			p = p.next;
			if (i != size-1) {
				sb.append(p.data + ",");
			}else {
				sb.append(p.data);
			}
		}
		sb.append("]");
		return sb.toString();
	}

}
测试

单链表测试,和顺序表一样,只是更改了实现类

package com.xgc.linearlist.linkedlist.singlelinkedlist.withheadnode;

import com.xgc.linearlist.LinearListADT;

public class SingleLinkedListTest {
	
	public static void main(String[] args) throws Exception {
		LinearListADT list = new SingleLinkedList();
		System.out.println(list.isEmpty());
		
		list.add("xgc");
		list.add("xiaoli");
		System.out.println(list);
		System.out.println(list.isEmpty());
		
		list.remove(1);
		System.out.println(list);
		
		System.out.println(list.contains("xgc"));
		
		System.out.println(list.size());
		
		System.out.println((String)list.get(0));
		
		System.out.println(list.indexOf("xgc"));
		
		list.add("zhangsan");
		list.add("xiaoming");
		list.add("xiaohong");
		list.add("lili");
		System.out.println(list);
	}
	
}

运行结果,和顺序表一样

true
[xgc,xiaoli]
false
[xgc]
true
1
xgc
0
[xgc,zhangsan,xiaoming,xiaohong,lili]

双向链表

双向链表和单链表类似,区别在于结点的有三个域:一个是保存数据的数据域,二个是指针域,分别指向上一个结点和下一个结点。

跟单链表相比,双向链表的遍历效率会比单链表高。但是存储密度就比单链表低。

为了编程的方便,我们可以在双向链表中带两个哑元结点来实现。其中一个是头结点,另一个是尾结点。它们都不存放数据元素。

双向链表的代码实现

带有头结点和尾结点

双向链表的结点:

package com.xgc.linearlist.linkedlist.doublylinkedlist.withheadnodeandtailnode;

/**
 * 双向链表的结点
 * @author xgc
 *
 */
public class Node {
	
	Node prev;
	Object data;
	Node next;
	
	public Node() {
	}
	
	public Node(Object data) {
		this.data = data;
	}
	
	public Node(Node prev, Object data, Node next) {
		this.prev = prev;
		this.data = data;
		this.next = next;
	}
}

双向链表的实现:

package com.xgc.linearlist.linkedlist.doublylinkedlist.withheadnodeandtailnode;

import com.xgc.linearlist.LinearListADT;

public class DoublyLinkedList implements LinearListADT{
	
	//头指针
	private Node head;
	//尾结点
	private Node last;
	//结点个数
	private int size;
	
	public DoublyLinkedList() {
		head = new Node();
		last = new Node();
		head.next = last;
		last.prev = head;
		this.size = 0;
	}

	@Override
	public int size() {
		return size;
	}

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

	@Override
	public Object get(int i) throws Exception {
		if (i<0 || i>=size) {
			throw new Exception("索引越界");
		}
		Node p = head;
		for(int j=0; j<=i; j++) {
			p = p.next;
		}
		return p.data;
	}

	@Override
	public void add(int i, Object o) throws Exception {
		if(i<0 || i>size) {
			throw new Exception("索引越界");
		}
		Node p = head;
		for(int j=0; j<i; j++) {
			p = p.next;
		}
		Node temp = p.next;
		Node data = new Node(p, o, temp);
		p.next = data;
		temp.prev = data;
		size++;
	}

	@Override
	public void add(Object o) {
		Node temp = last.prev;
		Node data  = new Node(temp, o, last);
		temp.next = data;
		last.prev = data;
		size++;
	}

	@Override
	public Object remove(int i) throws Exception {
		if (i<0 || i>=size) {
			throw new Exception("索引越界");
		}
		Node p = head;
		for(int j=0; j<i; j++) {
			p = p.next;
		}
		//被删除的结点
		Node temp = p.next;
		temp.next.prev = p;
		p.next = temp.next;
		size--;
		return temp.data;
	}

	@Override
	public boolean contains(Object o) {
		Node p = head;
		for(int j=0; j<size; j++) {
			p = p.next;
			if (o.equals(p.data)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public int indexOf(Object o) {
		Node p = head;
		for(int j=0; j<size; j++) {
			p = p.next;
			if (o.equals(p.data)) {
				return j;
			}
		}
		return -1;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("[");
		Node p = head;
		for (int i = 0; i < size; i++) {
			p = p.next;
			if (i != size-1) {
				sb.append(p.data + ",");
			}else {
				sb.append(p.data);
			}
		}
		sb.append("]");
		return sb.toString();
	}
	
}
测试
package com.xgc.linearlist.linkedlist.doublylinkedlist.withheadnodeandtailnode;

import com.xgc.linearlist.LinearListADT;

public class DoublyLinkedListTest {
	public static void main(String[] args) throws Exception {
		LinearListADT list = new DoublyLinkedList();
		System.out.println(list.isEmpty());
		
		list.add("xgc");
		list.add("xiaoli");
		System.out.println(list);
		System.out.println(list.isEmpty());
		
		list.remove(1);
		System.out.println(list);
		
		System.out.println(list.contains("xgc"));
		
		System.out.println(list.size());
		
		System.out.println((String)list.get(0));
		
		System.out.println(list.indexOf("xgc"));
		
		list.add("zhangsan");
		list.add("xiaoming");
		list.add("xiaohong");
		list.add("lili");
		System.out.println(list);
	}
}

测试结果

true
[xgc,xiaoli]
false
[xgc]
true
1
xgc
0
[xgc,zhangsan,xiaoming,xiaohong,lili]

循环链表

在上面的单链表和双向链表,最后一个结点的next指针域都赋值为null。

如果我们让最后一个结点的next指针域指向链表的第一个结点,那么这个链表就叫循环链表。

循环链表在单链表和双链表都可以实现。

循环单链表代码实现

下图是循环单链表的结构

Node结点

package com.xgc.linearlist.linkedlist.circularlinkedlist.circularsinglelinkedlist.withheadnode;

/**
 * 循环单链表的结点
 * @author xgc
 *
 */
public class Node {
	
	//数据域
	Object data;
	//指针域
	Node next;
	
	public Node() {}
	
	public Node (Object data) {
		this(data, null);
	}
	
	public Node (Object data, Node next) {
		this.data = data;
		this.next = next;
	}
	
}

循环单链表实现

package com.xgc.linearlist.linkedlist.circularlinkedlist.circularsinglelinkedlist.withheadnode;

import com.xgc.linearlist.LinearListADT;

public class CircularSingleLinkedList implements LinearListADT{

	//头指针
	private Node head;
	//首元结点
	private Node first;
	private int size;
	
	public CircularSingleLinkedList() {
		head = new Node();
		first = new Node();
		head.next = first;
		first.next = first;
	}
	
	@Override
	public int size() {
		return size;
	}

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

	@Override
	public Object get(int i) throws Exception {
		if(i<0 || i>=size) {
			throw new Exception("索引越界");
		}
		Node p = head;
		for(int j=0; j<=i; j++) {
			p = p.next;
		}
		return p.data;
	}

	@Override
	public void add(int i, Object o) throws Exception {
		if(i<0 || i>size) {
			throw new Exception("索引越界");
		}
		Node p = head;
		for(int j=0; j<i; j++) {
			p = p.next;
		}
		Node temp = p.next;
		Node data = new Node(o, temp);
		p.next = data;
		size++;
	}

	@Override
	public void add(Object o) throws Exception {
		this.add(size, o);
	}

	@Override
	public Object remove(int i) throws Exception {
		if(i<0 || i>=size) {
			throw new Exception("索引越界");
		}
		Node p = head;
		for(int j=0; j<i; j++) {
			p = p.next;
		}
		Node temp = p.next;
		p.next = temp.next;
		size--;
		return temp.data;
	}

	@Override
	public boolean contains(Object o) {
		Node p = head;
		for(int j=0; j<size; j++) {
			p = p.next;
			if (o.equals(p.data)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public int indexOf(Object o) {
		Node p = head;
		for(int j=0; j<size; j++) {
			p = p.next;
			if (o.equals(p.data)) {
				return j;
			}
		}
		return -1;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("[");
		Node p = head;
		for (int i = 0; i < size; i++) {
			p = p.next;
			if (i != size-1) {
				sb.append(p.data + ",");
			}else {
				sb.append(p.data);
			}
		}
		sb.append("]");
		return sb.toString();
	}

}
测试
package com.xgc.linearlist.linkedlist.doublylinkedlist.withheadnodeandtailnode;

import com.xgc.linearlist.LinearListADT;

public class DoublyLinkedListTest {
	public static void main(String[] args) throws Exception {
		LinearListADT list = new DoublyLinkedList();
		System.out.println(list.isEmpty());
		
		list.add("xgc");
		list.add("xiaoli");
		System.out.println(list);
		System.out.println(list.isEmpty());
		
		list.remove(1);
		System.out.println(list);
		
		System.out.println(list.contains("xgc"));
		
		System.out.println(list.size());
		
		System.out.println((String)list.get(0));
		
		System.out.println(list.indexOf("xgc"));
		
		list.add("zhangsan");
		list.add("xiaoming");
		list.add("xiaohong");
		list.add("lili");
		System.out.println(list);
	}
}

测试结果

true
[xgc,xiaoli]
false
[xgc]
true
1
xgc
0
[xgc,zhangsan,xiaoming,xiaohong,lili]