🐵本篇文章将对单链表进行讲解,模拟实现单链表中常见的方法
一、什么是链表
链表是一种逻辑结构上连续而物理结构上不一定连续的线性表,链表由一个一个节点组成:
每一个节点中都由数据域(val)和指针域(next)组成,数据域用于存放要存放在该节点的数据,指针域用于存放节点的地址,上图中链表的每一个节点的指针域存放的是下一个节点的地址,最后一个节点的指针指向空
二、链表的种类
链表细分可以分为八种,分别为:
1. 单向不带头非循环链表 2. 单向不带头循环链表
3. 单向带头非循环链表 4. 单向带头循环链表
5. 双向不带头非循环链表 6. 双向不带头循环链表
7. 单向带头非循环链表 8. 双向带头循环链表
三、链表的模拟实现
首先对第一种单链表的增删查改等操作用Java进行模拟实现,先将要模拟实现的方法写到IList接口中:
public interface IList {//头插法public void addFirst(int data);//尾插法public void addLast(int data);//任意位置插入,第一个数据节点为0号下标public void addIndex(int index,int data);//查找关键字key是否在单链表当中public boolean contains(int key);//删除第一次出现关键字为key的节点public void remove(int key);//删除所有值为key的节点public void removeAllKey(int key);//得到单链表的长度public int size();//清空链表public void clear();//遍历并展示链表中的数据public void display();
}
之后再创建一个MySingleList类实现上述接口并重写接口中所有的方法
public class MySingleList implements IList{static class ListNode {public int val;public ListNode next;public ListNode(int val) {this.val = val;}}public ListNode head; //指向第一个节点/*以下是要重写IList接口中的方法*/...}
在MySingleList类中创建一个静态内部类ListNode用于描述一个节点对象,val用于存放数据,next用于存放下一个节点的地址,构造方法用于初始化数据va,接下来对上述方法进行模拟实现
3.1 方法的实现
public void addFirst(int data);
在链表的头部新增一个节点newNode
很明显要在头部新增一个节点就应让newNode的next指向head,并让newNode成为head
public void addFirst(int data) {ListNode newNode = new ListNode(data);newNode.next = head;head = newNode;}
public void addLast(int data);
在链表的尾部新增一个节点
应让最后一个节点的next引用指向newNode,链表无法像顺序表一样可以直接找到某个位置的节点,所以应该通过循环先找到最后一个节点:
ListNode cur = head; //通常一个链表的头指针不动,需要定义一个cur来遍历链表、while (cur.next != null) {cur = cur.next; //只要cur的next不是空,cur就指向下一个节点
}//循环完成后cur就是最后一个节点
cur.next = newNode;
完成以上操作后还要考虑一个特殊情况:如果链表为空,上述代码能否完成尾插操作?答案是不能的,因为如果链表为空,则head为空,cur为空,在循环条件处cur.next != null就会抛出一个NullPointerException的空指针异常,所以要单独处理链表为空的情况:
public void addLast(int data) {ListNode newNode = new ListNode(data);if (head == null) { //如果链表为空,则直接让head指向newNodehead = newNode;} else {ListNode cur = head;while (cur.next != null) {cur = cur.next;}cur.next = newNode;}}
public int size();
求链表的长度也就是该链表有几个节点,只要直接遍历链表即可
public int size() {ListNode cur = head;int count = 0;while(cur != null) { //注意这里和尾插的循环条件不一样count++;cur = cur.next;}return count;}
public boolean contains(int key);
判断链表中是否有节点的val值等于key,也是只要直接遍历链表即可
public boolean contains(int key) {ListNode cur = head;while(cur != null) {if (cur.val == key) {return true;}cur = cur.next;}return false;}
public void addIndex(int index,int data);
在任意位置处插入一个节点,第一个节点的索引为0
首先要判断一下index是否合法:
public class IndexException extends RuntimeException{public IndexException(String message) {super(message);}
}=======================================================public void addIndex(int index, int data) {if (index < 0 || index > size()) {throw new IndexException("下标错误");}...
}
在任意位置插入节点,所以如果index为0或等于链表长度,就可以直接使用刚刚实现过的头插和尾插方法
public void addIndex(int index, int data) {if (index < 0 || index > size()) {throw new IndexException("下标错误");}if (index == 0) {addFirst(data);}if (index == size()) {addLast(data);}...
}
然后就是一般情况下:
如果要在1位置处插入新节点,那就应该先找到1位置处的节点的前驱节点,这里定义为cur,令newNode.next = cur.next;这样newNode的next就指向1位置处的节点,之后令cur.next = newNode这样就插入完成了
public void addIndex(int index, int data) {if (index < 0 || index > size()) {throw new IndexException("下标错误");}if (index == 0) {addFirst(data);}if (index == size()) {addLast(data);}ListNode newNode = new ListNode(data);ListNode cur = head;for (int i = 0; i < index - 1; i++) { 找到插入节点的前驱节点cur = cur.next;}newNode.next = cur.next;cur.next = newNode;}
public void remove(int key);
假如要删除val = 20的节点(这里先定义为del)如果要删除该节点就应让del的前驱节点的next指向del的后继节点:
public void remove(int key) {ListNode cur = head;while(cur.next.val != key) { //通过循环找到del的前驱节点cur = cur.next; }cur.next = cur.next.next;}
写完后还要考虑两种特殊情况:
第一,如果链表为空,上述代码不能满足要求,因为在循环条件处会抛出空指针异常
第二,如果要删除的节点是第一个节点,上述代码也不能满足要求,因为该循环是从第一个节点的下一个节点开始判断的
以上两种情况都需要单独处理:
public void remove(int key) {if (head == null) {return;}if (head.val == key) {head = head.next;return;}ListNode cur = head;while(cur.next.val != key) {cur = cur.next;}cur.next = cur.next.next;}
public void removeAllKey(int key);
删除所有val = key的节点,那就需要遍历整个链表,在遍历过程中删除节点:
public void removeAllKey(int key) {ListNode cur = head;while (cur.next != null) {if(cur.next.val == key) {cur.next = cur.next.next;} else {cur = cur.next;}}}
在循环的结束也要判断一下第一个节点是否为要删除的节点,因为上述循环条件为cur.next != null没有判断第一个节点:
public void removeAllKey(int key) {if (head == null) { //如果链表为空则直接返回return;}ListNode cur = head;while (cur.next != null) {if(cur.next.val == key) {cur.next = cur.next.next;} else {cur = cur.next;}}if (head.val == key) {head = head.next;}}
public void clear();
清空链表,由于对象在没有被引用时,就会自动被回收,所以只需要将head置为空就行了
public void clear() {head = null;
}
🙉本篇文章到此结束,下篇文章将对LinkedList类进行讲解