1.定义List接口
package com.huawei.list;
/**
* 功能描述
*
* @author wWX1050875
* @since 2022-11-03
*/
public interface List<E> {
int ELEMENT_NOT_FOUND = -1;
boolean contains(E element); // 是否包含某个元素
int size();
boolean isEmpty();
void add(E element); // 添加元素到最后面
E get(int index); // 返回index位置对应的元素
E set(int index, E element); // 设置index位置的元素
void add(int index, E element); // 往index位置添加元素
E remove(int index); // 删除index位置对应的元素
int remove(E element) ;
int indexOf(E element); // 查看元素的位置
void clear(); // 清除所有元素
}
2.定义抽象父类
package com.huawei.list;
/**
* 功能描述:抽象类,对外界不可见,用来抽取list公共属性和实现代码
*
* @author
* @since 2022-11-03
*/
public abstract class AbstractList <E> implements List<E> {
protected int size;
@Override
public boolean contains(E element) {
return indexOf(element) != ELEMENT_NOT_FOUND;
}
@Override
public void add(E element) {
add(size, element);
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public int remove(E element) {
int index = indexOf(element);
remove(index);
return index;
}
protected void checkRangeIndex(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("index error:" + index);
}
}
protected void checkRangeIndexForAdd(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("index error:" + index);
}
}
}
3.LinkedList实现
package com.huawei.list;
/**
* 功能描述
*
* @author wWX1050875
* @since 2022-11-03
*/
public class LinkedList<E> extends AbstractList<E> implements List<E> {
/**
* 链表设计思路:
* 1.链表是层层递进,没有初始容量
* 2。链表需要有一个节点对象(Node)来记录当前元素的值,以及下一个Node
* 3.链表需要有一个头节点
*/
// 定义静态内部类
private static class Node<E> {
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
private Node<E> first;
@Override
public E get(int index) {
return node(index).element;
}
// 复杂度分析:
// 最好情况复杂度,找第一个元素,O(1)
// 最坏情况复杂度,找最后一个元素,O(n)
// 平均情况复杂度,O(n)
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E oldElement = node.element;
node.element = element;
return oldElement;
}
// 复杂度分析:
// 最好情况复杂度,找第一个元素,O(1)
// 最坏情况复杂度,找最后一个元素,O(n)
// 平均情况复杂度,O(n)
@Override
public void add(int index, E element) {
checkRangeIndexForAdd(index);
// 在index位置添加元素
// 思路:
// 1.在index位置添加元素,有两种思路
// 1)找到index位置的节点,然后把新节点的next指向index位置的节点
// 2) 再把index节点上一个节点的next指向当前节点,这个又需要调用找节点的方法,因此不采取
// 或者
// 1)找到index-1位置的节点
// 2)新节点的next指向index-1位置的next
// 3) index-1位置的next指向新节点
// 采取第二种方法:
if (index == 0) {
// index = 0,代表在头部添加新节点
// 那么新节点是first,新节点的next是之前first,那么:
first = new Node<>(element, first);
} else {
// 找到index位置的上一个节点
Node<E> prev = node(index - 1);
// 新建节点
// Node<E> newNode = new Node<>(element, prev.next);
// index - 1 位置的next指向新节点
// prev.next = newNode;
prev.next = new Node<>(element, prev.next);
}
size ++;
}
// 复杂度分析:
// 最好情况复杂度,找第一个元素,O(1)
// 最坏情况复杂度,找最后一个元素,O(n)
// 平均情况复杂度,O(n)
@Override
public E remove(int index) {
checkRangeIndex(index);
// 思路:
// 1)找到当前节点
// 2)当前节点的前一个的节点的next指向当前节点的next
// 如果直接找当前节点,那么当找节点方法又要执行两次
// 因此这里直接找当前要删除节点的前一个节点
Node<E> removeNode = first;
if (index == 0) {
// index = 0,则是删除头节点,那么直接让first指向它的next即可
first = removeNode.next;
} else {
Node<E> node = node(index - 1);
removeNode = node.next;
node.next = removeNode.next;
}
size -- ;
return removeNode.element;
}
@Override
public int indexOf(E element) {
Node<E> node = first;
// element空校验
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_NOT_FOUND;
}
@Override
public void clear() {
size = 0;
first = null;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("size:" + size + ",[");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0)
builder.append(",");
builder.append(node.element);
node = node.next;
}
builder.append("]");
return builder.toString();
}
// 复杂度分析:
// 最好情况复杂度,找第一个元素,O(1)
// 最坏情况复杂度,找最后一个元素,O(n)
// 平均情况复杂度,O(n)
// 根据当前index获取Node元素
private Node<E> node(int index) {
// index合理性检查
checkRangeIndex(index);
// 获取当前Node,必须从头节点开始,一层一层往后面找,要找到index处的node节点,必须找到它的前一个节点
// 因此从first节点开始,一直node.next.next...直到找到index-1位置的节点,这里就涉及到循环
// 那么循环多少次?index = 0,则node=first,循环0次;index = 1,node = first.next,循环1次;index = 2,node = first.next.next,循环2次,
// 依次类推,index = n , 则循环n次,下标从0开始
// 找index-1次即可找到当前节点node
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
}
4.LinkedList虚拟头结点
package com.huawei.list;
/**
* 功能描述:引入虚拟头节点
*
* @author
* @since 2022-11-03
*/
public class LinkedList2<E> extends AbstractList<E> implements List<E> {
/**
* 链表设计思路:
* 1.链表是层层递进,没有初始容量
* 2。链表需要有一个节点对象(Node)来记录当前元素的值,以及下一个Node
* 3.链表需要有一个头节点
*/
// 定义静态内部类
private static class Node<E> {
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
private Node<E> first;
public LinkedList2() {
// 添加虚拟头结点
this.first = new Node<>(null,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 oldElement = node.element;
node.element = element;
return oldElement;
}
@Override
public void add(int index, E element) {
checkRangeIndexForAdd(index);
// 找到index位置的上一个节点
Node<E> prev = index == 0 ? first : node(index - 1);
prev.next = new Node<>(element, prev.next);
size ++;
}
@Override
public E remove(int index) {
checkRangeIndex(index);
// 思路:
// 1)找到当前节点
// 2)当前节点的前一个的节点的next指向当前节点的next
// 如果直接找当前节点,那么当找节点方法又要执行两次
// 因此这里直接找当前要删除节点的前一个节点
Node<E> prev = index == 0 ? first : node(index - 1);
Node<E> removeNode = prev.next;
prev.next = removeNode.next;
size --;
return removeNode.element;
}
@Override
public int indexOf(E element) {
Node<E> node = first.next;
// element空校验
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_NOT_FOUND;
}
@Override
public void clear() {
size = 0;
first = null;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("size:" + size + ",[");
Node<E> node = first.next;
for (int i = 0; i < size; i++) {
if (i != 0)
builder.append(",");
builder.append(node.element);
node = node.next;
}
builder.append("]");
return builder.toString();
}
// 根据当前index获取Node元素
private Node<E> node(int index) {
// index合理性检查
checkRangeIndex(index);
Node<E> node = first.next;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
}
5.ArrayList实现
package com.huawei.list;
/**
* 功能描述
*
* @author
* @since 2022-11-03
*/
public class ArrayList<E> extends AbstractList<E> implements List<E> {
/**
* 设计思路:
* 1、动态数组首先得有数组
* 2、了解数组的特性,是一块连续的内存空间,当在内存中开辟空间以后,那一块空间本身就不能变动了
* 3、数组的容量并不代表当前数据量
* 4、注意对象类型数据的内存管理与比较方式
*/
/**
* 完成功能:
* 1)动态数组大小 size
* 2)数组是否为空 isEmpty
* 3)是否包含某个元素
* 4)在末尾添加元素
* 5)获取某个索引的元素
* 6)设置某个索引的元素
* 7)在某个索引位添加元素
* 8)根据元素本身移除元素
* 9)根据元素索引移除元素
* 10)找到某个元素的索引位置
* 11)清空动态数组
* 这些方法中,最重要的方法就是:基础添加与基础删除方法
* 需要考虑问题:
* 1)添加扩容问题,扩容1.5倍
* 2)toString,采用StringBuilder拼接
* 3)对象clear时候,注意内存管理,对象引用置为空,但是数组要保留
*/
private E[] elements;
private static final int DEFAULT_CAPACITY = 10;
public ArrayList(int capacity) {
capacity = capacity > DEFAULT_CAPACITY ? capacity : DEFAULT_CAPACITY;
elements = (E[]) new Object[capacity];
}
public ArrayList() {
this(DEFAULT_CAPACITY);
}
@Override
public E get(int index) {
// 校验索引合理性
checkRangeIndex(index);
return elements[index]; // O(1)
}
@Override
public E set(int index, E element) {
// 校验索引合理性
checkRangeIndex(index);
E oldElement = elements[index];
elements[index] = element; // O(1)
return oldElement;
}
// O(n) n是数据规模,动态数组的size是数据规模
// 最好情况复杂度,往最后添加元素 O(1)
// 最坏情况复杂度,往0添加元素,O(size)--> O(n),数组的挪动
// 平均情况复杂度,(1+2+3+...+n)/n -->O(n)
@Override
public void add(int index, E element) {
// 校验索引合理性
checkRangeIndexForAdd(index);
// 考虑扩容问题,因为是添加元素,所以容量必须得是size+1才行
ensureCapacity(size + 1);
// 添加思路:
// 在index位置添加元素的本质是:index位置的元素往后移,即index位置的元素放到index+1的位置,然后将当前元素放在index的位置
// 但是为了避免元素覆盖,必须先移最后面的元素,也就是说,必须先把size-1位置的元素移到size上面
// 所以循环应该先从size-1开始到index结束
for (int i = size - 1; i >= index; i--) {
elements[i + 1] = elements[i];
}
// 或者
// for (int i = size; i > index; i--) {
// elements[i] = elements[i - 1];
// }
elements[index] = element;
size++;
}
// O(n) n是数据规模,动态数组的size是数据规模
// 最好情况复杂度,往最后添加元素 O(1)
// 最坏情况复杂度,往0添加元素,O(size)--> O(n)
// 平均情况复杂度, 均摊复杂度--> O(1),均摊等于最好
// 什么时候使用均摊复杂度?经过连续的多次复杂度比较低的情况以后,出现个别复杂度比较高的情况
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
// 如果之前的容量大于等于或者size+1,代表原来容量够用,直接return
if (oldCapacity >= capacity) {
return;
}
// 如果不够用,则扩容,java中官方扩容大小是1.5倍,因此这里也扩容1.5倍,避免频繁扩容,损耗CPU
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 由于数组是一块连续的空间,因此扩容只能new一个新的数组
E[] newElements = (E[]) new Object[newCapacity];
// 数据拷贝
for (int i = 0; i < size; i++) {
newElements[i] = elements[i];
}
elements = newElements;
System.out.println("数组扩容:" + oldCapacity +"-->" + newCapacity);
}
// O(n) n是数据规模,动态数组的size是数据规模
// 最好情况复杂度,删除最后添加元素 O(1)
// 最坏情况复杂度,删除0位置元素,O(size)--> O(n)
// 平均情况复杂度,(1+2+3+...+n)/n -->O(n)
@Override
public E remove(int index) {
// 校验索引合理性
checkRangeIndex(index);
// 删除思路:
// 移除某个index位置的元素,本质就是数组中,index后面的元素往前移,覆盖前面的元素
// 由于是后面的覆盖前面的,那必定是从index后面一个元素开始,逐个覆盖前面的元素
// 因此,实现思路:1.循环覆盖 2.如果要删除index位置的元素,那么应该是index + 1覆盖index,一直到size-1 覆盖size-2
// 由于内存管理的原因,size-1位的元素需要置为null
E oldElement = elements[index];
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
// size -- ;
// elements[size - 1] = null; 代码合并
elements[--size] = null;
// 缩容和扩容原理一样,申请新的内存空间
trimCapacity();
return oldElement;
}
// 缩容
private void trimCapacity() {
int oldCapacity = elements.length;
int newCapacity = oldCapacity >> 1;
if(size >= newCapacity || newCapacity < DEFAULT_CAPACITY) return;
// 由于数组是一块连续的空间,因此缩容只能new一个新的数组
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 int indexOf(E element) {
// 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;
}
@Override
public void clear() {
// 由于直接将size = 0,对象还滞留在内存中,因此这里还需要清理无用的对象
for (int i = 0; i < size; i++) {
elements[i] = null;
}
// 缩容,申请新的内存空间
if(elements.length > DEFAULT_CAPACITY){
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
size = 0;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("size:" + size + ",[");
for (int i = 0; i < size; i++) {
if (i != 0)
builder.append(",");
builder.append(elements[i]);
}
builder.append("]");
return builder.toString();
}
}
6.ArrayList采用双端队列思路优化
package com.wangzi.list;
/**
* 功能描述
*
* @author wWX1050875
* @since 2022-11-03
*/
public class ArrayList2<E> extends AbstractList<E> implements List<E> {
/**
* 设计思路:
* 1、动态数组首先得有数组
* 2、了解数组的特性,是一块连续的内存空间,当在内存中开辟空间以后,那一块空间本身就不能变动了
* 3、数组的容量并不代表当前数据量
* 4、注意对象类型数据的内存管理与比较方式
*/
/**
* 完成功能:
* 1)动态数组大小 size
* 2)数组是否为空 isEmpty
* 3)是否包含某个元素
* 4)在末尾添加元素
* 5)获取某个索引的元素
* 6)设置某个索引的元素
* 7)在某个索引位添加元素
* 8)根据元素本身移除元素
* 9)根据元素索引移除元素
* 10)找到某个元素的索引位置
* 11)清空动态数组
* 这些方法中,最重要的方法就是:基础添加与基础删除方法
* 需要考虑问题:
* 1)添加扩容问题,扩容1.5倍
* 2)toString,采用StringBuilder拼接
* 3)对象clear时候,注意内存管理,对象引用置为空,但是数组要保留
*/
private E[] elements;
private int front; // 头节点索引,在头部添加元素,以及删除头部的元素的时候需要注意
private static final int DEFAULT_CAPACITY = 10;
public ArrayList2(int capacity) {
capacity = capacity > DEFAULT_CAPACITY ? capacity : DEFAULT_CAPACITY;
elements = (E[]) new Object[capacity];
}
public ArrayList2() {
this(DEFAULT_CAPACITY);
}
@Override
public E get(int index) {
// 校验索引合理性
checkRangeIndex(index);
return elements[index(index)]; // O(1)
}
@Override
public E set(int index, E element) {
// 校验索引合理性
checkRangeIndex(index);
E oldElement = elements[index(index)];
elements[index(index)] = element; // O(1)
return oldElement;
}
// O(n) n是数据规模,动态数组的size是数据规模
// 最好情况复杂度,往最后添加元素 O(1)
// 最坏情况复杂度,往0添加元素,O(size)--> O(n),数组的挪动
// 平均情况复杂度,(1+2+3+...+n)/n --> O(n)
@Override
public void add(int index, E element) {
// 校验索引合理性
checkRangeIndexForAdd(index);
// 考虑扩容问题,因为是添加元素,所以容量必须得是size+1才行
ensureCapacity(size + 1);
// 添加思路:
// 添加的时候,根据index的位置,考虑数组往左移或者右移
boolean left = true;
if (index >= size >> 2) {
left = false;
}
// 往左移
if (left) {
for (int i = 0; i < index; i++) {
elements[index(i - 1)] = elements[index(i)];
}
// front向后移动
front = index(-1);
elements[index(index)] = element;
} else {
for (int i = size - 1; i >= index; i++) {
elements[index(i + 1)] = elements[index(i)];
}
elements[index(index)] = element;
}
size++;
}
// O(n) n是数据规模,动态数组的size是数据规模
// 最好情况复杂度,往最后添加元素 O(1)
// 最坏情况复杂度,往0添加元素,O(size)--> O(n)
// 平均情况复杂度, 均摊复杂度--> O(1),均摊等于最好
// 什么时候使用均摊复杂度?经过连续的多次复杂度比较低的情况以后,出现个别复杂度比较高的情况
private void ensureCapacity(int capacity) {
int oldCapacity = elements.length;
// 如果之前的容量大于等于或者size+1,代表原来容量够用,直接return
if (oldCapacity >= capacity) {
return;
}
// 如果不够用,则扩容,java中官方扩容大小是1.5倍,因此这里也扩容1.5倍,避免频繁扩容,损耗CPU
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 由于数组是一块连续的空间,因此扩容只能new一个新的数组
E[] newElements = (E[]) new Object[newCapacity];
// 数据拷贝
for (int i = 0; i < size; i++) {
newElements[i] = elements[index(i)];
}
elements = newElements;
System.out.println("数组扩容:" + oldCapacity + "-->" + newCapacity);
}
// O(n) n是数据规模,动态数组的size是数据规模
// 最好情况复杂度,删除最后添加元素 O(1)
// 最坏情况复杂度,删除0位置元素,O(size)--> O(n)
// 平均情况复杂度,(1+2+3+...+n)/n -->O(n)
@Override
public E remove(int index) {
// 校验索引合理性
checkRangeIndex(index);
// 删除思路:
// 移除某个index位置的元素,本质就是数组中,index后面的元素往前移,覆盖前面的元素
// 由于是后面的覆盖前面的,那必定是从index后面一个元素开始,逐个覆盖前面的元素
// 因此,实现思路:1.循环覆盖 2.如果要删除index位置的元素,那么应该是index + 1覆盖index,一直到size-1 覆盖size-2
// 由于内存管理的原因,size-1位的元素需要置为null
E oldElement = elements[index(index)];
boolean left = true;
if (index >= size >> 2) {
left = false;
}
if (left) { // 如果删左边,则左边部分右移
for (int i = index; i > 0; i--) {
elements[index(i)] = elements[index(i - 1)];
}
elements[index(0)] = null; // 头部置为null
front = index(1); // 头向右移动1位
} else { // 如果删右边,则右边部分左移
for (int i = index; i < size; i++) {
elements[index(i)] = elements[index(i + 1)];
}
elements[index(size - 1)] = null; // 尾部置为null
}
size--;
// 缩容和扩容原理一样,申请新的内存空间
trimCapacity();
return oldElement;
}
// 缩容
private void trimCapacity() {
int oldCapacity = elements.length;
int newCapacity = oldCapacity >> 1;
if (size >= newCapacity || newCapacity < DEFAULT_CAPACITY) return;
// 由于数组是一块连续的空间,因此缩容只能new一个新的数组
E[] newElements = (E[]) new Object[newCapacity];
// 数据拷贝
for (int i = 0; i < size; i++) {
newElements[i] = elements[index(i)];
}
elements = newElements;
System.out.println("数组缩容:" + oldCapacity + "-->" + newCapacity);
}
@Override
public int indexOf(E element) {
// element空校验
if (element == null) {
for (int i = 0; i < size; i++) {
int realIndex = index(i);
if (elements[realIndex] == null)
return realIndex;
}
} else {
for (int i = 0; i < size; i++) {
int realIndex = index(i);
if (element.equals(elements[realIndex]))
return realIndex;
}
}
return ELEMENT_NOT_FOUND;
}
@Override
public void clear() {
// 由于直接将size = 0,对象还滞留在内存中,因此这里还需要清理无用的对象
for (int i = 0; i < size; i++) {
elements[index(i)] = null;
}
// 缩容,申请新的内存空间
if (elements.length > DEFAULT_CAPACITY) {
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
size = 0;
}
private int index(int index) {
index += front;
if (index < 0) {
return index + elements.length;
}
return index >= elements.length ? index - elements.length : index;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("front:" + front);
builder.append(",size:" + size + ",[");
for (int i = 0; i < elements.length; i++) {
if (i != 0)
builder.append(",");
builder.append(elements[i]);
}
builder.append("]");
return builder.toString();
}
}
7.双向链表
package com.huawei.list;
/**
* 功能描述:双向链表
*
* @author wWX1050875
* @since 2022-11-03
*/
public class DoubleLinkedList<E> extends AbstractList<E> implements List<E> {
/**
* 链表设计思路:
* 1.链表是层层递进,没有初始容量
* 2。链表需要有一个节点对象(Node)来记录当前元素的值,以及下一个Node
* 3.链表需要有一个头节点
*/
// 定义静态内部类
private static class Node<E> {
E element;
Node<E> next;
Node<E> pre;
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (pre != null) {
builder.append(pre.element);
} else {
builder.append("null");
}
builder.append("_" + element + "_");
if (next != null) {
builder.append(next.element);
} else {
builder.append("null");
}
return builder.toString();
}
public Node(Node<E> pre, E element, Node<E> next) {
this.pre = pre;
this.element = element;
this.next = next;
}
}
private Node<E> first;
private Node<E> last;
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E oldElement = node.element;
node.element = element;
return oldElement;
}
@Override
public void add(int index, E element) {
checkRangeIndexForAdd(index);
// 先通用情况,再特殊情况
// 1.先考虑中间情况
// 在当前索引添加元素,则先找到index原来位置的元素
// 然后,新节点的next是原来节点,原来节点pre是新节点
// 原来节点上一个节点的next指向新节点
// 2.再考虑在尾部或者首部添加
if (index == size) { // 在尾部添加
// last肯定指向newNode,然后之前的last的next指向newNode
Node<E> oldLast = last;
last = new Node<>(oldLast, element, null);
if (oldLast == null) { // 没有节点的时候,first = last = newNode
first = last;
} else {
oldLast.next = last; // 这里当没有节点的时候,oldLast可能为null
}
} else {
Node<E> origin = node(index);
Node<E> newNode = new Node<>(origin.pre, element, origin);
if (origin.pre == null) { // 在首部添加节点
first = newNode;
} else {
origin.pre.next = newNode;
}
origin.pre = newNode;
}
size++;
}
@Override
public E remove(int index) {
checkRangeIndex(index);
// 删除的时候
// 1.找到当前节点
Node<E> removeNode = node(index);
// 2.removeNode的prev的next指向removeNode.next
// removeNode的next的pre指向removeNode.prev
Node<E> prev = removeNode.pre;
Node<E> next = removeNode.next;
if (prev == null) { // index = 0 的时候,删除头节点
first = next;
} else {
prev.next = next;
}
if (next == null) { // 删除尾节点
last = prev;
} else {
next.pre = prev;
}
size--;
return removeNode.element;
}
@Override
public int indexOf(E element) {
Node<E> node = first;
// element空校验
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_NOT_FOUND;
}
@Override
public void clear() {
size = 0;
first = null;
last = null;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("size:" + size + ",[");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0)
builder.append(",");
builder.append(node);
node = node.next;
}
builder.append("]");
return builder.toString();
}
// 复杂度分析:
// 最好情况复杂度,找第一个元素,O(1)
// 最坏情况复杂度,找最后一个元素,O(n)
// 平均情况复杂度,O(n)
// 根据当前index获取Node元素
private Node<E> node(int index) {
// index合理性检查
checkRangeIndex(index);
// 双向链表从两头开始查找元素,比单向链表节省一半时间
Node<E> node;
if (index > (size >> 2)) { // 从尾部找
node = last;
for (int i = size - 1; i > index; i--) {
node = node.pre;
}
} else { // 从头找
node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
}
return node;
}
}
8.单向循环链表
package com.huawei.list;
/**
* 功能描述:单向循环链表
*
* @author wWX1050875
* @since 2022-11-03
*/
public class CircleLinkedList<E> extends AbstractList<E> implements List<E> {
/**
* 链表设计思路:
* 1.链表是层层递进,没有初始容量
* 2。链表需要有一个节点对象(Node)来记录当前元素的值,以及下一个Node
* 3.链表需要有一个头节点
*/
// 定义静态内部类
private static class Node<E> {
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append( element + "_").append(next.element);
return builder.toString();
}
}
private Node<E> first;
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E oldElement = node.element;
node.element = element;
return oldElement;
}
@Override
public void add(int index, E element) {
checkRangeIndexForAdd(index);
// 在index位置添加元素
// 思路:
// 1.在index位置添加元素,有两种思路
// 1)找到index位置的节点,然后把新节点的next指向index位置的节点
// 2) 再把index节点上一个节点的next指向当前节点,这个又需要调用找节点的方法,因此不采取
// 或者
// 1)找到index-1位置的节点
// 2)新节点的next指向index-1位置的next
// 3) index-1位置的next指向新节点
// 采取第二种方法:
if (index == 0) {
// index = 0,代表在头部添加新节点
// 那么新节点是first,新节点的next是之前first,那么:
if(size == 0){
first = new Node<>(element,null);
first.next = first;
}else{
// 单向循环链表,添加头部节点的时候,需要找到尾部节点,然后尾部节点的头节点指向新添加的节点
// 先找尾部节点
Node<E> last = node(size - 1);
// 新节点成为头节点
first = new Node<>(element, first);
// 尾节点的next变成新的first
last.next = first;
}
} else {
// 找到index位置的上一个节点
Node<E> prev = node(index - 1);
// 新建节点
// Node<E> newNode = new Node<>(element, prev.next);
// index - 1 位置的next指向新节点
// prev.next = newNode;
prev.next = new Node<>(element, prev.next);
}
size ++;
}
@Override
public E remove(int index) {
checkRangeIndex(index);
Node<E> removeNode = first;
if (index == 0) {
if(size == 1){
first = null;
}else{
// 找到最后一个节点,最后一个节点的next指向要指向新的first,
// 当size = 1的时候,下列方法实际上没有把对象删掉,对象还保留在内存中,浪费了内存空间,但是把size减成了0,不影响使用
// 上面对于size = 1这种情况还是特殊处理一下
Node<E> last = node(size - 1);
first = removeNode.next;
last.next = first;
}
} else {
Node<E> node = node(index - 1);
removeNode = node.next;
node.next = removeNode.next;
}
size -- ;
return removeNode.element;
}
@Override
public int indexOf(E element) {
Node<E> node = first;
// element空校验
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_NOT_FOUND;
}
@Override
public void clear() {
size = 0;
first = null;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("size:" + size + ",[");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0)
builder.append(",");
builder.append(node);
node = node.next;
}
builder.append("]");
return builder.toString();
}
private Node<E> node(int index) {
// index合理性检查
checkRangeIndex(index);
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
}
9.双向循环列表
package com.huawei.list;
/**
* 功能描述:双向循环链表
* 注:不论是单向循环链表还是双向循环链表,在删除单一元素的时候,需要特殊处理
* 如果不处理,功能正确,但是会残留对象在内存
*
* @author wWX1050875
* @since 2022-11-03
*/
public class CircleDoubleLinkedList<E> extends AbstractList<E> implements List<E> {
/**
* 链表设计思路:
* 1.链表是层层递进,没有初始容量
* 2。链表需要有一个节点对象(Node)来记录当前元素的值,以及下一个Node
* 3.链表需要有一个头节点
*/
// 定义静态内部类
private static class Node<E> {
E element;
Node<E> next;
Node<E> pre;
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(pre.element);
builder.append("_" + element + "_");
builder.append(next.element);
return builder.toString();
}
public Node(Node<E> pre, E element, Node<E> next) {
this.pre = pre;
this.element = element;
this.next = next;
}
}
private Node<E> first;
private Node<E> last;
// 双向循环链表:约瑟夫环
private Node<E> current; // 环中链表的当前位置
// 重置当前节点
public void reset() {
current = first;
}
// next:当前节点移动到下一个节点
public void next() {
if (current == null)
return;
current = current.next;
}
// current 获取当前节点元素,删除当前节点,并且指针向后移动
public E current() {
if (current == null)
return null;
Node<E> temp = current;
remove(current);
if (size != 1) { // 避免size为1的时候,自己指向自己
current = temp.next;
}
return temp.element;
}
@Override
public E get(int index) {
return node(index).element;
}
@Override
public E set(int index, E element) {
Node<E> node = node(index);
E oldElement = node.element;
node.element = element;
return oldElement;
}
@Override
public void add(int index, E element) {
checkRangeIndexForAdd(index);
// 双向循环列表添加,在中间添加不影响,考虑尾部和头部
if (index == size) { // 在尾部添加
// 双向链表在尾部添加
// 1.新节点取代之前的尾部节点成为last,之前的last的next指向newLast
// 2.新节点next指向oldLast的next,新节点pre指向oldLast
Node<E> oldLast = last;
// 3.之前的first的prev指向新的next
if (oldLast == null) { // 此时数组还没有元素
first = last = new Node<>(null, element, null);
first.pre = first;
first.next = first;
} else {
last = new Node<>(oldLast, element, oldLast.next);
oldLast.next = last;
first.pre = last;
}
} else {
Node<E> origin = node(index);
Node<E> newNode = new Node<>(origin.pre, element, origin);
origin.pre.next = newNode;
origin.pre = newNode;
if (index == 0) { // 在首部添加的时候,要改变first的指向
first = newNode;
}
}
size++;
}
@Override
public E remove(int index) {
checkRangeIndex(index);
Node<E> removeNode = node(index);
remove(removeNode);
return removeNode.element;
}
private void remove(Node<E> removeNode) {
// 删除的时候
if (size == 1) {
first = last = null;
} else {
// 1.找到当前节点
// 2.removeNode的prev的next指向removeNode.next
// removeNode的next的pre指向removeNode.prev
Node<E> prev = removeNode.pre;
Node<E> next = removeNode.next;
prev.next = next;
next.pre = prev;
// 删除头节点的时,first指向变化
if (removeNode == first) {
first = next;
}
if (removeNode == last) { // 删除尾部节点的时候,last指向pre
last = prev;
}
}
size--;
}
@Override
public int indexOf(E element) {
Node<E> node = first;
// element空校验
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_NOT_FOUND;
}
@Override
public void clear() {
size = 0;
first = null;
last = null;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("size:" + size + ",[");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0)
builder.append(",");
builder.append(node);
node = node.next;
}
builder.append("]");
return builder.toString();
}
// 复杂度分析:
// 最好情况复杂度,找第一个元素,O(1)
// 最坏情况复杂度,找最后一个元素,O(n)
// 平均情况复杂度,O(n)
// 根据当前index获取Node元素
private Node<E> node(int index) {
// index合理性检查
checkRangeIndex(index);
// 双向链表从两头开始查找元素,比单向链表节省一半时间
Node<E> node;
if (index > (size >> 2)) { // 从尾部找
node = last;
for (int i = size - 1; i > index; i--) {
node = node.pre;
}
} else { // 从头找
node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
}
return node;
}
}