链表是由一个个节点组成的。每个节点的内存空间在堆上申请的。
共八种:主讲不带头 单向 不循环(算法常用),辅讲 不带头 双向 不循环(链表底层采取的这种)。
一个节点有两个部分,数据域 和 指针域。
区分一下头节点 和 首节点的概念?本篇代码中的head是首节点。
① 头插和尾插只用考虑正常和null的情况(先写正常情况下的代码,再判断其是否包含null的情况,如果不包含就需要额外讨论)
② 任意位置插入:判断index是否合法(链表为空会发生什么) + 正常 + 第一个节点位置 + size位置
③ contains,直接遍历即可,while的条件cur != null已经考虑了head为null的情况
④ remove,删除第一次key出现的节点,正常 + key为第一个节点 (有它的原因是,while的循环条件是cur.next != null,如果key就是第一个节点,按正常的逻辑根本删除它) + null
⑤ removeAllKey,遍历一次就能删除所有key,正常 + key为第一个节点 + null
⑥ size,直接遍历即可
⑦ clear,直接head=null
⑧ display,直接遍历即可
注意:直接遍历时不用判空是因为循环条件cur != nul已经判断了。
正常 和 null情况是必须要判断了,其他情况都是由于正常情况的代码有缺陷的补充。
涉及到某某位置的问题一定要判断该位置是否合法。
给链表新增元素时,是不必考虑是否扩容问题的。
将Node类型写成泛型时,注意在比较两个节点中的value值时,要用equals方法。
一、IList
/**
* Created with IntelliJ IDEA.
* Description:
* User: tangyuxiu
* Date: 2024-08-08
* Time: 6:53
*/
public interface IList<T> {
void addFirst(T data);
void addLast(T data);
//第一个数据节点为0号下标
void addIndex(int index,T data);
boolean contains(T key);
void remove(T key);
//一次遍历移除所有key值
void removeAllKey(T key);
int size();
void clear();
void display();
}
二、IndexIsLegalException
/**
* Created with IntelliJ IDEA.
* Description:
* User: tangyuxiu
* Date: 2024-08-08
* Time: 7:22
*/
public class IndexIsLegalException extends Exception {
public IndexIsLegalException() {
super();
}
public IndexIsLegalException(String s) {
super(s);
}
}
三、MyLinkedList
/**
* Created with IntelliJ IDEA.
* Description:
* User: tangyuxiu
* Date: 2024-08-08
* Time: 6:52
*/
public class MyLinkedList<T> implements IList<T> {
static class ListNode<T> {
T value;
ListNode<T> next;//默认值为null
public ListNode(T value) {
this.value = value;
}
}
private ListNode<T> head;//首节点
@Override
public void addFirst(T data) {
//正常,包含了head==null
ListNode<T> newNode = new ListNode<>(data);
newNode.next = this.head;
this.head = newNode;
}
@Override
public void addLast(T data) {
ListNode<T> newNode = new ListNode<>(data);
//head==null
if (this.head == null) {
newNode.next = this.head;
this.head = newNode;
return;
}
//正常,未包含head==null
//找到最后一个节点
ListNode<T> cur = this.head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = newNode;
}
private void indextIsLegal(int index) throws IndexIsLegalException {
//依据一下逻辑,当head==null,index==0时是合法的
//但当head==null,index>0时是不合法的
if (index < 0 && index > this.size()) {
throw new IndexIsLegalException("下标不合法!!!");
}
}
//第一个数据节点为0号下标
@Override
public void addIndex(int index, T data) {
//判断index下标是否合法
try {
this.indextIsLegal(index);
} catch (IndexIsLegalException e) {
e.printStackTrace();
}
ListNode<T> newNode = new ListNode<>(data);
//index=0
if (index == 0) {
//不管head是否为空,调用addFirst方法都满足前插
this.addFirst(data);
return;
}
//尾插,下列代码已含括
//正常:下列代码当index=0或1时,均只会在1位置前插入,因此当index=0时要额外讨论
//找到index的前一个节点
ListNode<T> pre = this.head;
for (int i = 0; i < index - 1 ; i++) {
pre = pre.next;
}
ListNode<T> find = pre.next;
newNode.next = find;
pre.next = newNode;
}
@Override
public boolean contains(T key) {
ListNode<T> cur = this.head;
while (cur != null) {
if (cur.value.equals(key)) {
return true;
}
cur = cur.next;
}
return false;
}
@Override
public void remove(T key) {
//head==null
if (this.head == null) {
return;
}
//key为第一个节点
if (this.head.value == key) {
this.head = this.head.next;
return;
}
//正常
//找到key的前一个节点
ListNode<T> pre = this.head;
ListNode<T> find = pre.next;
while (pre.next != null) {
if (find.value.equals(key)) {
pre.next = find.next;
return;
}
pre = find;
find = find.next;
}
}
//一次遍历移除所有key值
@Override
public void removeAllKey(T key) {
//head==null
if (this.head == null) {
return;
}
//正常,下列代码无法检查第一个节点的value值所以要额外讨论第一个节点
//找到找到key的前一个节点
ListNode<T> pre = this.head;
ListNode<T> find = pre.next;
while (pre.next != null) {
if (find.value.equals(key)) {
pre.next = find.next;
//find = find.next;
} else {
pre = find;
//find = find.next;
}
find = find.next;
}
//key为第一个节点
if (this.head.value == key) {
//为什么下行代码不写成pre = pre.next呢?
//答:“正常”的代码中pre,find只是工具,真正的head一直仍然指向的是同一个节点
//附图,间文章中
this.head = this.head.next;
}
}
@Override
public int size() {
int count = 0;//size计数器
ListNode<T> cur = this.head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
@Override
public void clear() {
this.head = null;
}
@Override
public void display() {
ListNode<T> cur = this.head;
while (cur != null) {
System.out.print(cur.value + " ");
cur = cur.next;
}
System.out.println();
}
}
四、Test
/**
* Created with IntelliJ IDEA.
* Description:
* User: tangyuxiu
* Date: 2024-08-08
* Time: 6:53
*/
public class Test {
public static void main(String[] args) {
// MyLinkedList<String> myLinkedList = new MyLinkedList<>();
//测试头插
// myLinkedList.addFirst("kk");
// myLinkedList.addFirst("qq");
// myLinkedList.addFirst("ww");
// myLinkedList.display();//ww qq kk
//测试尾插
// myLinkedList.addLast("kk");//head==null
// myLinkedList.addLast("ee");
// myLinkedList.addLast("ff");
// myLinkedList.display();//kk ee ff
//测试任意位置插入
// myLinkedList.addIndex(0,"kk");//head==null
// myLinkedList.addIndex(1,"qq");
// myLinkedList.addIndex(1,"ss");
// myLinkedList.display();//kk ss qq
//测试contains
// System.out.println(myLinkedList.contains("qq"));//true
// System.out.println(myLinkedList.contains("xx"));//false
//测试删除第一次出现的key
// myLinkedList.addLast("kk");
// myLinkedList.addLast("qq");
// myLinkedList.addLast("ii");
// myLinkedList.addLast("kk");
// myLinkedList.addLast("kk");
//
// myLinkedList.remove("kk");
// //myLinkedList.display();//qq ii kk kk
// myLinkedList.display();//qq ii kk
//测试删除所有的key
// myLinkedList.removeAllKey("kk");
// myLinkedList.display();//qq ii
//测试size
// System.out.println(myLinkedList.size());//5
//测试clear
// myLinkedList.clear();
// System.out.println(myLinkedList.size());//0
}
}
本篇已完结 ......