1、线性表
线性结构是最简单也是最常用的数据结构之一。线性数据结构的特点是:在数据元素的有限集中,除了第一个元素没有直接前驱,最后一个元素没有直接后继外,其他元素有且仅有一个前驱和一个后继。
线性表有两种存储实现方式,分别是顺序存储和链式存储,本文主要介绍两种方式实现线性表,并对他们的优缺点做一简单的比较。
1.1 线性表的顺序存储实现
import java.util.Arrays;
public class SequenceList {
private int DEFAULT_SIZE = 16;
//保存数组的长度
private int capacity;
//定义以一个线性表用于保存顺序表中的元素
private Object[] elementData;
//保存顺序表中元素的个数
private int size = 0;
//以默认数组长度创建空线性表
public SequenceList(){
capacity = DEFAULT_SIZE;
elementData = new Object[capacity];
}
//以一个初始化元素创建一个线性表
public SequenceList(T element){
this();
elementData[0] = element;
size++;
}
/**
* 已制定长度的数组来初始化线性表
* @param element 线性表的第一个元素
* @param initSize 指定顺序表底层的数组长度
*/
public SequenceList(T element, int initSize){
capacity = 1;
while(capacity < initSize){
capacity <<= 1;
}
elementData = new Object[capacity];
elementData[0] = element;
size++;
}
/**
* 获取线性表的长度
* @return 返回线性表的数据长度
*/
public int length(){
return size;
}
/**
* 获取索引为i的元素
* @param i 索引
* @return 返回索引处的元素
*/
@SuppressWarnings("unchecked")
public T get(int i){
if(i < 0 || i > size -1){
throw new IndexOutOfBoundsException("线性表索引越界");
}
return (T) elementData[i];
}
/**
* 查找顺序表中指定元素的索引
* @param element
* @return
*/
public int locate(T element){
for(int i = 0;i < size;i++){
if(elementData[i] == element){
return i;
}
}
return -1;
}
/**
* 在线性表的指定位置插入元素
* @param element 需要插入的元素
* @param index 插入元素的位置
*/
public void insert(T element,int index){
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("线性表索引越界");
}
ensureCapacity(size + 1);
//将指定索引处的元素都向后移动一格
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
/**
* 在线性表的末尾添加一格元素
* @param element
*/
public void insertAtFirst(T element){
insert(element,0);
}
/**
* 确保线性表的最小容量
* @param minCapacity 指定最小容量
*/
private void ensureCapacity(int minCapacity) {
if(capacity < minCapacity){
while(capacity < minCapacity){
capacity <<= 1;
}
elementData = Arrays.copyOf(elementData, capacity);
}
}
/**
* 删除指定索引出的元素
* @param index 索引
* @return 返回被删除的元素
*/
public T delete(int index){
if(index < 0 || index > size -1){
throw new IndexOutOfBoundsException("数组越界异常 ");
}
@SuppressWarnings("unchecked")
T oldValue = (T) elementData[index];
int numMoved = size - index - 1;
if(numMoved > 0){
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
//清空最后一个元素
elementData[--size] = null;
return oldValue;
}
/**
* 删除最后一个元素
* @return 返回最后一个元素
*/
public T remove(){
return delete(size -1);
}
/**
* 判断线性表是否为空
* @return 根据size的是否为零来判断
*/
public boolean isEmpty(){
return size == 0;
}
/**
* 清空线性表
*/
public void clear(){
//将底层数组的所有元素全部置为空
Arrays.fill(elementData, null);
size = 0;
}
public String toString(){
if(size == 0){
return "[]";
}else{
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < size; i++) {
sb.append(elementData[i].toString()+",");
}
int len = sb.length();
return sb.delete(len -2, len).append("]").toString();
}
}
}
1.2 线性表的链式存储实现——单链表
public class LinkList {
/**
* 定义一个内部内,实例表示链表的节点
*
* @author Administrator
*
*/
private class Node {
// 保存节点数据
private T data;
// 指向下一个节点
private Node next;
// 无参构造器
public Node() {
}
// 带参构造器
public Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
// 保存链表的头节点
private Node header;
// 保存链表的尾节点
private Node tail;
// 保存节点的个数
private int size;
// 创建空链表
public LinkList() {
header = null;
tail = null;
}
// 已制定元素来创建链表
public LinkList(T element) {
header = new Node(element, null);
tail = header;
size++;
}
// 返回链表的长度
public int length() {
return size;
}
// 获取链表指定索引处的元素
public T get(int index) {
return getNodeByIndex(index).data;
}
// 根据索引获取指定位置的节点
private Node getNodeByIndex(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("数组越界异常 ");
}
// 从header节点开始
Node current = header;
for (int i = 0; i < size; i++) {
if (current != null) {
if (i == index) {
return current;
}
current = current.next;
}
}
return null;
}
// 查找线性表中指定元素的索引
public int getIndex(T element) {
// 从header节点开始
Node current = header;
for (int i = 0; i < size; i++) {
if (current != null) {
if (current.data.equals(current)) {
return i;
}
}
}
return -1;
}
// 向链式表的 指定位置插入元素
public void insert(T element, int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("数组越界异常 ");
}
// 如果是空链表
if (header == null) {
add(element);
} else {
// 在标头插入
if (index == 0) {
addAtHeader(element);
} else {
// 获取插入节点的前一个节点
Node pre = getNodeByIndex(index - 1);
// 新节点指向前一个节点的下一个节点,前一个节点的下一个节点指向新节点
pre.next = new Node(element, pre.next);
size++;
}
}
}
// 头插法
private void addAtHeader(T element) {
// 创建新节点,并且让新节点指向header
header = new Node(element, header);
if (tail == null) {
tail = header;
}
size++;
}
// 采用尾插法插入一个元素
private void add(T element) {
// 如果为空链表
if (header == null) {
header = new Node(element, null);
tail = header;
}
// 链表不为空
else {
Node newNode = new Node(element, null);
tail.next = newNode;
tail = newNode;
}
size++;
}
// 删除指定索引处的节点
public T delete(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("数组越界异常 ");
}
Node del = null;
// 如果删除的是第一个节点
if (index == 0) {
del = header;
header = header.next;
} else {
// 获取删除节点的前一个节点
Node pre = getNodeByIndex(index - 1);
// 获取将要被删除的节点
del = pre.next;
pre.next = del.next;
// 将被删除节点的next置为空
del.next = null;
}
size--;
return del.data;
}
// 删除链表的最后一个节点元素
public T remove() {
return delete(size - 1);
}
/**
* 判断线性表是否为空
*
* @return 根据size的是否为零来判断
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 清空线性表
*/
public void clear() {
header = null;
tail = null;
size = 0;
}
public String toString() {
if (size == 0) {
return "[]";
} else {
StringBuilder sb = new StringBuilder("[");
for (Node current = header; current != null; current = current.next) {
sb.append(current.data.toString() + ",");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
}
}
1.3 线性表的链式存储实现——双链表
public class DuLinkList {
/**
* 定义一个内部内,实例表示链表的节点
*
* @author Administrator
*
*/
private class Node {
// 保存节点数据
private T data;
// 指向下一个节点
private Node next;
// 指向前一个节点
private Node pre;
// 无参构造器
public Node() {
}
// 含参构造器
public Node(T data, Node next, Node pre) {
this.data = data;
this.next = next;
this.pre = pre;
}
}
// 保存链表的头节点
private Node header;
// 保存链表的尾节点
private Node tail;
// 保存节点的个数
private int size;
// 创建空链表
public DuLinkList() {
header = null;
tail = null;
}
// 以指定数据元素来创建链表
public DuLinkList(T element) {
header = new Node(element, null, null);
tail = header;
size++;
}
// 根据索引获取指定位置的节点
private Node getNodeByIndex(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("数组越界异常 ");
}
if (index <= size / 2) {
// 从header开始遍历
Node current = header;
for (int i = 0; i <= size / 2 && current != null; i++, current = current.next) {
if (i == index) {
return current;
}
}
} else {
// 从结尾开始遍历
Node current = tail;
for (int i = 0; i > size / 2 && current != null; i++, current = current.pre) {
if (i == index) {
return current;
}
}
}
return null;
}
// 返回链表的长度
public int length() {
return size;
}
// 获取链表中索引为index的元素
public T get(int index) {
return getNodeByIndex(index).data;
}
// 查找指定元素在线性表中的索引
public int locate(T element) {
// 从头节点开始遍历
Node current = header;
for (int i = 0; i < size && current != null; i++, current = current.next) {
if (current.data.equals(element)) {
return i;
}
}
return -1;
}
// 采用尾插法为链表插入一个元素
public void inserAtLast(T element) {
// 如果链表为空链表
if (header == null) {
header = new Node(element, null, null);
tail = header;
} else {
// 创建新节点新节点的pre指向原tail
Node newNode = new Node(element, tail, null);
// 让尾节点的next指向新节点
tail.next = newNode;
tail = newNode;
}
size++;
}
// 采用头插法为链表插入一个元素
public void insertAtFirst(T element) {
// 创建新节点,新节点的next指向原来的header,并且让新节点作为新的header
header = new Node(element, null, header);
// 插入之前为空链表
if (tail == null) {
tail = header;
}
size++;
}
// 向链表的指定位置插入元素
public void insert(T element, int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("数组越界异常 ");
}
// 插入之前为空链表
if (header == null) {
insertAtFirst(element);
} else {
if (index == 0) {
insertAtFirst(element);
} else {
// 获取插入节点的前一个节点
Node prev = getNodeByIndex(index - 1);
// 新建要插入的检点
Node newNode = new Node(element, prev, prev.next);
// 让prev的next指向新节点
prev.next = newNode;
// 让原来的index节点的pre指向新节点
prev.next.pre = newNode;
size++;
}
}
}
// 删除指定索引处的元素
public T remove(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("数组越界异常 ");
}
Node del = null;
// 如果删除的是头结点
if (index == 0) {
header = header.next;
// 释放对原来头节点的引用
header.pre = null;
} else {
// 获取删除节点的前一个节点
Node prev = getNodeByIndex(index - 1);
// 获取被删除的节点
del = prev.next;
// 让被删除节点的前一个节点的next指向删除节点的next
prev.next = del.next;
// 被删除节点的下一个节点的next指向被删除节点的前一个节点
if (del.next != null) {
del.next.pre = prev.next;
}
// 将被删除节点的next和pre引用置为空
del.pre = null;
del.next = null;
}
size--;
return del.data;
}
// 删除链表的最后一个元素
public T delete() {
return remove(size - 1);
}
// 将链表置为空
public void clear() {
header = null;
tail = null;
size = 0;
}
public String toString() {
if (size == 0) {
return "[]";
} else {
StringBuilder sb = new StringBuilder("[");
for (Node current = header; current != null; current = current.next) {
sb.append(current.data.toString() + ",");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
}
public String reverseToString() {
if (size == 0) {
return "[]";
} else {
StringBuilder sb = new StringBuilder("[");
for (Node current = tail; current != null; current = current.pre) {
sb.append(current.data.toString() + ",");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
}
}
1.4 顺序表和链表的比较
线性表的常用操作主要有查找、插入、删除,下面就这三个方面分别从时间复杂度和空间复杂度进行比较分析。
1.4.1 基于时间的比较
- 顺序表的读取性能优于链表:顺序表中元素的逻辑顺序与物理存储顺序一致,而且支持随机存取,因此顺序表在查找、读取时性能很好;而链表必须从头开始遍历查找元素,无法在O(1)的时间内完成。
- 基于元素的插入删除操作:顺序表中插入和删除操作需要移动大量的元素;相比较而言链表只需要在查找到位置后修改几个指针的引用即可,因此对于插入和删除较多的操作,应该选择链式存储结构的链表。
1.4.2 基于空间的比较
- 当线性表的长度范围变化比较大时,应该考虑采用链表:顺序表,其存储空间是预先静态分配的,虽然在存储期间可以扩展数组的容量,但是如果线性表的长度范围变化范围较大,则在使用中会存在大量的空闲空间,空间的利用率不高;而链表的存储空间是动态分配的,不会存在空间浪费的问题。
- 当线性表的长度范围变化不大时,应该考虑采用顺序表。