版权声明:本文为博主原创文章,未经博主允许不得转载。
一、比较
1.1 动态数组
- 动态数组的缺点:可能会造成内存空间的大量浪费
- 动态数组是一种 顺序存储,所有元素的 内存地址是连续 的
1.2 链表
- 用多少内存就申请多少内存
- 链表是一种 链式存储 的线性表,所有元素的 内存地址不一定是连续 的
二、链表
2.1 接口设计
- 创建 LinkedList 类,用来管理链表数据,其中的 size 属性记录存储数据的数量,first 属性引用链表的第 0 个元素
- 创建私有类 Node,其中的 element 属性用于存储元素, next 属性用于指向链表中的下一个节点
public class LinkedList<E> {
private int size;
private Node<E> first;
int size();
boolean isEmpty();
boolean contains(E element);
void add(E element);
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(E element);
void clear();
private class Node<E> {
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
}
2.2 方法实现
2.2.1 构造方法
- 链表的创建与动态数组不同,动态数组在构造时需要传入一个空间属性,来决定这个数组的容量。但链表元素是在添加时才创建的,内存地址不一定是连续的。所以链表不需要在单独设计构造方法,使用默认构造方法即可
2.2.2 添加元素
- 添加数据时,需要创建一个节点存储数据,并将该节点拼接到最后节点的后面,然后 size加1
- 需要区分 当前链表没有数据,新节点拼接到 first 和 当前链表有数据,新节点拼接到最后的节点
public void add(E element) {
if (first == null) {
first = new Node<E>(element, null);
}
else {
Node<E> node = node(size - 1);
node.next = new Node<E>(element, null);
}
size++;
}
2.2.3 插入元素
- 插入链表,首先需要创建新节点,然后通过 变更插入位置前一个元素 next 指针指向 ,插入指定位置即可
- 需要区分 插入到 0 的位置,使用 first 指向新节点 和 插入到非 0 位置,找到前一个节点进行处理 两种情况
2.2.3.1 数组越界
- 插入元素的位置必须 不能小于0, 也不能大于等于size ,所以我们在插入元素之前需要先进行索引检查
protected void outOfBounds(int index) {
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
protected void rangeCheck(int index) {
if (index < 0 || index >= size) {
outOfBounds(index);
}
}
插入元素 代码如下:
在编写链表过程中,要注意边界测试、如 index 为0、size - 1、size时的情况
public void add(int index, E element) {
rangeCheckForSize(index);
if (index == 0) {
first = new Node<E>(element, first.next);
}else {
Node<E> prev = node(index - 1);
prev.next = new Node<>(element, prev.next);
}
size++;
}
添加元素 也可以简写:
public void add(E element) {
add(size, element);
}
2.2.4 删除元素
- 首先找到删除节点 (delete_node)的前一个节点 (pre_node),然后通过 变更(pre_node)节点 next 指针指向删除节点(delete_node)的下一个节点 即可,然后 size 减 1
- 需要判断是否删除的 第 0 个元素,如果是,则 使用 first 指向第1个节点
public E remove(int index) {
rangeCheck(index);
Node<E> old = first;
if (index == 0) {
first = first.next;
}else {
Node<E> prev = node(index - 1);
old = prev.next;
prev.next = old.next;
}
size--;
return old.element;
}
2.2.5 清空元素
- 将 first 指向 null,释放链表所有 node ,同时 size 置为 0 即可
public void clear() {
first = null;
size = 0;
}
2.2.6 修改元素
private Node<E> node(int index) {
rangeCheck(index);
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
public E set(int index, E element) {
Node<E> node = node(index);
E old = node.element;
node.element = element;
return old;
}
2.2.7 查找元素
public E get(int index) {
return node(index).element;
}
2.2.8 查找元素索引
- 查找指定元素的索引,需要遍历所有节点,找到节点对应的元素与执行元素相等即可
- 如果需要支持节点 element 为 null ,则需要分两种情况处理
private static final int ELEMENT_ON_FOUND = -1;
public int indexOf(E element) {
Node<E> node = first;
if (element == null) {
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
}else {
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_ON_FOUND;
}
2.2.9 获取链表存储元素的个数
public int size() {
return size;
}
2.2.10 链表是否为空
- 链表是否为空, 只需要判断 size 是否等于 0 即可
public boolean isEmpty() {
return size == 0;
}
2.2.11 判断元素是否存在
- 判断元素是否存在, 只需要判断元素的索引是否为 ELEMENT_ON_FOUND 即可
public boolean contains(E element) {
return indexOf(element) != ELEMENT_ON_FOUND;
}
2.2.12 打印链表中存储的数据
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size = ").append(size).append(", [");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(",");
}
string.append(node.element);
node = node.next;
}
string.append("]");
return string.toString();
}
2.3 链表的复杂度
2.4 完整代码
2.4.1 AbstractList.java
package com.mj;
public abstract class AbstractList<E> implements List<E> {
protected int size;
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public boolean contains(E element) {
return indexOf(element) != ELEMENT_NOT_FOUND;
}
public void add(E element) {
add(size, element);
}
protected void outOfBounds(int index) {
throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
}
protected void rangeCheck(int index) {
if (index < 0 || index >= size) {
outOfBounds(index);
}
}
protected void rangeCheckForAdd(int index) {
if (index < 0 || index > size) {
outOfBounds(index);
}
}
}
2.4.2 List.java
package com.mj;
public interface List<E> {
static final int ELEMENT_NOT_FOUND = -1;
void clear();
int size();
boolean isEmpty();
boolean contains(E element);
void add(E element);
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(E element);
}
2.4.3 LinkedList.java
package com.mj;
import com.mj.AbstractList;
public class LinkedList<E> extends AbstractList<E> {
private Node<E> first;
private Node<E> last;
private static class Node<E> {
E element;
Node<E> prev;
Node<E> next;
public Node(Node<E> prev, E element, Node<E> next) {
this.prev = prev;
this.element = element;
this.next = next;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (prev != null) {
sb.append(prev.element);
} else {
sb.append("null");
}
sb.append("_").append(element).append("_");
if (next != null) {
sb.append(next.element);
} else {
sb.append("null");
}
return sb.toString();
}
}
@Override
public void clear() {
size = 0;
first = null;
last = null;
}
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E old = node.element;
node.element = element;
return old;
}
@Override
public void add(int index, E element) {
rangeCheckForAdd(index);
if (index == size) {
Node<E> oldLast = last;
last = new Node<>(oldLast, element, null);
if (oldLast == null) {
first = last;
} else {
oldLast.next = last;
}
} else {
Node<E> next = node(index);
Node<E> prev = next.prev;
Node<E> node = new Node<>(prev, element, next);
next.prev = node;
if (prev == null) {
first = node;
} else {
prev.next = node;
}
}
size++;
}
@Override
public E remove(int index) {
rangeCheck(index);
Node<E> node = node(index);
Node<E> prev = node.prev;
Node<E> next = node.next;
if (prev == null) {
first = next;
} else {
prev.next = next;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
}
size--;
return node.element;
}
@Override
public int indexOf(E element) {
if (element == null) {
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
} else {
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
private Node<E> node(int index) {
rangeCheck(index);
if (index < (size >> 1)) {
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
} else {
Node<E> node = last;
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
return node;
}
}
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(", ");
}
string.append(node);
node = node.next;
}
string.append("]");
return string.toString();
}
}
2.4.4 ArrayList.java
package com.mj;
@SuppressWarnings("unchecked")
public class ArrayList<E> extends AbstractList<E> {
private E[] elements;
private static final int DEFAULT_CAPACITY = 10;
public ArrayList(int capaticy) {
capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy;
elements = (E[]) new Object[capaticy];
}
public ArrayList() {
this(DEFAULT_CAPACITY);
}
public void clear() {
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
}
public E get(int index) {
rangeCheck(index);
return elements[index];
}
public E set(int index, E element) {
rangeCheck(index);
E old = elements[index];
elements[index] = element;
return old;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacity(size + 1);
for (int i = size; i > index; i--) {
elements[i] = elements[i - 1];
}
elements[index] = element;
size++;
}
public E remove(int index) {
rangeCheck(index);
E old = elements[index];
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
elements[--size] = null;
return old;
}
public int indexOf(E element) {
if (element == null) {
for (int i = 0; i < size; i++) {
if (elements[i] == null) return i;
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(elements[i])) return i;
}
}
return ELEMENT_NOT_FOUND;
}
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
if (oldCapacity >= capacity) return;
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
System.out.println(oldCapacity + "扩容为" + newCapacity);
}
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(", ");
}
string.append(elements[i]);
}
string.append("]");
return string.toString();
}
}
2.4.5 ArrayList2.java
package com.mj;
@SuppressWarnings("unchecked")
public class ArrayList2<E> extends AbstractList<E> {
private E[] elements;
private static final int DEFAULT_CAPACITY = 10;
public ArrayList2(int capaticy) {
capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy;
elements = (E[]) new Object[capaticy];
}
public ArrayList2() {
this(DEFAULT_CAPACITY);
}
public void clear() {
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
if (elements != null && elements.length > DEFAULT_CAPACITY) {
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
}
public E get(int index) {
rangeCheck(index);
return elements[index];
}
public E set(int index, E element) {
rangeCheck(index);
E old = elements[index];
elements[index] = element;
return old;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacity(size + 1);
for (int i = size; i > index; i--) {
elements[i] = elements[i - 1];
}
elements[index] = element;
size++;
}
public E remove(int index) {
rangeCheck(index);
E old = elements[index];
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
elements[--size] = null;
trim();
return old;
}
public int indexOf(E element) {
if (element == null) {
for (int i = 0; i < size; i++) {
if (elements[i] == null) return i;
}
} else {
for (int i = 0; i < size; i++) {
if (element.equals(elements[i])) return i;
}
}
return ELEMENT_NOT_FOUND;
}
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
if (oldCapacity >= capacity) return;
int newCapacity = oldCapacity + (oldCapacity >> 1);
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
System.out.println(oldCapacity + "扩容为" + newCapacity);
}
private void trim() {
int oldCapacity = elements.length;
int newCapacity = oldCapacity >> 1;
if (size > (newCapacity) || oldCapacity <= DEFAULT_CAPACITY) return;
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
System.out.println(oldCapacity + "缩容为" + newCapacity);
}
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(", ");
}
string.append(elements[i]);
}
string.append("]");
return string.toString();
}
}
2.4.6 Asserts.java
package com.mj;
public class Asserts {
public static void test(boolean value) {
try {
if (!value) throw new Exception("测试未通过");
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.4.7 Main.java
package com.mj;
import com.mj.circle.CircleLinkedList;
public class Main {
static void testList(List<Integer> list) {
list.add(11);
list.add(22);
list.add(33);
list.add(44);
list.add(0, 55);
list.add(2, 66);
list.add(list.size(), 77);
list.remove(0);
list.remove(2);
list.remove(list.size() - 1);
Asserts.test(list.indexOf(44) == 3);
Asserts.test(list.indexOf(22) == List.ELEMENT_NOT_FOUND);
Asserts.test(list.contains(33));
Asserts.test(list.get(0) == 11);
Asserts.test(list.get(1) == 66);
Asserts.test(list.get(list.size() - 1) == 44);
System.out.println(list);
}
static void josephus() {
CircleLinkedList<Integer> list = new CircleLinkedList<>();
for (int i = 1; i <= 8; i++) {
list.add(i);
}
list.reset();
while (!list.isEmpty()) {
list.next();
list.next();
System.out.println(list.remove());
}
}
public static void main(String[] args) {
josephus();
}
}
3.3 环形链表
https://leetcode-cn.com/problems/linked-list-cycle/
3.4 移除链表元素
https://leetcode-cn.com/problems/remove-linked-list-elements/
3.5 删除排序链表中的重复元素
https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
3.6 链表的中间结点
https://leetcode-cn.com/problems/middle-of-the-linked-list/solution/
版权声明:本文为博主原创文章,未经博主允许不得转载。
|