### 链表进阶2:双向链表的构建
- [@TOC(链表进阶2:双向链表的构建) 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [1.创建链表节点;](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [2.头插法](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [2.尾插](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [3.中间位置插入](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [4.头删](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [5.尾删](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [6.按下标删除](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [7.按值删除](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [8.判断元素是否存在](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [循环遍历即可](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [9.返回存在元素的下标](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [循环遍历即可](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
- [10.toString方法](#链表进阶2:双向链表的构建 @TOC 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节; 1.创建链表节点; 2.头插法 2.尾插 3.中间位置插入 4.头删 5.尾删 6.按下标删除 7.按值删除 8.判断元素是否存在 循环遍历即可 9.返回存在元素的下标 循环遍历即可 10.toString方法 这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;)
这一版我没有引入尾部tail,后面会更新单向链表与双向链表引入尾部节点tail的章节;
1.创建链表节点;
这个链表节点与先前的单向链表相比,多了一个prev指向前一个节点;
java
class ListNode{
public int val;
public ListNode next;
public ListNode prev;
public ListNode(int val) {
this.val = val;
this.next = null;
this.prev = null;
}
}
2.头插法

1)首先要有头节点,判断是否为null,如果是,头节点指向新的节点;
2)新节点指向head,head的前一个节点prev指向新节点;
3)新节点变为头节点;
java
public ListNode head = null;
public void addFirst(int val){
ListNode newnode = new ListNode(val);
if(head == null){
head = newnode;
return ;
}
newnode.next = head;
head.prev = newnode;
head = newnode;
}
2.尾插
1)创建一个尾部节点,头节点;
2)当头节点为空时,全部指向新节点;
3)不为空,尾部插入,还要更新tail尾部
java
public void addLast(int val){
LinkedNode newnode = new LinkedNode(val);
if(head == null){
head = newnode;
return;
}
LinkedNode cur = head;
while(cur.next!=null){
cur = cur.next;
}
cur.next = newnode;
newnode.prev = cur;
}
3.中间位置插入
1)首先判断下标,我们需要写链表的尺寸大小;
2)下标为0或者最后调用前面的头插与尾插;
- 找出要插入的位置,开始插入
java
private int size(){
if(head==null){
return 0;
}
int size = 0;
for(LinkedNode cur = head;cur!=null;cur = cur.next){
size++;
}
return size;
}
public void add(int index,int val){
LinkedNode newnode = new LinkedNode(val);
int size = size();
if(index<0||index>size){
throw new IndexOutOfBoundsException("下标越界");
}
if(index==0){
addFirst(val);
return;
}
if(index==size){
addLast(val);
return;
}
LinkedNode cur = head;
for(int i = 0;i<index-1;i++){
cur = cur.next;
}
newnode.next = cur.next;
cur.next.prev = newnode;
cur.next = newnode;
newnode.prev = cur;
}
4.头删
1)判断是不是头节点head为空或者只有一个节点;
2)更新head
3)注意head的prev指向一定要为null
java
public void removeFirst(){
if(head==null){
return;
}
if(head.next==null){
head=null;
return;
}
head = head.next;
head.prev = null;
}
5.尾删
1)先判断是不是为空,或者只有一个节点
2)找到最后要删除的节点的上一个,让他指向空;
3)删除节点的prev也要指向空;
java
public void removeLast(){
if(head==null){
return;
}
if(head.next==null){
head=null;
return;
}
LinkedNode tail = head;
while(tail.next!=null){
tail = tail.next;
}
LinkedNode prev = tail.prev;
prev.next = null;
tail.prev=null;
}
6.按下标删除
1)首先还是要判断下标;
2)下标为0,为尾部特殊处理
3)循环遍历找到要删除的元素;
java
public void remove(int index){
int size = size();
if(index<0||index>=size){
throw new IndexOutOfBoundsException("下标越界");
}
if(index==0){
removeFirst();
return ;
}
if(index==size-1){
removeLast();
return;
}
LinkedNode toDelete = head;
for(int i = 0;i<index;i++){
toDelete = toDelete.next;
}
LinkedNode prev = toDelete.prev;
prev.next = toDelete.next;
toDelete.next.prev = prev;
toDelete.next = null;
toDelete.prev = null;
}
7.按值删除
1).通过循环遍历,找出要删除的值的节点,直接退出break;
2).判断循环结束是否找到,没找到直接返回;
3).最后考虑删除节点是否为尾部节点;
java
public void removeByvalue(int val){
if(head==null){
return;
}
if(head.val==val){
removeFirst();
return;
}
LinkedNode cur = head;
for(;cur!=null;cur = cur.next){
if(cur.val==val){
break;
}
}
if(cur == null){
return;
}
LinkedNode prev = cur.prev;
LinkedNode next = cur.next;
prev.next = cur.next;
if(next!=null){
next.prev = prev;
}
cur.next = null;
cur.prev = null;
}
8.判断元素是否存在
循环遍历即可
java
public boolean contains(int val) {
if (head == null) {
return false;
}
for (LinkedNode cur = head; cur != null;cur = cur.next ) {
if(cur.val==val){
return true;
}
}
return false;
}
9.返回存在元素的下标
循环遍历即可
java
public int indexOf(int val) {
int i = 0;
for (LinkedNode cur = head; cur != null; cur = cur.next, i++) {
if (cur.val == val) {
return i;
}
}
return -1;
}
10.toString方法
java
public String toString(){
if(head==null){
return null;
}
StringBuilder str = new StringBuilder();
for(LinkedNode cur = head;cur!=null;cur = cur.next){
str.append(cur.val+"");
if(cur.next!=null){
str.append(", ");
}
}
LinkedNode tail = head;
while(tail.next!=null){
tail = tail.next;
}
str.append("||");
for(LinkedNode cur = tail;cur!=null;cur = cur.prev){
str.append(cur.val+"");
if(cur.prev!=null){
str.append(", ");
}
}
return str.toString();
}
java
public class TestToString {
private static int pass = 0, fail = 0;
private static void check(String name, String expected, String actual) {
if (expected == null ? actual == null : expected.equals(actual)) {
pass++;
System.out.println("[PASS] " + name + " -> \"" + actual + "\"");
} else {
fail++;
System.out.println("[FAIL] " + name);
System.out.println(" expected: \"" + expected + "\"");
System.out.println(" actual : \"" + actual + "\"");
}
}
public static void main(String[] args) {
MyDLinkedList empty = new MyDLinkedList();
check("空链表", "", empty.toString());
MyDLinkedList one = new MyDLinkedList();
one.addFirst(1);
check("单节点 [1]", "1||1", one.toString());
MyDLinkedList multi = new MyDLinkedList();
multi.addLast(1);
multi.addLast(2);
multi.addLast(3);
check("多节点 [1,2,3]", "1, 2, 3||3, 2, 1", multi.toString());
MyDLinkedList afterHead = new MyDLinkedList();
afterHead.addLast(1);
afterHead.addLast(2);
afterHead.addLast(3);
afterHead.removeFirst();
check("头删后 [2,3]", "2, 3||3, 2", afterHead.toString());
MyDLinkedList afterTail = new MyDLinkedList();
afterTail.addLast(1);
afterTail.addLast(2);
afterTail.addLast(3);
afterTail.removeLast();
check("尾删后 [1,2]", "1, 2||2, 1", afterTail.toString());
System.out.println("\n通过 " + pass + ",失败 " + fail);
}
}
