目录
1. ArrayList的缺陷
2. 链表
2.1 链表的概念及结构
2.2 接口的实现
3. 动手实现单链表
3.1 重写SeqList接口方法
3.2 在当前链表头部添加节点(头插)
3.3 在 第index位置添加节点(任意位置)
3.4 在当前链表尾部添加节点(尾插)
3.5 删除第index个节点
3.6 检验index是否合法
3.7 删除第一个值element的节点
3.8 删除所有值element的节点
3.9 修改第index个节点的值为element
3.10 获取第index个节点的值
3.11 判断链表中是否存在element
3.12 获取element在链表中的位置
3.13 打印链表
4. SingleLinkedList整体实现
4.1 SingleLinkedList类
4.2 Test类
4.3 测试结果
由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
(3)循环或者非循环
虽然有这么多的链表的结构,但是我们重点掌握两种: 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。 无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。
使用泛型更好的接收不同数据类型(一个顺序表只能接受一种类型!)
public interface SeqList {// 尾插void add(E element);// 将 element 插入到 index 位置void add(int index,E element);// 删除 index 位置元素<返回删除的值E removeByIndex(int index);// 删除第一个值element的元素void removeByValue(E element);// 删除所有值element的元素void removeAllValue(E element);// 将下标 index 位置元素设置为 element,返回替换前的值E set(int index,E element);E get(int index);// 判断 o 是否在其中boolean contains(E element);int indexOf(E element);int size();void clear();}
实现没有头节点的单链表
public class SingleLinkedList implements SeqList {private Node head;//头节点private int size; // 节点个数,保存的元素个数//类的定义,内部类,对外部完全隐藏private class Node {E val;//保存的元素Node next;//下节车厢的位置Node(E val) {this.val = val;}}public void addFrist(E val){ }@Overridepublic void add(E element) { }@Overridepublic void add(int index, E element) { }@Overridepublic E removeByIndex(int index) { }@Overridepublic void removeByValue(E element) { }@Overridepublic void removeAllValue(E element) { }@Overridepublic E set(int index, E element) { }public boolean rangeCheck(int index){ }@Overridepublic E get(int index) { }@Overridepublic boolean contains(E element) { }@Overridepublic int indexOf(E element) { }@Overridepublic int size() { }@Overridepublic void clear() { }@Overridepublic String toString() { }
}
无论链表中是否包含节点,头插法都是这相同的代码处理~
public void addFrist(E val){// 要插入一个元素,首先要产生一个新节点Node node = new Node(val);// 将当前节点插入到链表中node.next = head;// 更新head的引用,指向当前的新节点head = node;size ++;}public void add(E element) {add(size,element);}
思考一下,①和②的代码顺序能换吗?
先执行2再执行1,原来的链表就丢了,head指向新节点,最终链表中只剩下新节点了~
(1)判断index是否合法
(2)判断没有前驱,没有就是头插
(3)寻找index位置的前驱节点(单链表的核心操作就是在寻找前驱节点!)
(4)先将新节点和原先位置节点连接
(5)再更改原先prev的引用
public void add(int index, E element) {// 1.base case,边界判断if (index < 0 || index > size) {throw new IllegalArgumentException("add index illegal!");}// 2.判断没有前驱的情况if (index == 0) {addFrist(element);return;}// 3.确实是中间位置的插入,寻找待插入位置的前驱!Node prev = head;for (int i = 0; i < index - 1; i++) {prev = prev.next;}// prev走到待插入位置的前驱Node node = new Node(element);node.next = prev.next;prev.next = node;size ++;}
第一种方法
(1)走到当前链表的最后
(2)让最后节点的next指向这个新添加的节点
第二种方法
调用add()方法,index等于size就是在链表末尾插入了
public void addLast(E val){Node prev = head;while (prev.next != null){prev = prev.next;}Node node = new Node(val);prev.next = node;}//尾插2@Overridepublic void add(E element) {add(size,element);}
(1)判断index是否合法(链表为空则index不合法)
(2)判断没有前驱,没有就是头节点删除
(3)寻找index位置的前驱节点
(4)将前驱节点和原先位置的下一个节点连接
public E removeByIndex(int index) {// 1.base caseif (!rangeCheck(index)) {throw new IllegalArgumentException("remove index illegal!");}// 2.判断头节点的删除问题if (index == 0) {Node node = head;head = head.next;node.next = null;size --;return node.val;}// 3.现在确实是中间位置的删除Node prev = head;for (int i = 0; i < index - 1; i++) {prev = prev.next;}Node node = prev.next;prev.next = node.next;node.next = null;size --;return node.val;}
public boolean rangeCheck(int index){if (index < 0 || index >= size) {return false;}return true;}
(1)判断链表是否为空
(2)判断头节点是否是待删除的节点,是的话删除完就方法返回
(3)寻找element节点的前驱节点(凡是用到引用的位置,一定要保证这个引用不为空)
(4)将前驱节点和原先位置的下一个节点连接
这里的图和3.5 中的图一致
public void removeByValue(E element) {// 1.base caseif (head == null) {return;}// 2.判断头节点恰好是待删除的节点if (head.val.equals(element)) {head = head.next;size --;return;}// 3.此时头节点不为空其一定不是待删除的结点Node prev = head;while (prev.next != null) {if (prev.next.val.equals(element)) {// prev走到了待删除节点的前驱位置prev.next = prev.next.next;size --;return;}prev = prev.next;}// 4.链表中没有待删除的节点System.out.println("当前链表中不存在值为" + element + "的节点");}
(1)判断链表是否为空
(2)判断头节点是否是待删除的节点
(3)判断链表是否已经删除完,删除完就返回
(4)寻找element节点的前驱节点(凡是用到引用的位置,一定要保证这个引用不为空)
(5)将前驱节点和原先位置的下一个节点连接
(6)如果下一个节点不是待删除的节点,prev指向下一个节点
(7)循环执行步骤(5)、(6)直到链表走完(prev.next != null)
public void removeAllValue(E element) {// 1.base caseif(head == null) {return;}// 2.若头节点就是待删除的节点且出现连续的待删除节点while (head != null && head.val.equals(element)) {head = head.next;size --;}if (head == null) {// 整个链表已经删除完了return;}// 3.头节点一定不是待删除的节点且链表不为空!// prev一定指向的不是待删除的结点~Node prev = head;while (prev.next != null) {if (prev.next.val.equals(element)) {// 此时prev就是待删除节点的前驱prev.next = prev.next.next;size --;}else {// 只有后继节点不是待删除的结点才能移动prev引用!prev = prev.next;}}}
(1)判断index是否合法
(2)寻找index位置的节点
(3)将节点的val修改为element
public E set(int index, E element) {
// 1.索引的合法性校验if(!rangeCheck(index)){throw new IllegalArgumentException("set index illegal!");}// 从前向后遍历走到index对应的元素Node x = head;for (int i = 0; i < index; i++) {x = x.next;}// 此时x就落在了待修改的节点位置E oldVal = x.val;x.val = element;return oldVal;}public boolean rangeCheck(int index){if (index < 0 || index >= size) {return false;}return true;}
public E get(int index) {if(!rangeCheck(index)){throw new IllegalArgumentException("get index illegal!");}Node x = head;for (int i = 0; i < index; i++) {x = x.next;}return x.val;}
public boolean contains(E element) {Node cur = head;while (cur.next != null){if (cur.val.equals(element)){return true;}}return false;}
public int indexOf(E element) {int count = 0;Node cur = head;while (cur.next != null){if (cur.val.equals(element)){return count;}count++;cur = cur.next;}return -1;}
public String toString() {StringBuilder sb = new StringBuilder();// 从当前链表的第一个节点开始向后遍历,直到走到尾结点为止// 第一个节点headfor(Node x = head;x != null;x = x.next){sb.append(x.val);sb.append("->");if(x.next == null){// 此时temp走到了尾结点sb.append("NULL");}}return sb.toString();}
3.14 获取链表长度以及清空链表
public int size() {return this.size;}@Overridepublic void clear() {Node cur = this.head;Node curNext = null;while (cur != null) {curNext = cur.next;cur.next = null;cur = curNext;}head = null;}
public class SingleLinkedList implements SeqList {private Node head;//头节点private int size; // 车厢节点个数,保存的元素个数//车厢类的定义,车厢作为火车的内部类,对外部完全隐藏private class Node {E val;//保存的元素Node next;//下节车厢的位置Node(E val) {this.val = val;}}// ------------------------------------------------// 头插,在当前链表头部添加元素 head在移动public void addFrist(E val){// 要插入一个元素,首先要产生一个新节点Node node = new Node(val);// 将当前节点插入到链表中node.next = head;// 更新head的引用,指向当前的新节点head = node;size ++;}@Overridepublic void add(E element) {add(size,element);}@Overridepublic void add(int index, E element) {// 1.base case,边界判断if (index < 0 || index > size) {throw new IllegalArgumentException("add index illegal!");}// 2.判断没有前驱的情况if (index == 0) {addFrist(element);return;}// 3.确实是中间位置的插入,寻找待插入位置的前驱!Node prev = head;for (int i = 0; i < index - 1; i++) {prev = prev.next;}// prev走到待插入位置的前驱Node node = new Node(element);node.next = prev.next;prev.next = node;size ++;}@Overridepublic E removeByIndex(int index) {// 1.base caseif (!rangeCheck(index)) {throw new IllegalArgumentException("remove index illegal!");}// 2.判断头节点的删除问题if (index == 0) {Node node = head;head = head.next;node.next = null;size --;return node.val;}// 3.现在确实是中间位置的删除Node prev = head;for (int i = 0; i < index - 1; i++) {prev = prev.next;}Node node = prev.next;prev.next = node.next;node.next = null;size --;return node.val;}@Overridepublic void removeByValue(E element) {// 1.base caseif (head == null) {return;}// 2.判断头节点恰好是待删除的节点if (head.val.equals(element)) {head = head.next;size --;return;}// 3.此时头节点不为空其一定不是待删除的结点Node prev = head;while (prev.next != null) {if (prev.next.val.equals(element)) {// prev走到了待删除节点的前驱位置prev.next = prev.next.next;size --;return;}prev = prev.next;}// 4.链表中没有待删除的节点System.out.println("当前链表中不存在值为" + element + "的节点");}@Overridepublic void removeAllValue(E element) {// 1.base caseif(head == null) {return;}// 2.若头节点就是待删除的节点且出现连续的待删除节点while (head != null && head.val.equals(element)) {head = head.next;size --;}if (head == null) {// 整个链表已经删除完了return;}// 3.头节点一定不是待删除的节点且链表不为空!// prev一定指向的不是待删除的结点~Node prev = head;while (prev.next != null) {if (prev.next.val.equals(element)) {// 此时prev就是待删除节点的前驱prev.next = prev.next.next;size --;}else {// 只有后继节点不是待删除的结点才能移动prev引用!prev = prev.next;}}}// 修改@Overridepublic E set(int index, E element) {
// 1.索引的合法性校验if(!rangeCheck(index)){throw new IllegalArgumentException("set index illegal!");}// 从前向后遍历走到index对应的元素Node x = head;for (int i = 0; i < index; i++) {x = x.next;}// 此时x就落在了待修改的节点位置E oldVal = x.val;x.val = element;return oldVal;}public boolean rangeCheck(int index){if (index < 0 || index >= size) {return false;}return true;}@Overridepublic E get(int index) {if(!rangeCheck(index)){throw new IllegalArgumentException("get index illegal!");}Node x = head;for (int i = 0; i < index; i++) {x = x.next;}return x.val;}@Overridepublic boolean contains(E element) {Node cur = head;while (cur.next != null){if (cur.val.equals(element)){return true;}}return false;}@Overridepublic int indexOf(E element) {int count = 0;Node cur = head;while (cur.next != null){if (cur.val.equals(element)){return count;}count++;cur = cur.next;}return -1;}@Overridepublic int size() {return this.size;}@Overridepublic void clear() {Node cur = this.head;Node curNext = null;while (cur != null) {curNext = cur.next;cur.next = null;cur = curNext;}head = null;}@Overridepublic String toString() {StringBuilder sb = new StringBuilder();// 从当前链表的第一个节点开始向后遍历,直到走到尾结点为止// 第一个节点headfor(Node x = head;x != null;x = x.next){sb.append(x.val);sb.append("->");if(x.next == null){// 此时temp走到了尾结点sb.append("NULL");}}return sb.toString();}
}
public class SingleLinkedTest {public static void main(String[] args) {SingleLinkedList list = new SingleLinkedList<>();list.add(1);list.add(2);list.add(3);list.add(9);list.add(10);list.add(10);list.add(6);list.add(10);list.add(5);list.add(7);list.add(10);list.add(10);System.out.println(list);System.out.println("------------添加测试-----------");System.out.println("从链表尾部添加99");list.add(99);System.out.println("添加index为2,element为88");list.add(2,88);System.out.println(list);System.out.println("-----------删除测试------------");System.out.println("删除index为0");list.removeByIndex(0);System.out.println("删除元素为6");list.removeByValue(6);System.out.println("删除所有10");list.removeAllValue(10);System.out.println(list);System.out.println("-----------其他------------");System.out.println("查看是否包含10这个元素");System.out.println(list.contains(10));System.out.println("修改index为3,element为19");list.set(3,19);System.out.println("获取index为3的元素");System.out.println(list.get(3));System.out.println(list);System.out.println("获取element为88的index");System.out.println(list.indexOf(88));System.out.println("获取顺序表长度");System.out.println(list.size());System.out.println("清空顺序表");list.clear();System.out.println(list + "...");}
}