讲解链表之前你必须知道的知识概要:
Java采用面向对象设计,如果我们把一个引用first赋给current(current = first),那么会变成这个样子:
可以看到,赋值后current和first存储了相同的引用值,因而指向同一个对象。这是链表中各种引用变量指来指去的基础,务必搞清楚这一点,避免绕晕!
1.单链表
1)结点定义:
public class ListNode { public int val; public ListNode next; //这里的val是当前节点的值,next指向下一个节点。 因为两个变量都是public的,所以创建对象ListNode listNode = new ListNode(1)后,能直接使用listNode.val和listNode.next 来操作. 虽然违背了面向对象的设计要求,但是更精简,在算法题中使用广泛。 public ListNode(int val) { this.val = val; next = null;//这句不写也可 }
}
所谓单链表就是每个节点有数据域val和指向同类型对象的指针next, 多个节点之间通过引用变量(next)相互连接,最后一个节点的next指针值为null。
2)单链表长度计算:
public static int getListLength(ListNode head){ int length = 0; ListNode node = head; while (node != null){ //只要指向的是一个非空节点,就将长度++ length++; node = node.next; } return length; }
3)增加元素
首先我们要清楚地知道,为了避免前驱结点的丢失,应该用node.next来判断是否到了插入位置,让变量node指向插入位置的前驱节点,然后在进行插入
需要注意的是,插入节点时需要考虑两种情况:第一种是插入在链表的表头位置(即position==1),这个时候插入位置没有前驱节点;第二种是插入在非表头位置,有前驱节点。
public static ListNode insertNode(ListNode head,ListNode nodeInsert, int position){ if (head == null){ return nodeInsert; //如果链表是空的话,那么插入的节点就作为链表的头结点,返回插入的节点 } if (position < 1 || position > getListLength(head) + 1){ //因为一共有length个节点,可以在尾部插入,那么位置就最大可以是length+1,所以position做了上面的限制 System.out.println("插入位置越界"); return head; } if (position == 1){ nodeInsert.next = head; return nodeInsert;//这时nodeInsert就是头结点 } ListNode node = head; int count = 1; while (count < position - 1){ node = node.next; count++; } 我知道while循环这一段会有人纠结,今天来详细解释一下为什么要用count < position - 1作为控制条件。 我们知道,刚开始count==1,而node也==head,也就是说node指向了链表的第一个结点。注意,第一个结点,count==1.然后每一次node = node.next,也就是指向下一个节点的时候,count总会同步的++。 然后我们就有了一个很好的发现:node指向第几个节点,count就一定等于几。 那么再看这个判断条件就很清晰了。我们是要在第position个位置插入节点,因此我们需要找到它的前驱节点,第position-1个节点。 while循环在count < position - 1时会不断执行,各变量都是整数,那么什么时候while循环结束呢?答案呼之欲出:count == position - 1的时候循环终止。 再参考我们前面的结论,node总指向链表的第count个节点。这也就是说,当node指向第position-1个节点的时候循环终止。 此时跳出循环,node就指向了前驱节点。接下来的代码就很好懂了,让新节点的next和前驱结点的next都指向后继结点,再让前驱结点的next指向新节点。插入完成。
nodeInsert.next = node.next; node.next = nodeInsert; return head;//尾结点插入情况也考虑在内了 }
4)删除节点(与插入节点类似,不再赘述)
/** * * @param head 链表头结点 * @param position 删除元素位置(从1开始计算) * @return 删除操作之后的链表头结点 */ public static ListNode deleteNode(ListNode head, int position){ if (head == null){ return null; } if (position < 1 || position > getListLength(head)){//这里大于head就越界了而不是head+1,因为链表只有head个元素可删除 System.out.println("删除节点的索引越界"); return head; }
//正常删除所用的node.next = node.next.next中,删除的节点至少也是第二个了,因此需要考虑删除第一个的情况 if (position == 1){ return head.next; } int count = 1;//count仍然表示node所指第几个节点 ListNode node = head; //要删除第p个节点,首先node要指向第p-1个节点 while (count < position - 1){ node = node.next; count++; } node.next = node.next.next; return head; }
2.双向链表
构造定义双向链表节点,注意区分节点和双向链表对象。节点有数据域和两个指针,而一个双向链表对象只有头指针和尾指针两个字段
public class DoubleNode {//双向链表节点定义 public int data; public DoubleNode prev; public DoubleNode next; public DoubleNode(int data) { this.data = data; } }
public class DoublyLinkedList {//双向链表类定义 public DoubleNode first; public DoubleNode last;
}
1)双向链表的增加元素(以下代码在DoublyLinkedList类中)
public boolean isEmpty(){ return first == null;//如果头指针为空证明双向链表为空 } //(1)插入链表最前面 public void insertFirst(int data){ DoubleNode newNode = new DoubleNode(data); if (isEmpty()){ //如果双向链表为空的话 first = newNode; last = newNode; }else { first.prev = newNode;//原先的第一节点成了第二个,让他的prev指向newNode newNode.next = first; first = newNode;//头结点已经发生改变,因而改变first引用的对象 } }
//(2)插入链表尾部
public void insertLast(int data){ DoubleNode newNode = new DoubleNode(data); if (isEmpty()){ first = newNode; last = newNode; }else { last.next = newNode;//原来的尾结点成了现在的倒数第二节点,让它的next指向newNode newNode.prev = last; last = newNode;//改变尾结点指向 } }
//(3)插入值为key的节点后面
public void insertAfter(int key,int data){ DoubleNode newNode = new DoubleNode(data); DoubleNode node = first; while (node != null && node.data != key){ node = node.next; } //最后结束循环时,要么node.data == key,即node指向了值为Key的结点,要么没找到值为key的节点,即node == null //node == null又分两种情况,第一种是链表有元素但没找到,node是表尾的null;第二种是链表本身就为空所以没找到 if (node == null){ if (isEmpty()) {//本身为空 first = newNode; last = newNode; }else {//非空但没有key值的节点,这个时候就在尾部插入该data节点 last.next = newNode; newNode.prev = last; last = newNode; } }else { //else表明node指向了key节点,应该插在key节点后面,这时问题要讨论情况,需要考虑key节点是否有后继结点,如果key节点是最后一个节点就没有后继结点 if (node.next == null){//node.next == null,即key节点没有后继结点时,等同于插入到表尾 insertLast(data); }else { node.next.prev = newNode; newNode.next = node.next; node.next = newNode; newNode.prev = node;
} } }
2)双向链表删除元素:
public DoubleNode deleteFirst(){ if (isEmpty()){ throw new RuntimeException("空链表不支持删除"); } //不为空时分两种情况:第一种是删除之后双向链表为空,即双向链表只有一个结点;第二种是双向链表有至少两个结点 DoubleNode temp = first;//temp保存first节点便于返回 if (first.next == null){ first = null; last = null; return temp; } first.next.prev = null; first = first.next;//相同引用值指向同一对象 return temp; } public DoubleNode deleteLast(){ //从尾部删除节点 if (isEmpty()){ throw new RuntimeException("空链表不支持删除"); } DoubleNode temp = last;//同上,保存尾部节点 if (last.prev == null){ //如果链表只有一个节点,删除后就成了空表,first,last均为null first = null; last = null; return temp; } last.prev.next = null; last = last.prev; return temp; } public DoubleNode deleteKey(int key){ //删除对应值为key的结点 DoubleNode current = first; while (current != null && current.data != key){ current = current.next; } //找到值为key的结点,如果没有值为key的节点或者链表为空那current就为null,也返回null if (current == null){ //没找到,返回null return null; }else { //找到了,current.data == key,current结点就是key结点,分三种情况讨论: //如果key节点是第一个结点 if (current == first){ deleteFirst(); }else if (current == last){ deleteLast(); }else { //key结点是中间节点时 current.prev.next = current.next; current.next.prev = current.prev;
} } return current;//返回被删除节点 }
单双链表的教程到此结束了。如有启发,欢迎点个赞鼓励鼓励小萌新。