单链表的实现 —— Java
每博一文案
董卿曾说过,你若好到毫无保留,对方就敢坏到。
肆无忌惮要有一颗善良的心,这样才能够让自己心安。
单凡事也都有个度,一旦太过善良,就会变得廉价。
付出只会被当作理所当然帮助,只会换来得寸进尺。
最后不是自己心汗,就是对方翻脸人要想发出光芒,必须先长出锋芒,
如果太过善良,就会丢失自己的价值和尊严,成为别人攻击你的利器
,更变成了伤害你的软肋。
三毛说过,不要害怕拒绝别人,因为当一个人开口提出要求的时候
他的心里根本预备好了两种答案。
所以给任意一个答案都是意料中的。
有时候你把别人看得太重,结果你在别人眼中什么都不是。
———————————— 一禅心灵庙语
文章目录
- 有关链表的理论知识这里就不多说明了,大家可以参考这里 🔜🔜🔜 数据结构 —— 单链表的实现
- 还有这个 🔜🔜🔜 双向循环带头节点链表 —— C语言_
单链表的实现:
-
单链表的结构示图:
-
我们现在所实现的 单链表 是没有,头节点的,其结构比较简单,但是实现其功能的话,会比较复杂一些
单链表的类的创建
- 注意 Java的类的一些事项:
- 静态方法不可以直接访问非静态的方法和非静态属性,需要实例化对象,通过对象的引用方可,访问
- 非静态方法可以直接静态方法和非静态的方法,非静态的属性以及静态的属性
- 首先我们需要创建一个有关 单链表结构的类出来 ,
- 这里我们通过该 类的构造方法 ,对创建节点的初始化,以及赋值的操作
// 创建该链表的类,同C语言中的结构体类似
class Node {
public int data; // 节点的数值
public Node next; // 对象的引用,就是地址,深度拷贝
public Node(int data) { // 该类的构造方法:通过构造方法,对节点的生成,以及初始化
this.data = data;
this.next = null;
}
}
头插法
- 两种情况 :
- 1 种:只有 首节点 ,直接将插入的节点,改写成 首节点 就可以了
- 2 种:存在多个节点
- 其 头插法 的操作示图如下:
// 头插法
public void addFirst(int data) {
Node node = new Node(data);
if (this.pHead == null) {
// 1. 只有一个首节点的存在
this.pHead = node;
return; // 插入完毕,停止后面的继续执行
}
// 2.多个节点的存在
node.next = this.pHead;
this.pHead = node;
}
打印单链表
- 通过循环遍历,该单链表,注意 :跳出循环的条件是:null
- 图示:
// 打印单链表
public void disPlay() {
Node cur = this.pHead; // 首节点的拷贝,代替首节点的移动
while(null != cur) {
System.out.print(" "+cur.data);
cur = cur.next;
}
System.out.println();
}
尾插法
- 首先判断该 单链表 是否为 空 ,如果为空,自定义异常
- 循环 找到 尾节点 的地址,拼接
// 尾插法
/*两种情况
1. 该单链表为 “空”链表
2.该单链表不为空,找尾节点
* */
public void addList(int data) {
Node cur = this.pHead; // 首节点的拷贝
if(this.pHead == null) {
// 该单链表为空
throw new RuntimeException("链表为空");
}
while(null != cur.next) {
cur = cur.next; // 移动节点
}
Node node = new Node(data); // 调用构造方法,生成节点
cur.next = node;
}
头删法
- 同样空单链表就不要 删除 了
- 注意 对首节点进行 拷贝,复制 防止,因为 插入 的 先后顺序 ,把整个链表丢失,掉了
- 头删法 的操作示图:
// 头删法
public void deleteFirst() {
Node cur = this.pHead.next; // 首节点的下一个节点的,拷贝,复制
if(null == this.pHead) {
System.out.println("空单链表不用删除");
return;
}
this.pHead.next =cur.next;
this.pHead = cur;
}
尾删法
- 存在着 三 中情况
- 1 链表为 “空”
- 只有 首节点 这一个节点的存在,我们不可以把,首节点给删除了
- 多个节点存在
- 尾删法 的操作示图:
// 尾删法
/*
三种情况
1.空链表
2.只有一个首节点的存在
3.多个节点存在
* */
public void deleteList() {
Node node = this.pHead; // 首节点的复制,拷贝
Node tmp = null;
// 1.空链表
if(null == this.pHead) {
throw new RuntimeException("空链表,无法操作");
}
// 2.只有一个首节点的存在
if(null == this.pHead.next) {
this.pHead = null;
return ;
}
// 3.多个节点存在
while(node.next != null) {
tmp = node;
node = node.next; // 移动节点
}
tmp.next = null;
}
查找是否包含关键字 key 是否在单链表当中
- 循环判断是否存在该数值的节点
- 空链表就不用看了
// 查找是否包含关键字 key 是否在单链表当中
public boolean contains(int key) {
Node node = this.pHead; // 首节点的复制拷贝
if(null == this.pHead) {
throw new RuntimeException("该链表为空");
}
while(node != null) {
if(key == node.data) {
return true; // 找到了,
}
node = node.next; // 移动节点
}
return false; // 该数值不存在
}
计算该单链表的长度
- 循环遍历计数
- 空链表就不用看了
// 该单链表的长度
public int size() {
Node node = this.pHead; // 首节点复制,拷贝
int count = 0;
if(this.pHead == null) {
System.out.println("空链表");
return 0;
}
while(node != null) {
count++;
node = node.next; // 移动节点
}
return count;
}
查找数值为 x 的的地址
- 循环遍历
- 空链表就不用看了
// 查找数值为 x 的的地址
public Node searchIndex(int x) {
Node cur = this.pHead;
if (this.pHead == null) {
throw new RuntimeException("该链表为空");
}
while (null != cur) {
if (x == cur.data) {
return cur;
}
cur = cur.next; // 移动节点
}
return null;
}
在数值为 x 的后面插入数据
- 三种情况
- 1.该数值在 首节点 中,复用 尾插法,因为插入的位置是在 该数值 的 后面
- 2.该数值在 尾节点 中,复用 尾插法
- 3.该数值在中间
- 其操作示图:
// 在数值为 x 的后面插入数据
/* * 三种情况
1.在开头,可以复用 尾插法
2.在尾部,可以复用 尾插法
3.在中间,*/
public void addIndex(int index,int data) {
// 不存在
if(null == searchIndex(index)){ // 该数值的节点的地址
System.out.println("该数值的节点不存在,返回不执行");
return;
}
// 1. 在开头
if(this.pHead == searchIndex(index)) { // 该数值的节点的地址
addList(data);
return; // 执行完毕,
}
// 2.在尾部
if((searchIndex(index).next == null)) { // 该数值的节点的地址
addList(data);
return; // 执行完毕
}
Node cur = searchIndex(index); // 该数值节点地址的,拷贝
Node tmpNext = cur.next; // 该数值节点的后面节点的地址
Node node = new Node(data); // 生成节点
cur.next = node;
node.next = tmpNext;
}
删除第一个数值为 key 的节点
- 四种情况
- 1 . 链表为空,不用操作了
- 2 . 该删除的数值为 首节点 上
- 3 .该删除的数值为 尾节点 上 ,复用 ,尾删法
- 4 该删除的数值的节点,在中间
- 操作示图如下:
/*
删除第一个数值为 key 的节点
三种情况:
1.空链表,不用删了
2.位于首节点
3.在中间位置
*/
public void remove(int key) {
// 1.空链表,不用删了
if(null == searchIndex(key)) { // 该数值节点的地址
System.out.println("该数值的节点不存在,返回不执行");
return; // 执行完毕
}
// 2.位于尾节点
if(null == searchIndex(key).next) { // 该数值节点的地址
deleteList(); // 尾删法,复用
return; // 执行完毕
}
// 3.为与首节点
if(searchIndex(key) == this.pHead) {
deleteFirst(); // 头删法,复用
return; // 执行完毕
}
// 4.中间
Node cur = searchIndex(key);
Node copyPHead = this.pHead;
while(copyPHead!=cur) { // 找到该数值节点的前一个节点
copyPHead = copyPHead.next; // 移动节点
}
copyPHead.next = cur.next;
}
删除所有数值为 key 的节点
- 因为该单链表的缺陷:无法直接拿到该数值节点的 前一个节点 的位置
- 所以这里我们使用:前后两个引用的方式,删除节点
- 注意 :首节点我们最后判断是否为该数值的节点,是删除,因为这样比较简单,防止,漏了节点的判断,而没有完全删除该数值节点
- 操作示图:
// 删除所有数值为 key 的节点
public void removeKey(int key) {
Node tmp = this.pHead;
Node cur = this.pHead.next;
while(cur != null) {
if(key == cur.data) { // 是该数值节点,删除
tmp = cur.next;
cur = cur.next; // 移动节点
} else { // 不是该数值节点,移动
tmp = cur;
cur = cur.next;
}
if(key == this.pHead.data) {
// 判断首节点是否为该数值的节点,是山粗
this.pHead = this.pHead.next;
}
}
}
清空链表
- Java是有自动回收内存空间的机制的,该变量只有没有被引用,JVM 虚拟机才会回收该空间的,
- 当有人引用它的时候,是不会被回收的
- 当我们把首节点置为 NULL 的时候,也就不会有对下面的节点的引用了,JVM 虚拟机就会自动回收该内存空间
// 清空单链表
public void clear() {
this.pHead = null;
}
查看Java程序运行的进程
-
进程,进程 ,需要程序是在一个 运行 的状态
-
所以让我们的 Java程序卡在一个调试 的状态,不要让程序停止了,停止了,哪里有进程了
-
按键盘上的 win + R 键 在弹出中输入 cmd 进入 运行命令窗口
-
在运行命令窗口中输入
jps
- 显示如下:下面红色框框中的就是我们正在运行的一个,Java 程序
- 左边的数值是:该程序的进程号, 右边的表示的是:我们程序的名称
- 最后根据 进程号 输入以下命令:
jmap -histo:live 24328 >E:\临时文件\log.txt
24328:表示的是我们所对应程序的进程
> 表示的将内容重定向到,就是拷贝,复制到后面我们所定义到的文件中去:E:\临时文件\log.txt
如果没有重定 > 内容就会直接显示在我们的 运行对话窗口中
- 最后我们通过 调试 结束程序的运行:
- 内容就会 重定向 到我们定义的路径的文件当中去了
- 效果如下:
MyLinkList.java
- 单链表完整实现:
package Project.LinkList;
class Node { // 创建单链表的类,和C语言中的构造方法类似
public int data;
public Node next; // 对应的单链表的对象的引用,就是地址
public Node(int data) {
// 使用构造方法对其单链表的初始化,创建节点
this.data = data;
this.next = null;
}
}
public class MyLinkList {
public Node head;
// 头插法
/*
两种情况 1.空链表,不是空链表
**/
public void addFirst(int data) {
Node node = new Node(data); // 创建节点
if(this.head == null) {
// 1. 为空链表,直接置换
this.head = node;
return; // 程序执行完停止
}
// 2.不是空链表
node.next = this.head;
this.head = node;
}
// 打印单链表
public void disPlay() {
Node cur = this.head; // 首节点的拷贝,代替移动
while(cur != null) { // 循环遍历到尾节点 null
System.out.print(cur.data+" ");
cur = cur.next; // 移动节点
}
System.out.println();
}
// 尾插法
/*
1.链表为空
2,链表不为空,null ,找尾节点
* */
public void addList(int data) {
Node node = new Node(data); // 创建节点
Node cur = this.head; // 对首节点拷贝
// 链表为空
if(this.head == null) {
this.head = node;
return; // 程序执行完停止
}
while(cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
// 尾删法
/*
三种情况
1.空链表,不用删除
2.只有首节点,首节点置为null
3.多个节点的存在
* */
public void deleteList() {
Node cur = this.head;
Node tmp = this.head.next;
// 1.空链表,不用删除
if(this.head == null) {
throw new RuntimeException("空链表不用删了");
}
// 2.只有首节点,首节点置为null
if(this.head.next == null) {
this.head = null;
return; // 把首节点删除了
}
// 3.多个节点的存在
while(tmp.next != null) {
cur = tmp;
tmp = tmp.next;
}
cur.next = null;
}
// 查找是否包含关键字 key 是否在单链表当中
public boolean contains(int key) {
Node node = this.head; // 首节点的拷贝
while(node != null) {
if(key == node.data) {
return true; // 存在该数值的节点
}
node = node.next; // 移动节点
}
return false; // 不存在该数值的节点
}
// 该单链表的长度
public int size() {
if(null == this.head) {
throw new RuntimeException("该单链表为空");
}
int count = 0;
Node cur = this.head; // 首节点的拷贝,代替首节点的,移动
while(cur != null) { // 循环遍历,单链表
count ++;
cur = cur.next; // 移动节点
}
return count;
}
// 查找 x 序号的地址
private Node searchIndex(int x) {
Node cur = this.head; // 对首节点的拷贝,代替移动
if(x < 0 || x > size() ) {
throw new RuntimeException("该序号不合理");
}
while((x-1) != 0) { // 注意序号是从0 开始的 所以减 1;
cur = cur.next; // 移动节点
x--;
}
return cur; // 返回该节点的地址
}
// 在某个 序号后面插入数据节点
/* 三种情况
1.在开头,可以复用 头插法
2.在尾部,可以复用 尾插法
3.在中间,
* */
public void addIndex(int index,int data) {
//1. 在开头
if(index == 0 ) {
addFirst(data);
return; // 完成插入,程序停止执行
}
//2. 在尾部
if(index == size()) {
addList(data);
return; // 完成插入,程序停止执行
}
//3. 在中间
Node node = new Node(data); // 创建节点
Node cur = searchIndex(index); // 对应序号的节点的地址
node.next = cur.next; // 注意:其中的顺序,不要把链表丢了
cur.next = node;
}
// 查找数值 key 的地址
private Node searchPrev(int key) {
Node cur = this.head; // 首节点的拷贝,代替首节点,移动
if(this.head == null) {
throw new RuntimeException("链表为空,不用找了");
}
while(cur.next != null) {
if(cur.next.data == key) {
return cur; // 找到,返回该数值的前驱节点
} else {
cur = cur.next; // 移动节点
}
}
return null; // 没有找到,返回 null
}
// 删除第一个数值为 key 的节点
/*
三种情况:
1.空链表,不用删了
2.位于首节点
3.在中间位置
* */
public void remove(int key) {
// 1. 空链表
if(this.head == null) {
return; // 空链表,不用删了
}
// 该数值的节点,位于首节点
if(key == this.head.data) {
this.head = this.head.next;
return ;
}
Node prev = searchPrev(key); // 该数值的前驱节点
if(null == prev) {
System.out.println("该数值的节点,不存在");
return;
}
prev.next = prev.next.next;
}
// 删除所有数值为 key 的节点
public void removeKey(int key) {
Node prev = this.head;
Node cur = this.head.next;
while(cur != null) {
if(key == cur.data) { // 对应数值节点的删除
prev.next = cur.next;
cur = cur.next;
} else {
prev = cur;
cur = cur.next;
}
// 首节点删除
if(key == this.head.data) {
this.head = this.head.next;
}
}
}
// 清空单链表
public void clear() {
this.head = null;
}
/*
注意:明白一点:JVM是有自动回收空间的时候,但是只有没有对象
引用它的时候,JVM虚拟机才会,回收该空间的,在有人引用了它的时候是不会
被回收的,当我们把首节点置为null的时候,也就不会有对下面的节点,引用了
JVM虚拟机就会自动回收内存了
* */
}
TestDemo.java
- main
package Project.LinkList;
public class TestDemo {
public static void main(String[] args) {
test1(); // 测试一
System.out.println("************************");
test2(); // 测试二
System.out.println("************************");
test3(); // 测试三
}
public static void test1() {
// 创建单链表的实例
MyLinkList myLinkList = new MyLinkList();
myLinkList.addFirst(0); // 头插法
myLinkList.deleteList(); // 尾删法
myLinkList.disPlay(); // 打印单链表
myLinkList.addFirst(9); // 头插法
myLinkList.addFirst(99);
myLinkList.addFirst(999);
myLinkList.disPlay(); // 打印单链表
myLinkList.addList(10); // 头插法
myLinkList.addList(100);
myLinkList.addList(1000);
myLinkList.disPlay(); // 打印单链表
myLinkList.deleteList(); // 尾删法
myLinkList.disPlay(); // 打印单链表
}
public static void test2() {
// 创建单链表的实例
MyLinkList myLinkList = new MyLinkList();
myLinkList.addFirst(9); // 头插法
myLinkList.addFirst(99);
myLinkList.addFirst(999);
myLinkList.disPlay(); // 打印单链表
System.out.println(myLinkList.contains(0)); // 查找是否含有对应数值的节点
System.out.println(myLinkList.contains(999)); //
System.out.println(myLinkList.size()); // 查找单链表中含有多少了节点
myLinkList.addIndex(3,0); // 在某个序号中插入节点
myLinkList.addIndex(0,10);
myLinkList.addIndex(1,1000);
myLinkList.disPlay(); // 打印单链表
myLinkList.remove(0); // 删除第一个该数值的节点
myLinkList.remove(10);
myLinkList.disPlay(); // 打印单链表
}
public static void test3() {
// 创建节点
MyLinkList myLinkList = new MyLinkList();
myLinkList.addList(3); // 尾插法
myLinkList.addList(6);
myLinkList.addList(6);
myLinkList.addList(3);
myLinkList.addList(3);
myLinkList.addList(3);
myLinkList.addList(3);
myLinkList.addList(2);
myLinkList.addList(3);
myLinkList.disPlay(); // 打印该单链表
myLinkList.removeKey(3); // 删除所有该数值的节点
myLinkList.disPlay(); // 打印该单链表
myLinkList.clear(); // 清空单链表
System.out.println();
System.out.println();
}
}
最后限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家!
后会有期,江湖再见!