当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。
链表是一种物理存储结构上非连续存储结构,逻辑上是连续的,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
链表的结构多样,下面情况组合有8种链表结构:
单链表是一种常见的线性数据结构,用于储存一系列具有相同类型的元素。它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。单链表可以通过指针的方式实现元素的增删改查等方法。
用面向对象来把单链表封装成一个类,然后写一个静态内部类,类中的成员变量val,指向下一个节点的指针next,获取val的构造方法,最后再定义一个头节点。
public class MySingleList implements IList{
static class ListNode {
public int val;
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
//链表的属性 链表的头节点
public ListNode head;//null
}
遍历链表的时候通常需要定义一个临时cur来从头节点遍历,目的是防止head头节点丢失。循环条件cur != null,每次打印一个元素,cur都需要移动到下一个节点,即:cur = cur.next。
@Override
public void display() {
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val+" ");
cur = cur.next; //让cur这个节点 可以从一个节点走到下一个节点
}
System.out.println();
}
遍历链表,判断cur.val == key。
@Override
public boolean contains(int key) {
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
@Override
public int size() {
ListNode cur = head;
int count = 0;
while (cur != null) { // 代表整个链表走完了
count++;
cur = cur.next;
}
return count;
}
在头节点插入新的节点,让该节点成为新的头节点。如果head头节点为空,直接将插入的node节点设为头节点即head = node,如果不为空,把插入节点指向下一个节点的next设为head,把node插入head之前,再把head重新设为node,即node.next = head,
@Override
public void addFirst(int data) {
ListNode node = new ListNode(data);
node.next = head;
head = node;
}
如果链表为空,直接插入,不为空cur遍历链表,循环条件是cur.next != null,即cur.next == null的时候,cur指向的是最后一个节点,然后cur.next = node。注意:cur == null时链表已经走完。
@Override
public void addLast(int data) {
ListNode node = new ListNode(data);
if (head == null) {
head = node;
}else {
ListNode cur = head;
while (cur.next != null) { // cur.next == null的时候,cur指向的是最后一个节点
cur = cur.next;
}
cur.next = node;
}
}
定义一个计数器count,循环条件count != index-1,让cur一直走到index
private ListNode searchPreIndex(int index) {
ListNode cur = head;
int count = 0;
while (count != index-1) {
cur = cur.next;
count++;
}
return cur;
}
判断索引位置是否合法,如果链表为空,直接插入,如果index==0,头插法,然后是在最后一个位置插入即index == size(),尾插法,中间插入的话先调用searchPreIndex方法获取到该索引的节点,然后先把cur后面的数据接到node.next防止数据丢失,即node.next = cur.next,再把node连接上cur.next即:cur.next = node。
@Override
public void addIndex(int index, int data) throws IndexException{
if (index < 0 || index > size()) {
throw new IndexException("index不合法"+index);
}
ListNode node = new ListNode(data);
if (head == null) {
head = node;
return;
}
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
// 中间插入
ListNode cur = searchPreIndex(index);
node.next = cur.next;
cur.next = node;
}
定义一个指针 走到要删除的节点的前一个节点, 如果cur.next == key cur就是要删除节点的前驱,如果head为空返回空,如果删除的是头节点,直接将头节点的下一个节点设头节点,即head = head.next。写一个findPreKey方法来找到删除元素的节点,如果cur为空,即没有要删除的数字。把要删除的数据的下一个节点连上删除节点的前驱next。即:cur.next = del.next。
@Override
public void remove(int key) {
if (head == null) {
return;
}
if (head.val == key) {
head = head.next;
return;
}
ListNode cur = findPreKey(key);
if(cur == null){
return; //没有你要删除的数字
}
ListNode del = cur.next;
cur.next = del.next;
}
private ListNode findPreKey(int key) {
ListNode cur = head;
while (cur.next != null) {
if (cur.next.val == key) {
return cur;
}else {
cur = cur.next;
}
}
return null;
}
如果head为空返回空,定义一个prev,和一个cur为prev的下一个节点,循环条件为cur != null走完链表,如果cur.val == key,进行删除操作,prev.next = cur.next;cur = cur.next;即如下图:
如果不是要删除的数据,prev和cur往下一走,即prev = cur; cur = cur.next;
以上把除了头节点都删完了,如果头节点的值是要删的,直接head = head.next。
@Override
public void removeAllKey(int key) {
if(head == null) {
return;
}
ListNode prev = head;
ListNode cur = prev.next;
while (cur != null) {
if (cur.val == key) {
prev.next = cur.next;
cur = cur.next;
}else {
prev = cur;
cur = cur.next;
}
} //除了头节点都删完了
if (head.val == key) { // head头节点为想要删的值
head = head.next;
}
}
// 当一个对象 没有人引用的时候 就会被回收掉
@Override
public void clear() {
head = null;
}