需求:
因为数组的各种局限性(长度,修改等),所以需要一种类似数组但是能够灵活操作的结构,也就是链表。
客户端(主类中)应该只是关心数据的存储、获取、修改等操作,而不是关心链表的操作,所以应该有一个专门的类来进行节点的配置。
例如:寄拿快递,本人只需要寄出或者拿取快递,关心的是寄出或者拿取多少快递,而不应该去关心快递的封装,运输等操作。
简单的链表逻辑创建:
// 通过即可设置标准 public interface ILink <E> { // 设置泛型避免安全隐患 public void add(E e); }
/* * 整个过程中LinkImpl类只是进行root节点的创建和add方法参数的接收并向Node节点发出指令addNode(),真正的节点操作类是LinkImpl类中的Node类 * 而节点的连接其实就是引用的传递,Node类只管进行将后节点的引用传给给前节点的next来记录 * data的值是通过客户端传入到LinkImpl的add()方法,所以Node节点并不关心data的值 * */ public class LinkImpl<E> implements ILink<E> { // 内部类的属性是提供给外部类使用,那么就没有必要再加上setter和getter方法了 private class Node{ //保存节点的数据关系 private E data; //保存的数据 private Node next; // 下一个节点的引用 public Node(E data){ // 有数据才有意义 this.data = data; } // 第一次调用:this = LinkImpl.root; // 第一次调用:this = LinkImpl.root.next; // 第一次调用:this = LinkImpl.root.next.next; public void addNode(Node newNode){ // 保存新的Node数据 if (this.next == null){ //当前节点(根节点)的下一个节点为空 // 下面将新节点(下一节点)的引用传给当前节点的next以记录地址 this.next = newNode; // 根节点的下一节点为newNode(将新节点与前一节点成功连接) }else { this.next.addNode(newNode); // 递归调用方法知道找出下一节点为空的节点并保存新节点 } } } /*--------------- 以下为LinkImpl类中定义的成员 ------------------*/ private Node root; // 保存根元素 /*--------------- 以下为LinkImpl类中定义的方法 ------------------*/ @Override public void add(E e) { if (e == null) {//保存的数据为空 return; } // 数据本身不具备关联特性,只有Node类有,那么想实现关联处理,就必须将数据包装在Node类中 Node newNode = new Node(e); // 创建一个新的节点,传入的数据e通过Node类的构造方法赋值给data if (this.root == null){ this.root = newNode; // 第一个节点为根节点 }else{ // 根节点存在 this.root.addNode(newNode); } } }
public class Main { public static void main(String[] args) { ILink<String> all = new LinkImpl<>(); all.add("Hello"); all.add("World"); all.add("!!!"); } }
获取集合个数、判断集合是否为空:
package Demo_1_28_链表; // 通过即可设置标准 public interface ILink <E> { // 设置泛型避免安全隐患 public void add(E e); // 增加数据 public int size(); // 获取数据的个数 public boolean isEmpty(); // 判断集合是否为空 }
package Demo_1_28_链表; /* * 整个过程中LinkImpl类只是进行root节点的创建和add方法参数的接收并向Node节点发出指令addNode(),真正的节点操作类是LinkImpl类中的Node类 * 而节点的连接其实就是引用的传递,Node类只管进行将后节点的引用传给给前节点的next来记录 * data的值是通过客户端传入到LinkImpl的add()方法,所以Node节点并不关心data的值 * */ public class LinkImpl<E> implements ILink<E> { // 内部类的属性是提供给外部类使用,那么就没有必要再加上setter和getter方法了 private class Node{ //保存节点的数据关系 private E data; //保存的数据 private Node next; // 下一个节点的引用 public Node(E data){ // 有数据才有意义 this.data = data; } // 第一次调用:this = LinkImpl.root; // 第一次调用:this = LinkImpl.root.next; // 第一次调用:this = LinkImpl.root.next.next; public void addNode(Node newNode){ // 保存新的Node数据 if (this.next == null){ //当前节点(根节点)的下一个节点为空 // 下面将新节点(下一节点)的引用传给当前节点的next以记录地址 this.next = newNode; // 根节点的下一节点为newNode(将新节点与前一节点成功连接) }else { this.next.addNode(newNode); // 递归调用方法知道找出下一节点为空的节点并保存新节点 } } } /*--------------- 以下为LinkImpl类中定义的成员 ------------------*/ private Node root; // 保存根元素 private int count; // 保存数据的个数 /*--------------- 以下为LinkImpl类中定义的方法 ------------------*/ @Override public void add(E e) { if (e == null) {//保存的数据为空 return; } // 数据本身不具备关联特性,只有Node类有,那么想实现关联处理,就必须将数据包装在Node类中 Node newNode = new Node(e); // 创建一个新的节点,传入的数据e通过Node类的构造方法赋值给data if (this.root == null){ this.root = newNode; // 第一个节点为根节点 }else{ // 根节点存在 this.root.addNode(newNode); } this.count ++; } @Override public int size() { return this.count; } @Override public boolean isEmpty() { // return this.root == null; return this.count == 0; // 两个return效果都是一样的,为空则返回true,否则返回false } }
package Demo_1_28_链表; public class Main { public static void main(String[] args) { ILink<String> all = new LinkImpl<>(); // 设置接口引用(all),这个引用(all)只能使用接口存在的方法和属性 System.out.println("add前数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); all.add("Hello"); all.add("World"); all.add("!!!"); System.out.println("add后数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); } }
返回集合的数据:
链表本身属于一个动态对象数组,所以可以把所有的数据以数组的形式返回,这时就可以定义一个toArray()方法,但是这个时候的方法只能返回Object类型的数组。
package Demo_1_28_链表; // 通过即可设置标准 public interface ILink <E> { // 设置泛型避免安全隐患 public void add(E e); // 增加数据 public int size(); // 获取数据的个数 public boolean isEmpty(); // 判断集合是否为空 public Object[] toArray(); // 将集合元素以数组的形式返回保存 }
package Demo_1_28_链表; /* * 整个过程中LinkImpl类只是进行root节点的创建和add方法参数的接收并向Node节点发出指令addNode(),真正的节点操作类是LinkImpl类中的Node类 * 而节点的连接其实就是引用的传递,Node类只管进行将后节点的引用传给给前节点的next来记录 * data的值是通过客户端传入到LinkImpl的add()方法,所以Node节点并不关心data的值 * */ public class LinkImpl<E> implements ILink<E> { // 内部类的属性是提供给外部类使用,那么就没有必要再加上setter和getter方法了 private class Node{ //保存节点的数据关系 private E data; //保存的数据 private Node next; // 下一个节点的引用 public Node(E data){ // 有数据才有意义 this.data = data; } // 第一次调用:this = LinkImpl.root; // 第二次调用:this = LinkImpl.root.next; // 第三次调用:this = LinkImpl.root.next.next; public void addNode(Node newNode){ // 保存新的Node数据 if (this.next == null){ //当前节点(根节点)的下一个节点为空 // 下面将新节点(下一节点)的引用传给当前节点的next以记录地址 this.next = newNode; // 根节点的下一节点为newNode(将新节点与前一节点成功连接) }else { this.next.addNode(newNode); // 递归调用方法知道找出下一节点为空的节点并保存新节点 } } // 第一次调用: this = LinkImpl.root // 第二次调用: this = LinkImpl.root.next // 第三次调用: this = LinkImpl.root.next.next public void toArrayNode(){ // 将数据添加到返回数组中 LinkImpl.this.returnData [LinkImpl.this.foot ++ ] = this.data; // 当前data添加到数组中 if (this.next != null){ //还有下一个数据 this.next.toArrayNode(); // 这一步是this.next调用的toArrayNode方法,递归进行数据的添加 } } } /*--------------- 以下为LinkImpl类中定义的成员 ------------------*/ private Node root; // 保存根元素 private int count; // 保存数据的个数 private int foot = 0; // 操作数组的脚标 private Object[] returnData; //返回的数据 /*--------------- 以下为LinkImpl类中定义的方法 ------------------*/ @Override public void add(E e) { if (e == null) {//保存的数据为空 return; } // 数据本身不具备关联特性,只有Node类有,那么想实现关联处理,就必须将数据包装在Node类中 Node newNode = new Node(e); // 创建一个新的节点,传入的数据e通过Node类的构造方法赋值给data if (this.root == null){ this.root = newNode; // 第一个节点为根节点 }else{ // 根节点存在 this.root.addNode(newNode); } this.count ++; } @Override public int size() { return this.count; } @Override public boolean isEmpty() { // return this.root == null; return this.count == 0; // 两个return效果都是一样的,为空则返回true,否则返回false } @Override public Object[] toArray() { if (this.isEmpty()){ // 空集合 return null; } this.foot = 0; // 脚标起始为0 this.returnData = new Object[this.count]; // 当前数据的个数就是返回数组的长度 this.root.toArrayNode(); // 利用Node类进行递归获取数据 return this.returnData; } }
package Demo_1_28_链表; import java.util.Arrays; public class Main { public static void main(String[] args) { ILink<String> all = new LinkImpl<>(); // 设置接口引用(all),这个引用(all)只能使用接口存在的方法和属性 System.out.println("add前数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); all.add("Hello"); all.add("World"); all.add("!!!"); System.out.println("add后数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); Object res [] = all.toArray(); System.out.println(Arrays.toString(res)); for (Object re : res) { System.out.println(re); } } }
获取指定索引数据:
数组的获取时间复杂度为1,链表的获取时间复杂度为n,因为需要遍历n遍;
package Demo_1_28_链表; // 通过接口设置标准 public interface ILink <E> { // 设置泛型避免安全隐患 public void add(E e); // 增加数据 public int size(); // 获取数据的个数 public boolean isEmpty(); // 判断集合是否为空 public Object[] toArray(); // 将集合元素以数组的形式返回保存 public E get(int index); // 根据索引获取数据 }
package Demo_1_28_链表; /* * 整个过程中LinkImpl类只是进行root节点的创建和add方法参数的接收并向Node节点发出指令addNode(),真正的节点操作类是LinkImpl类中的Node类 * 而节点的连接其实就是引用的传递,Node类只管进行将后节点的引用传给给前节点的next来记录 * data的值是通过客户端传入到LinkImpl的add()方法,所以Node节点并不关心data的值 * */ public class LinkImpl<E> implements ILink<E> { // 内部类的属性是提供给外部类使用,那么就没有必要再加上setter和getter方法了 private class Node{ //保存节点的数据关系 private E data; //保存的数据 private Node next; // 下一个节点的引用 public Node(E data){ // 有数据才有意义 this.data = data; } // 第一次调用:this = LinkImpl.root; // 第二次调用:this = LinkImpl.root.next; // 第三次调用:this = LinkImpl.root.next.next; public void addNode(Node newNode){ // 保存新的Node数据 if (this.next == null){ //当前节点(根节点)的下一个节点为空 // 下面将新节点(下一节点)的引用传给当前节点的next以记录地址 this.next = newNode; // 根节点的下一节点为newNode(将新节点与前一节点成功连接) }else { this.next.addNode(newNode); // 递归调用方法知道找出下一节点为空的节点并保存新节点 } } // 第一次调用: this = LinkImpl.root // 第二次调用: this = LinkImpl.root.next // 第三次调用: this = LinkImpl.root.next.next public void toArrayNode(){ // 将数据添加到返回数组中 LinkImpl.this.returnData [LinkImpl.this.foot ++ ] = this.data; // 当前data添加到数组中 if (this.next != null){ //还有下一个数据 this.next.toArrayNode(); // 这一步是this.next调用的toArrayNode方法,递归进行数据的添加 } } public E getNode(int index){ if (LinkImpl.this.foot ++ == index){ // 定位至指定的索引 return this.data; // 返回当前数据 }else { return this.next.getNode(index); // 递归调用继续向下匹配指定的索引 } } } /*--------------- 以下为LinkImpl类中定义的成员 ------------------*/ private Node root; // 保存根元素 private int count; // 保存数据的个数 private int foot = 0; // 操作数组的脚标 private Object[] returnData; //返回的数据 /*--------------- 以下为LinkImpl类中定义的方法 ------------------*/ @Override public void add(E e) { if (e == null) {//保存的数据为空 return; } // 数据本身不具备关联特性,只有Node类有,那么想实现关联处理,就必须将数据包装在Node类中 Node newNode = new Node(e); // 创建一个新的节点,传入的数据e通过Node类的构造方法赋值给data if (this.root == null){ this.root = newNode; // 第一个节点为根节点 }else{ // 根节点存在 this.root.addNode(newNode); } this.count ++; } @Override public int size() { return this.count; } @Override public boolean isEmpty() { // return this.root == null; return this.count == 0; // 两个return效果都是一样的,为空则返回true,否则返回false } @Override public Object[] toArray() { if (this.isEmpty()){ // 空集合 return null; } this.foot = 0; // 脚标起始为0 this.returnData = new Object[this.count]; // 当前数据的个数就是返回数组的长度 this.root.toArrayNode(); // 利用Node类进行递归获取数据 return this.returnData; } @Override public E get(int index) { if (index >= this.count){ // 索引大于数据的个数 return null; } //索引数据的获取应该由Node类完成 this.foot = 0; return this.root.getNode(index); } }
package Demo_1_28_链表; import java.util.Arrays; public class Main { public static void main(String[] args) { ILink<String> all = new LinkImpl<>(); // 设置接口引用(all),这个引用(all)只能使用接口存在的方法和属性 System.out.println("add前数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); all.add("Hello"); all.add("World"); all.add("!!!"); System.out.println("add后数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); Object res [] = all.toArray(); System.out.println(Arrays.toString(res)); for (Object re : res) { System.out.println(re); } System.out.println("---------------数据获取------------------"); System.out.println(all.get(0)); System.out.println(all.get(1)); System.out.println(all.get(2)); System.out.println(all.get(3)); } }
指定索引的数据修改:
此操作的时间复杂度也是为n。
package Demo_1_28_链表; // 通过接口设置标准 public interface ILink <E> { // 设置泛型避免安全隐患 public void add(E e); // 增加数据 public int size(); // 获取数据的个数 public boolean isEmpty(); // 判断集合是否为空 public Object[] toArray(); // 将集合元素以数组的形式返回保存 public E get(int index); // 根据索引获取数据 public void set(int index, E data); // 修改指定索引数据 }
package Demo_1_28_链表; /* * 整个过程中LinkImpl类只是进行root节点的创建和add方法参数的接收并向Node节点发出指令addNode(),真正的节点操作类是LinkImpl类中的Node类 * 而节点的连接其实就是引用的传递,Node类只管进行将后节点的引用传给给前节点的next来记录 * data的值是通过客户端传入到LinkImpl的add()方法,所以Node节点并不关心data的值 * */ public class LinkImpl<E> implements ILink<E> { // 内部类的属性是提供给外部类使用,那么就没有必要再加上setter和getter方法了 private class Node{ //保存节点的数据关系 private E data; //保存的数据 private Node next; // 下一个节点的引用 public Node(E data){ // 有数据才有意义 this.data = data; } // 第一次调用:this = LinkImpl.root; // 第二次调用:this = LinkImpl.root.next; // 第三次调用:this = LinkImpl.root.next.next; public void addNode(Node newNode){ // 保存新的Node数据 if (this.next == null){ //当前节点(根节点)的下一个节点为空 // 下面将新节点(下一节点)的引用传给当前节点的next以记录地址 this.next = newNode; // 根节点的下一节点为newNode(将新节点与前一节点成功连接) }else { this.next.addNode(newNode); // 递归调用方法知道找出下一节点为空的节点并保存新节点 } } // 第一次调用: this = LinkImpl.root // 第二次调用: this = LinkImpl.root.next // 第三次调用: this = LinkImpl.root.next.next public void toArrayNode(){ // 将数据添加到返回数组中 LinkImpl.this.returnData [LinkImpl.this.foot ++ ] = this.data; // 当前data添加到数组中 if (this.next != null){ //还有下一个数据 this.next.toArrayNode(); // 这一步是this.next调用的toArrayNode方法,递归进行数据的添加 } } public E getNode(int index){ if (LinkImpl.this.foot ++ == index){ // 定位至指定的索引 return this.data; // 返回当前数据 }else { return this.next.getNode(index); // 递归调用继续向下匹配指定的索引 } } public void setNode(int index, E data){ if (LinkImpl.this.foot ++ == index){ // 定位至指定的索引 this.data = data; // 返回当前数据 }else { this.next.setNode(index, data);// 递归调用继续向下匹配指定的索引 } } } /*--------------- 以下为LinkImpl类中定义的成员 ------------------*/ private Node root; // 保存根元素 private int count; // 保存数据的个数 private int foot = 0; // 操作数组的脚标 private Object[] returnData; //返回的数据 /*--------------- 以下为LinkImpl类中定义的方法 ------------------*/ @Override public void add(E e) { if (e == null) {//保存的数据为空 return; } // 数据本身不具备关联特性,只有Node类有,那么想实现关联处理,就必须将数据包装在Node类中 Node newNode = new Node(e); // 创建一个新的节点,传入的数据e通过Node类的构造方法赋值给data if (this.root == null){ this.root = newNode; // 第一个节点为根节点 }else{ // 根节点存在 this.root.addNode(newNode); } this.count ++; } @Override public int size() { return this.count; } @Override public boolean isEmpty() { // return this.root == null; return this.count == 0; // 两个return效果都是一样的,为空则返回true,否则返回false } @Override public Object[] toArray() { if (this.isEmpty()){ // 空集合 return null; } this.foot = 0; // 脚标起始为0 this.returnData = new Object[this.count]; // 当前数据的个数就是返回数组的长度 this.root.toArrayNode(); // 利用Node类进行递归获取数据 return this.returnData; } @Override public E get(int index) { if (index >= this.count){ // 索引大于数据的个数 return null; } //索引数据的获取应该由Node类完成 this.foot = 0; return this.root.getNode(index); } @Override public void set(int index, E data) { if (index >= this.count) { // 索引大于数据的个数 return; //结束 } //索引数据的修改应该由Node类完成 this.foot = 0; // 重置索引下标 this.root.setNode(index,data); // 修改数据 } }
package Demo_1_28_链表; import java.util.Arrays; public class Main { public static void main(String[] args) { ILink<String> all = new LinkImpl<>(); // 设置接口引用(all),这个引用(all)只能使用接口存在的方法和属性 System.out.println("add前数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); all.add("Hello"); all.add("World"); all.add("!!!"); System.out.println("add后数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); Object res [] = all.toArray(); System.out.println("修改前的数据:" + Arrays.toString(res)); System.out.println("---------------数据修改------------------"); all.set(1,"世界"); all.set(2,"@!@!@"); Object res1 [] = all.toArray(); System.out.println("修改后的数据:" + Arrays.toString(res1)); System.out.println("---------------数据获取------------------"); System.out.println(all.get(0)); System.out.println(all.get(1)); System.out.println(all.get(2)); System.out.println(all.get(3)); } }
节点的删除:
package Demo_1_28_链表; // 通过接口设置标准 public interface ILink <E> { // 设置泛型避免安全隐患 public void add(E e); // 增加数据 public int size(); // 获取数据的个数 public boolean isEmpty(); // 判断集合是否为空 public Object[] toArray(); // 将集合元素以数组的形式返回保存 public E get(int index); // 根据索引获取数据 public void set(int index, E data); // 修改指定索引数据 public boolean contains(E data); // 判断数据是否存在 public void remove(E e); // 删除节点 }
package Demo_1_28_链表; /* * 整个过程中LinkImpl类只是进行root节点的创建和add方法参数的接收并向Node节点发出指令addNode(),真正的节点操作类是LinkImpl类中的Node类 * 而节点的连接其实就是引用的传递,Node类只管进行将后节点的引用传给给前节点的next来记录 * data的值是通过客户端传入到LinkImpl的add()方法,所以Node节点并不关心data的值 * */ public class LinkImpl<E> implements ILink<E> { // 内部类的属性是提供给外部类使用,那么就没有必要再加上setter和getter方法了 private class Node{ //保存节点的数据关系 private E data; //保存的数据 private Node next; // 下一个节点的引用 public Node(E data){ // 有数据才有意义 this.data = data; } // 第一次调用:this = LinkImpl.root; // 第二次调用:this = LinkImpl.root.next; // 第三次调用:this = LinkImpl.root.next.next; public void addNode(Node newNode){ // 保存新的Node数据 if (this.next == null){ //当前节点(根节点)的下一个节点为空 // 下面将新节点(下一节点)的引用传给当前节点的next以记录地址 this.next = newNode; // 根节点的下一节点为newNode(将新节点与前一节点成功连接) }else { this.next.addNode(newNode); // 递归调用方法知道找出下一节点为空的节点并保存新节点 } } // 第一次调用: this = LinkImpl.root // 第二次调用: this = LinkImpl.root.next // 第三次调用: this = LinkImpl.root.next.next public void toArrayNode(){ // 将数据添加到返回数组中 LinkImpl.this.returnData [LinkImpl.this.foot ++ ] = this.data; // 当前data添加到数组中 if (this.next != null){ //还有下一个数据 this.next.toArrayNode(); // 这一步是this.next调用的toArrayNode方法,递归进行数据的添加 } } public E getNode(int index){ if (LinkImpl.this.foot ++ == index){ // 定位至指定的索引 return this.data; // 返回当前数据 }else { return this.next.getNode(index); // 递归调用继续向下匹配指定的索引 } } public void setNode(int index, E data){ if (LinkImpl.this.foot ++ == index){ // 定位至指定的索引 this.data = data; // 返回当前数据 }else { this.next.setNode(index, data);// 递归调用继续向下匹配指定的索引 } } public boolean containsNode(E data){ if (data.equals(this.data)){ // 想要查询的数据等于当前数据则表示存在 return true; // 返回真 }else { // 想要查询的数据不等当前数据则表示想要查询的数据不在当前位置 if (this.next == null){ return false; // 当前位置的下一个数据的地址为空返回false(后面没有数据了) }else { return this.next.containsNode(data); // 继续判断想要查询的数据是否在下一个位置 } } } public void removeNode(Node previous,E data){ if (this.data.equals(data)){ // 找到指定位置的节点 System.out.println("删除了节点数据为:" + data + "、地址为:" + previous.next); previous.next = this.next; // 上一节点与当前节点的下一节点连接,以空出当前节点 }else { // 当前节点不是指定节点 if (this.next != null){ // 如果当前节点的下一个节点不为空 this.next.removeNode(this,data); // 这里传入到下一个节点,this是当前节点(下一节点的上节点),data是指定的节点 } } } } /*--------------- 以下为LinkImpl类中定义的成员 ------------------*/ private Node root; // 保存根元素 private int count; // 保存数据的个数 private int foot = 0; // 操作数组的脚标 private Object[] returnData; //返回的数据 /*--------------- 以下为LinkImpl类中定义的方法 ------------------*/ @Override public void add(E e) { if (e == null) {//保存的数据为空 return; } // 数据本身不具备关联特性,只有Node类有,那么想实现关联处理,就必须将数据包装在Node类中 Node newNode = new Node(e); // 创建一个新的节点,传入的数据e通过Node类的构造方法赋值给data if (this.root == null){ this.root = newNode; // 第一个节点为根节点 }else{ // 根节点存在 this.root.addNode(newNode); } this.count ++; } @Override public int size() { return this.count; } @Override public boolean isEmpty() { // return this.root == null; return this.count == 0; // 两个return效果都是一样的,为空则返回true,否则返回false } @Override public Object[] toArray() { if (this.isEmpty()){ // 空集合 return null; } this.foot = 0; // 脚标起始为0 this.returnData = new Object[this.count]; // 当前数据的个数就是返回数组的长度 this.root.toArrayNode(); // 利用Node类进行递归获取数据 return this.returnData; } @Override public E get(int index) { if (index >= this.count){ // 索引大于数据的个数 return null; } //索引数据的获取应该由Node类完成 this.foot = 0; return this.root.getNode(index); } @Override public void set(int index, E data) { if (index >= this.count) { // 索引大于数据的个数 return; //结束 } //索引数据的修改应该由Node类完成 this.foot = 0; // 重置索引下标 this.root.setNode(index,data); // 修改数据 } @Override public boolean contains(E data) { if (data == null){ return false; // 想查看的数据为空返回false } return this.root.containsNode(data); } @Override public void remove(E data) { if (this.contains(data)){ // 判断想要删除的节点是否存在 if (this.root.data.equals(data)){ // 如果想要删除的节点已存在,判断是否为根节点 System.out.println(data + "-->数据为根节点已经删除!"); this.root = this.root.next; // 删除根节点 // 根节点判断完成,以后应该从下一节点开始判断 }else { this.root.next.removeNode(this.root,data); // 不是根节点那么就让Node节点进行操作,传入根节点作为节点的上一节点,和指定节点data } this.count --; } } }
package Demo_1_28_链表; import java.util.Arrays; public class Main { public static void main(String[] args) { ILink<String> all = new LinkImpl<>(); // 设置接口引用(all),这个引用(all)只能使用接口存在的方法和属性 System.out.println("add前数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); all.add("Hello"); all.add("World"); all.add("!!!"); System.out.println("add后数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); Object res [] = all.toArray(); System.out.println("修改前的数据:" + Arrays.toString(res)); System.out.println("---------------数据修改------------------"); all.set(1,"世界"); all.set(2,"@!@!@"); Object res1 [] = all.toArray(); System.out.println("修改后的数据:" + Arrays.toString(res1)); System.out.println("---------------数据获取------------------"); System.out.println(all.get(0)); System.out.println(all.get(1)); System.out.println(all.get(2)); System.out.println(all.get(3)); System.out.println("---------------查看数据是否存在------------------"); if (all.contains("世界")){ // 存在则为真 System.out.println("存在!"); }else { // 数据不存在 System.out.println("Not Found!"); } System.out.println("---------------数据删除------------------"); all.remove("Hello"); all.remove("@!@!@"); Object res2 [] = all.toArray(); System.out.println("删除后的数据:" + Arrays.toString(res2)); } }
链表清空:
package Demo_1_28_链表; // 通过接口设置标准 public interface ILink <E> { // 设置泛型避免安全隐患 public void add(E e); // 增加数据 public int size(); // 获取数据的个数 public boolean isEmpty(); // 判断集合是否为空 public Object[] toArray(); // 将集合元素以数组的形式返回保存 public E get(int index); // 根据索引获取数据 public void set(int index, E data); // 修改指定索引数据 public boolean contains(E data); // 判断数据是否存在 public void remove(E e); // 删除指定节点 public void clean(); // 清空链表 }
package Demo_1_28_链表; /* * 整个过程中LinkImpl类只是进行root节点的创建和add方法参数的接收并向Node节点发出指令addNode(),真正的节点操作类是LinkImpl类中的Node类 * 而节点的连接其实就是引用的传递,Node类只管进行将后节点的引用传给给前节点的next来记录 * data的值是通过客户端传入到LinkImpl的add()方法,所以Node节点并不关心data的值 * */ public class LinkImpl<E> implements ILink<E> { // 内部类的属性是提供给外部类使用,那么就没有必要再加上setter和getter方法了 private class Node{ //保存节点的数据关系 private E data; //保存的数据 private Node next; // 下一个节点的引用 public Node(E data){ // 有数据才有意义 this.data = data; } // 第一次调用:this = LinkImpl.root; // 第二次调用:this = LinkImpl.root.next; // 第三次调用:this = LinkImpl.root.next.next; public void addNode(Node newNode){ // 保存新的Node数据 if (this.next == null){ //当前节点(根节点)的下一个节点为空 // 下面将新节点(下一节点)的引用传给当前节点的next以记录地址 this.next = newNode; // 根节点的下一节点为newNode(将新节点与前一节点成功连接) }else { this.next.addNode(newNode); // 递归调用方法知道找出下一节点为空的节点并保存新节点 } } // 第一次调用: this = LinkImpl.root // 第二次调用: this = LinkImpl.root.next // 第三次调用: this = LinkImpl.root.next.next public void toArrayNode(){ // 将数据添加到返回数组中 LinkImpl.this.returnData [LinkImpl.this.foot ++ ] = this.data; // 当前data添加到数组中 if (this.next != null){ //还有下一个数据 this.next.toArrayNode(); // 这一步是this.next调用的toArrayNode方法,递归进行数据的添加 } } public E getNode(int index){ if (LinkImpl.this.foot ++ == index){ // 定位至指定的索引 return this.data; // 返回当前数据 }else { return this.next.getNode(index); // 递归调用继续向下匹配指定的索引 } } public void setNode(int index, E data){ if (LinkImpl.this.foot ++ == index){ // 定位至指定的索引 this.data = data; // 返回当前数据 }else { this.next.setNode(index, data);// 递归调用继续向下匹配指定的索引 } } public boolean containsNode(E data){ if (data.equals(this.data)){ // 想要查询的数据等于当前数据则表示存在 return true; // 返回真 }else { // 想要查询的数据不等当前数据则表示想要查询的数据不在当前位置 if (this.next == null){ return false; // 当前位置的下一个数据的地址为空返回false(后面没有数据了) }else { return this.next.containsNode(data); // 继续判断想要查询的数据是否在下一个位置 } } } public void removeNode(Node previous,E data){ if (this.data.equals(data)){ // 找到指定位置的节点 System.out.println("删除了节点数据为:" + data + "、地址为:" + previous.next); previous.next = this.next; // 上一节点与当前节点的下一节点连接,以空出当前节点 }else { // 当前节点不是指定节点 if (this.next != null){ // 如果当前节点的下一个节点不为空 this.next.removeNode(this,data); // 这里传入到下一个节点,this是当前节点(下一节点的上节点),data是指定的节点 } } } } /*--------------- 以下为LinkImpl类中定义的成员 ------------------*/ private Node root; // 保存根元素 private int count; // 保存数据的个数 private int foot = 0; // 操作数组的脚标 private Object[] returnData; //返回的数据 /*--------------- 以下为LinkImpl类中定义的方法 ------------------*/ @Override public void add(E e) { if (e == null) {//保存的数据为空 return; } // 数据本身不具备关联特性,只有Node类有,那么想实现关联处理,就必须将数据包装在Node类中 Node newNode = new Node(e); // 创建一个新的节点,传入的数据e通过Node类的构造方法赋值给data if (this.root == null){ this.root = newNode; // 第一个节点为根节点 }else{ // 根节点存在 this.root.addNode(newNode); } this.count ++; } @Override public int size() { return this.count; } @Override public boolean isEmpty() { // return this.root == null; return this.count == 0; // 两个return效果都是一样的,为空则返回true,否则返回false } @Override public Object[] toArray() { if (this.isEmpty()){ // 空集合 return null; } this.foot = 0; // 脚标起始为0 this.returnData = new Object[this.count]; // 当前数据的个数就是返回数组的长度 this.root.toArrayNode(); // 利用Node类进行递归获取数据 return this.returnData; } @Override public E get(int index) { if (index >= this.count){ // 索引大于数据的个数 return null; } //索引数据的获取应该由Node类完成 this.foot = 0; return this.root.getNode(index); } @Override public void set(int index, E data) { if (index >= this.count) { // 索引大于数据的个数 return; //结束 } //索引数据的修改应该由Node类完成 this.foot = 0; // 重置索引下标 this.root.setNode(index,data); // 修改数据 } @Override public boolean contains(E data) { if (data == null){ return false; // 想查看的数据为空返回false } return this.root.containsNode(data); } @Override public void remove(E data) { if (this.contains(data)){ // 判断想要删除的节点是否存在 if (this.root.data.equals(data)){ // 如果想要删除的节点已存在,判断是否为根节点 System.out.println(data + "-->数据为根节点已经删除!"); this.root = this.root.next; // 删除根节点 // 根节点判断完成,以后应该从下一节点开始判断 }else { this.root.next.removeNode(this.root,data); // 不是根节点那么就让Node节点进行操作,传入根节点作为节点的上一节点,和指定节点data } this.count --; } } @Override public void clean() { this.root = null; // 根节点重置为空(根节点后指向的地址重置为空),后续所有节点为空 this.count = 0; // 个数清零 } }
package Demo_1_28_链表; import java.util.Arrays; public class Main { public static void main(String[] args) { ILink<String> all = new LinkImpl<>(); // 设置接口引用(all),这个引用(all)只能使用接口存在的方法和属性 System.out.println("add前数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); all.add("Hello"); all.add("World"); all.add("!!!"); System.out.println("add后数据的个数:" + all.size()); System.out.println("集合是否为空:" + all.isEmpty()); Object res [] = all.toArray(); System.out.println("修改前的数据:" + Arrays.toString(res)); System.out.println("---------------数据修改------------------"); all.set(1,"世界"); all.set(2,"@!@!@"); Object res1 [] = all.toArray(); System.out.println("修改后的数据:" + Arrays.toString(res1)); System.out.println("---------------数据获取------------------"); System.out.println(all.get(0)); System.out.println(all.get(1)); System.out.println(all.get(2)); System.out.println(all.get(3)); System.out.println("---------------查看数据是否存在------------------"); if (all.contains("世界")){ // 存在则为真 System.out.println("存在!"); }else { // 数据不存在 System.out.println("Not Found!"); } System.out.println("---------------数据删除------------------"); all.remove("Hello"); all.remove("@!@!@"); Object res2 [] = all.toArray(); System.out.println("删除后的数据:" + Arrays.toString(res2)); System.out.println("---------------数据清零------------------"); all.clean(); Object res3 [] = all.toArray(); if (res3 != null) { System.out.println("清空后的数据:" + Arrays.toString(res3)); }else{ System.out.println("数据清空后的链表数据:" + res3); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)