public class OutClass {
class InnerClass{
}
}
// OutClass是外部类
// InnerClass是内部类
先来创建一个类:
java复制代码
public class MySingleList {
static class ListNode {
//数据
public int val;
//节点的引用
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
//存储链表的头节点的引用
public ListNode head;
//public int usedSize;
}
public class MySingleList implements ILinkedList{
//定义静态内部类 来表示节点对象
static class ListNode {
//数据
public int val;
//节点的引用
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
//存储链表的头节点的引用
public ListNode head;
//public int usedSize;
}
同时需要重写其中相关的方法(接口的相关内容)
然后开始实现以下方法:以下方法均在 MySingleList 类当中
2.1 我们先来手动实现--创建一个链表:createList
下图为没有链接时候的链表:
那么我们来画一个链接好的链表图:
下面为实际代码:
java复制代码
public void createList() {
ListNode node1 = new ListNode(val: 12);
ListNode node2 = new ListNode(val: 23);
ListNode node3 = new ListNode(val: 34);
ListNode node4 = new ListNode(val: 45);
ListNode node5 = new ListNode(val: 56);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
head = node1;
}
需要注意:局部变量在栈上面,所以用完就会被系统回收!!
2.2 那最重要的链表遍历呢???--display
没错,关键就是让这个 head 引用来不断遍历节点!
也就是写成循环:head = head.next;
但是循环条件改写成啥样呢???
我们来分析一下:
正确写法为:
那么第一个简单方法出现:
display方法:
java复制代码
@Override
public void display() {
while (head != null) {
System.out.print(head.val + " ");
head = head.next;
}
System.out.println();
}
我们来测试一下:
新建一个 Test 类来测试:
java复制代码
public class Test {
public static void main(String[] args) {
MySingleList mySingleList = new MySingleList();
mySingleList.createList();
mySingleList.display();
}
}
输出结果为:
所以我们的代码是正确的!!!
那为什么:不选择循环条件为 head.next != null 呢?
通过画图我们明白,当使用上述条件时,会少打印一个数据!
可如下图分析:
总结一下:!!!!!
head != null----> head == null 结束循环
把整个链表 一个不拉的 遍历完成了
head.next != null----> head.next == null 结束循环
此时 head 指向的是 最后一个节点
head 往后执行的语句
head = head.next;
但是,代码似乎有一些 不太妥的地方:
也就是 当 head 便利完成之后,head引用将 无法再次找到头节点了!!
要想解决也很简单,我再请一个 引用保镖 来帮我指代 节点 不就行了吗。。。
来看代码:
java复制代码
@Override
public void display() {
ListNode cur = this.head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
也就是 大佬head 不用亲自做事,请个名字叫 cur 的保镖不就行了。
OK,当了解这些基础知识后,再来看其他的操作 就会变得无比简单了,哈哈哈。
2.3 先来写一个 size 的方法
java复制代码
@Override
public int size() {
int count = 0;
ListNode cur = this.head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
也就是在 遍历链表 的基础上,多加了一个 count 的变量,哈哈哈,开胃小菜了。
2.4 如何判断链表中 是否有某个元素呢--contains方法
java复制代码
@Override
public boolean contains(int key) {
ListNode cur = this.head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
是不是也是遍历为主!!!没错,那我们把上面三个方法 放到一起看看:
可以看到,这三个方法 都是依赖于 遍历来完成的,很简单对吧。
接下来讲一些别的操作:
2.5 先来讲 头插法--addFirst
什么是头插法呢,比如有个数据:val = 99;
那要插入,先把数据变成一个节点吧!
那种如何变呢?
在上面的 节点静态类ListNode 当中 ,我定义了一个构造方法:
java复制代码
public ListNode(int val) {
this.val = val;
}
头插法 也就是让 新节点变成第一个节点,也就是 让head引用 指向这个节点!
来画图理解:
如图,左侧为新节点,那就先让它的 next域 值为原头节点的 引用地址!
再让head 指向新的头节点,也就是:
java复制代码
node.next = head;
head = node;
并且注意⚠️:代码顺序别给我调换哈!!!也就是:
先把新节点--绑定后面,再去移动 head...
写完代码后:图就变成了这样:
这样就成功实现了--头插法-插入元素。
有思路后,下面我们来具体实现代码:
java复制代码
@Override
public void addFirst(int data) {
ListNode node = new ListNode(data);
node.next = head;
head = node;
}
那有了这个,我们也可不通过createList方法,而是通过 头插法来 直接创建链表,
如下:
java复制代码
public class Test {
public static void main(String[] args) {
MySingleList mySingleList = new MySingleList();
//mySingleList.createList();
mySingleList.addFirst(12);
mySingleList.addFirst(23);
mySingleList.addFirst(34);
mySingleList.addFirst(45);
mySingleList.addFirst(56);
mySingleList.addFirst(67);
mySingleList.display();
}
}
那我想请问大家:会打印什么??
看结果:
对了没有呢?因为头插法是--每次新插入的 节点 都会放到第一个哈😄
2.6 理解了头插,那么再来看尾插法!--addLast
首先明白:插入元素,首先都是先有 一个节点 ,再会想着插入到哪里!
那尾插法,首先需要找到 链表的尾巴!
那怎么找呢?也就是让 节点保镖cur 满足循环条件,
但这个条件核心为:cur.next != null;
这样就可以找到最后的一个节点!
如上图,cur引用 要想找到最后一个节点,写出如下代码即可:
java复制代码
@Override
public void addLast(int data) {
ListNode node = new ListNode(data);
//1.找到链表的尾巴
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
//cur指向的节点 就是尾巴节点
}
接下来画图理解之后的 操作:
直接用 cur.next = node; 这样就可以让尾巴cur 成功接上一个新的节点。
代码如下:
java复制代码
@Override
public void addLast(int data) {
ListNode node = new ListNode(data);
//1.找到链表的尾巴
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
//cur指向的节点 就是尾巴节点
cur.next = node;
}
是不是就觉得上面代码完美了???
我们来测试一下:
java复制代码
public class Test {
public static void main(String[] args) {
MySingleList mySingleList = new MySingleList();
//mySingleList.createList();
mySingleList.addLast(12);
mySingleList.addLast(23);
mySingleList.addLast(34);
mySingleList.addLast(45);
mySingleList.addLast(56);
mySingleList.addLast(67);
mySingleList.display();
}
}
居然报了一个 空指针异常!!
这是为啥??
假如如上图所示,我是从无到有创建一个 链表,那么cur = head,也就是cur = head = null;
那我问你,cur.next 不就变成了 null.next, 我问你,空有next吗???
所以肯定 会报一个 空指针异常。
如何解决???
加一个判断就好了:代码:
java复制代码
@Override
public void addLast(int data) {
ListNode node = new ListNode(data);
//0.如果链表当中一个元素都没有 此时 插入的节点 就是第一个节点
if(head == null) {
head = node;
return;
}
//1.找到链表的尾巴
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
//cur指向的节点 就是尾巴节点
cur.next = node;
}
加入判断语句后:可画图理解如下:
再来测试一下:
java复制代码
public class Test {
public static void main(String[] args) {
MySingleList mySingleList = new MySingleList();
//mySingleList.createList();
mySingleList.addLast(12);
mySingleList.addLast(23);
mySingleList.addLast(34);
mySingleList.addLast(45);
mySingleList.addLast(56);
mySingleList.addLast(67);
mySingleList.addFirst(199);
mySingleList.display();
}
}
测试成功了!
此外,我们发现:
这些插入 都只是 修改指向,而并没有 移动元素,所以我才说
插入元素
用链表 会比 用顺序表 更好用。
2.7 那来看 addIndex 在任意位置 插入元素!
java复制代码
@Override
public void addIndex(int index, int data) {
}
链表不是数组,但是 我们可以先 给链表 标注一个 位置号码--index,方便理解
假如 node 要插入到 2号位置(index = 2),画图理解:
先让 cur节点 到1号位置(cur-->index - 1)
来写代码吧:
任何一个方法写的时候,一定先看参数:
index的合法性 我们是不是 要检查一下?
先来一个 自定义异常:
java复制代码
public class CheckPosException extends RuntimeException{
public CheckPosException() {
}
public CheckPosException(String message) {
super(message);
}
}
里面的两个构造方法:Alt + Insert + Fn
再在MySingleList类中:
java复制代码
public void addIndex(int index, int data) {
checkPos(index);
}
private void checkPos(int index) {
if(index < 0 || index > size()) {
throw new CheckPosException("index位置不合法:"+index);
}
}
先把 头插法 和 尾插法 两种方法的特殊情况给他考虑了:
java复制代码
@Override
public void addIndex(int index, int data) {
checkPos(index);
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
}
那完善 插入到中间,所以要先找到 index-1 的节点,对不对?
写一个方法:
java复制代码
private ListNode findIndex(int index) {
ListNode cur = head;
int count = 0;//count代表 我的cur 所走过的步数
//当 count = index-1 时,代表cur已经 走到了这个index-1 的位置
while (count != index-1) {
cur = cur.next;
count++;
}
return cur;
}
最后完善终极版本的 addIndex方法,如下:
java复制代码
public void addIndex(int index, int data) {
checkPos(index);
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
//插入 到 中间位置
ListNode cur = findIndex(index);
ListNode node = new ListNode(data);
node.next = cur.next;
cur.next = node;
}