本篇文章将对单链表进行讲解,模拟实现单链表中常见的方法
链表是一种逻辑结构上连续而物理结构上不一定连续的线性表,链表由一个一个节点组成:
每一个节点中都由数据域(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,接下来对上述方法进行模拟实现
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指向newNode
head = 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类进行讲解