前言
数组和链表是非常基本的数据结构,而数据结构就像我们常用的数据库一样,只不过我们将数据存在了运行程序的内存中,不同的数据结构就像是不同的数据库,每一个都有自己的特点,需要进行深入的了解。像我们常用的ArrayList,LinkedList就是对数组和链表的一个很好的实现。
数组简介
众所周知,数组的特点就是最大的优点是查询速度快。这是因为在知道改元素的索引下,可以直接根据arr[index] 获取到该值,时间复杂度为O(1)。缺点是数组容量固定死的。那么什么场景下应该使用数组呢?数组最好应用在索引有语意的情况下。比如score[6] = 100,表示学号为6的学生分数是100分。
容量和大小
什么是数组容量,什么是数组的大小。做一个比喻,家用冰箱一般是8层,每一层都是隔开来的。现在冰箱里面6层放了食材。那么8就代表数组的容量,也就是capacity,6层放了食材代表数组的大小,也就是size。在Java中数组只有一个公开的域,也就是length。代表数组的容量。
链表简介
链表是真正的动态数据结构,也是最简单的动态数据结构。优点是真正的动态,不需要处理固定容量的大小。缺点是失去了随机访问的能力。
动态数组
在Java中我们可以对一个数组进行扩容,让一个数组达到一个动态的效果。其中java.util中的ArrayList就是对一个数组的动态实现。现在我通过代码实现以下动态数组。原理和ArrayList差不多。
代码如下:
/**
* @ClassName Array
* @Description 自己封装的一个动态数组
* @Author ouyangkang
* @Date 2019-01-21 09:24
**/
public class Array<E> {
//数组
private E[] data;
//数组大小
private int size;
// 容量 可以不用,为什么? 因为 他的大小就是data.length;
// private int capacity;
// 构造函数,传入数组容量capacity构造函数Array
public Array(int capacity) {
data = (E[]) new Object[capacity];
size = 0;
}
// 默认参数 容量等于10
public Array() {
this(10);
}
// 获取数组大小
public int getSize() {
return size;
}
// 获取数据容量
public int getCapacity() {
return data.length;
}
// 数组大是否为空
public boolean isEmpty() {
return getSize() == 0;
}
// 添加一个元素 扩容
public void addLast(E e) {
add(size, e);
}
public void addFirst(E e) {
add(0, e);
}
// 在第index插入e
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("addLast failed : index < 0 or index > size");
}
if (size == getCapacity()) {
resize(2 * data.length);
}
// 数组插入前的元素,后移
for (int i = size - 1; i >= index; i--) {
data[i + 1] = data[i];
}
data[index] = e;
size++;
}
// 获取index位置上的元素
public E get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Get failed . Index is illegal");
}
return data[index];
}
// 更新index位置上的元素
public void set(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Set failed . Index is illegal");
}
data[index] = e;
}
// 是否包含某个元素e
public boolean contains(E e) {
for (int i = 0; i < size; i++) {
if (data[i] == e){
return true;
}
}
return false;
}
// 查找改元素的位置
public int find(E e) {
for (int i = 0; i < size; i++) {
if (data[i] == e) {
return i;
}
}
return -1;
}
//移除指定index位置上的元素
public E remove(int index) {
if (index < 0 || index > size)
throw new IllegalArgumentException("remove failed . Index is illegal");
E ret = data[index];
for (int i = index + 1; i < size; i++) {
data[i - 1] = data[i];
}
size--;
data[size] = null;
//缩容操作
if (size == data.length / 4 && data.length / 2 != 0) {
resize(data.length / 2);
}
return ret;
}
// 移除第一个
public E removeFirst() {
return remove(0);
}
//移除最后一个
public E removeLast() {
return remove(size - 1);
}
// 移除某一个元素 有就删除 没有就不删除
public void removeElement(E e) {
int i = find(e);
if (i != -1) {
remove(i);
}
}
// 删除所有元素
public void removeAllElement(E e) {
boolean flag = true;
while (flag) {
int i = find(e);
if (i != -1) {
remove(i);
} else {
flag = false;
}
}
}
// 扩容操作 想要扩容大小
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
//全部赋值给新的数组
newData[i] = data[i];
}
data = newData;
}
public E getLast() {
return get(size - 1);
}
public E getFirst() {
return get(0);
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("Array: size = %d, capacity=%d \n", size, data.length));
res.append('[');
for (int i = 0; i < size; i++) {
res.append(data[i]);
if (i != size - 1) {
res.append(", ");
}
}
res.append(']');
return res.toString();
}
}
有兴趣的可以仔细看一下,写个main方法测试一下。
链表实现
代码如下:
/**
* @ClassName LinkedList
* @Description 链表
* @Author ouyangkang
* @Date 2019-01-25 10:22
**/
public class LinkedList<E> {
// 节点类
private class Node {
public E e;
public Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
public String toString() {
return e.toString();
}
}
//利用虚拟头节点
private Node dummyHead;
private int size;
public LinkedList() {
dummyHead = new Node(null, null);
size = 0;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
//在链表中添加一个元素
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("add failed Illegal index");
}
Node prv = dummyHead;
for (int i = 0; i < index; i++) {
prv = prv.next;
}
// Node node = new Node(e);
// node.next = prv.next;
// prv.next = node;
prv.next = new Node(e, prv.next);
size++;
}
//在链表头添加新元素 e
public void addFirst(E e) {
// Node node = new Node(e);
// node.next = head;
// head = node;
add(0, e);
}
// 在链表尾部
public void addLast(E e) {
add(size, e);
}
//获取链表的第index个位置的元素
// 在链表中不是一个常用操作
public E get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("get failed Illegal index");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.e;
}
public E getFirst() {
return get(0);
}
public E getLast() {
return get(size - 1);
}
// 更新
public void set(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("set failed Illegal index");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
// 是否包含
public boolean contains(E e) {
Node cur = dummyHead.next;
while (cur != null) {
if (cur.e.equals(e)) {
return true;
} else {
cur = cur.next;
}
}
return false;
}
// 删除节点 返回节点值
public E remove(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("remove failed Illegal index");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node delNode = prev.next;
prev.next = delNode.next;
delNode.next = null;
size--;
return delNode.e;
}
public E removeFirst() {
return remove(0);
}
public E removeLast() {
return remove(size - 1);
}
public String toString() {
StringBuilder res = new StringBuilder();
Node cur = dummyHead.next;
while (cur != null) {
res.append(cur + "->");
cur = cur.next;
}
res.append("NULL");
return res.toString();
}
}
有兴趣的可以写一个main方法测试一下。
数组和链表的扩展
我们可以根据上面写的Array类或者LinkedList类,实现一个队列,栈。只要你编写的队列,栈拥有一个Array类或者LinkedList类就可以实现了。下面我通过代码实现一下队列。队列是一种先进先出的数据结构,也就是First in First Out (FIFO)。代码如下:
/**
* @ClassName ArrayQueue
* @Description 数组实现队列
* @Author ouyangkang
* @Date 2019-01-23 17:44
**/
public class ArrayQueue<E> implements Queue<E> {
private Array<E> array;
public ArrayQueue(){
array = new Array<>();
}
public ArrayQueue(int capacity){
array = new Array<>(capacity);
}
@Override
public void enQueue(E e) {
array.addLast(e);
}
@Override
public E deQueue() {
return array.removeFirst();
}
@Override
public E getFront() {
return array.getFirst();
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("Queue :");
res.append("front [");
for (int i = 0; i < array.getSize(); i++) {
res.append(array.get(i));
if (i != array.getSize() - 1){
res.append(", ");
}
}
res.append("] tail");
return res.toString();
}
}
/**
* @ClassName LinkListQueue
* @Description 链表实现队列
* @Author ouyangkang
* @Date 2019-01-29 09:08
**/
public class LinkListQueue<E> implements Queue<E> {
private class Node {
public E e;
public Node next;
public Node(Node next, E e) {
this.next = next;
this.e = e;
}
public Node(E e) {
this(null, e);
}
public Node() {
this(null, null);
}
public String toString(){
return e.toString();
}
}
private Node head;
private Node tail;
private int size;
@Override
public void enQueue(E e) {
if (tail == null) {
tail = new Node(e);
head = tail;
} else {
tail.next = new Node(e);
tail = tail.next;
}
size++;
}
@Override
public E deQueue() {
if (isEmpty()) {
throw new IllegalArgumentException("queue isEmpty");
}
Node ret = head;
head = head.next;
ret.next = null;
if (head == null) {
tail = null;
}
size--;
return ret.e;
}
@Override
public E getFront() {
if (isEmpty()) {
throw new IllegalArgumentException("queue isEmpty");
}
return head.e;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
public String toString() {
StringBuilder res = new StringBuilder();
res.append("Queue: front ");
Node cur = head;
while (cur != null) {
res.append(cur + "->");
cur = cur.next;
}
res.append("NULL tail");
return res.toString();
}
}
上面通过数组实现的队列,出队操作涉及到数组中元素的移动。时间复杂度为O(n),入队操作的时间复杂度为O(1)。链表实现的队列,入队,出队操作时间复杂度都是O(1)。但是我们可以根据数组来实现一个循环队列。这样的入队,出队操作时间复杂度都是O(1)了。有兴趣的可以实现一下。
总结
数组和链表是我们非常常用的两种数据结构。也有很多数据结构是数组和链表的变形实现。总的来说,如果复杂的数据结构像一套房子的话,那么数组和链表就是砖和水泥。