链表 -- 单链表 的创建以及插入、删除的基本操作

  链表是一种线性表,但是不同于顺序表,它存储在不连续的内存空间上,可以更灵活的进行插入、删除的操作。

1 分类

1.1 单向链表 vs 双向链表

1.2 带傀儡节点的 vs 不带傀儡节点的

傀儡节点(dummy node):不存储实际使用的数据。
在这里插入图片描述

1.3 带环的 vs 不带环的

  所谓带环的就是最后一个节点的 next 不指向 null,而是指向链表中的某个元素,一旦链表是带环的,就没有任何一个节点是指向 null 的。例如这里的第五个节点,它的后继不是空而是第三个节点,next 引用存储的是第三个节点的地址。
在这里插入图片描述

2 创建单链表

2.1 节点定义

public class Node {
    public int val;
    public Node next = null;

    public Node(int val) {
        this.val = val;
    }

    @Override
    public String toString() {
        return "[" + val +"]";
    }
}

2.2 创建链表

public class Main {
    public static Node createList() {
        Node a= new Node(1);
        Node b= new Node(2);
        Node c= new Node(3);
        Node d= new Node(4);
        a.next = b;
        b.next = c;
        c.next = d;
        // 可以不写,因为Node方法将next默认设为null
        d.next = null;
        return a;
    }

    public static void main(String[] args) {
        Node head = createList();
    }
}

2MTkx,size_16,color_FFFFFF,t_70)
  方法结束后,a、b、c、d这四个局部变量就没了,但是因为方法返回第一个节点的地址给 head 了,然后后面的每一个节点都有前一个节点的 next 引用指向着,链表就还在,通过 head 就可以找到链表( 一个对象没有任何一个强引用指向,或者它不可达,就会被垃圾回收器回收 )。引用概念的理解对链表的理解很重要《Java笔记 (五)—— 引用类型》

3 单链表的遍历

public static void main(String[] args) {  
    Node head = createList();
    for (Node cur = head; cur != null ; cur=cur.next) {
        System.out.println(cur.val);
    }
}

3.1 链表的最后一个节点

Node head = createList();
Node cur = head;
// 注意空链表的情况,解引用前要保证它非空
// 如果是没有傀儡节点的链表,空链表就是 head = null
// 如果是有傀儡节点的链表,空链表就是 head.next = null
while(cur != null && cur.next != null) {
	cur = cur.next;
}
System.out.println(cur.val);

3.2 倒数第二个节点

Node cur = head;
while(cur != null 
&& cur.next != null 
&& cur.next.next != null) {
    cur = cur.next;
}
System.out.println(cur.val);

3.3 第 N 个节点

int N = 3;
Node cur = head;
for (int i = 1; i < N; i ++) {
    cur = cur.next;
}
System.out.println(cur.val);

3.4 倒数第 N 个节点

int count = 0;
int minusN = 1;
Node cur = head;
for (;cur != null;cur = cur.next) {
    count ++;
}
int plusN = count + 1 - minusN;
cur = head;
for (int i = 0; i < plusN; i++) {
    cur = cur.next;
}
System.out.println(plusN);

3.5 获取链表长度

int count = 0;
for (Node cur = head;cur!= null ; cur = cur.next) {
    count ++;
}
System.out.println(count);

3.6 查找链表上是否有某个元素

int toFind = 5;
Node cur = head;
for (;cur != null;cur = cur.next) {
    if(cur.val == toFind) {
        break;
    }
}
if (cur != null) {
    System.out.println("true");
} else {
    System.out.println("false");
}

4 插入操作

4.1 不带傀儡节点

2MTkx,size_16,color_FFFFFF,t_70)

public static void traversal(Node head) {
    for(Node cur = head;cur != null;cur = cur.next) {
        System.out.print(cur.val + " ");
    }
}

 // 1 插入到中间位置
	Node head = createLinkedList();
	Node one = head;
	//Node newNode = new Node(50);
	//newNode.next = one.next;
	//one.next = newNode;

 // 2 插入到最头部
	//Node newNode = new Node(70);
	//newNode.next = head;
	//head = newNode;
	//traversal(head);

// 3 插入到尾部
	Node newNode = new Node(90);
	Node prev = head;
	while (prev != null && prev.next != null) {
	    prev = prev.next;
	}
	prev.next = newNode;
	traversal(head);

4.2 带傀儡节点

 // 插入数据,插到头部、中间都一样
        Node head = createLinkedListWithDummy();
        Node newNode = new Node(110);
 // 1 和 2 之间
	//Node prev = head.next;
	//newNode.next = prev.next;
	//prev.next = newNode;
	//traversalWithDummy(head);

// 插入到头部
	Node prev = head;
	newNode.next = prev.next;
	prev.next = newNode;
	traversalWithDummy(head);

5 删除操作

在这里插入图片描述

5.1 无傀儡节点

// 1 按照值删除
    //   找到该值对应的位置
    //   同时找到前一个位置
public static void remove1(Node head,int val) {
	if (head == null) {
	    return head;
	}
	if (head.val == val) {
	    head = head.next;
	    return head;
	}
	Node prev = head;
	while (prev.next != null && prev.next.val == val) {
	    prev = prev.next;
	}
	if(prev.next == null) {
	System.out.println("未找到");
	    return null;
	}
	Node toDelete = prev.next;
    //   真正进行删除,toDelete 指向要被删除的节点
    //   prev.next 指的是要删除的节点
    //   prev 指的是要删除的前一个节点
    prev.next = toDelete.next;
    return head;

}


// 2 按照位置删除
//(基础做法,时间复杂度 O(N) )
public static Node remove2(Node head,Node toDelete) {
        // 时间复杂度 O(N)
        if (head == null) {
            return head;
        }
        if (head == toDelete) {
            head = head.next;
            return head;
        }
        Node prev = head;
        while ( prev.next != null && prev.next != toDelete) {
            prev = prev.next;
        }
        if (prev.next == null) {
        System.out.println("未找到");
            return null;
        }
	prev.next = toDelete.next;
	return head;	
// 按照位置删除 优化 (时间复杂度 O(1))
//     toDelete.val = toDelete.next.val;
//     toDelete.next = toDelete.next.next;
//     return head;
// 但如果是删除最后一个位置的节点,不能用这个方法
// 要用上一个方法
}


// 3 按照下标删除
// 其实就是给定第几个(从第 0 个开始)
public static Node remove3(Node head,int index) {
    // 按照下标删除,其实就是按照给定第几个
	if(index < 0 || index >= size(head)) {
	    return null;
	}
	if(index == 0) {
	    head = head.next;
	    return head;
	}
	Node prev = head;
	for (int i = 1; i < index; i++) {
	    prev = prev.next;
	}
	Node toDelete = prev.next;
	prev.next = toDelete.next;
	return head;
}
private static int size(Node head) {
    int size = 0;
    for(Node cur = head;cur != null;cur = cur.next) {
        size++;
    }
    System.out.println(size);
    return size;
}

// 4 删除尾部节点
public static Node removeTail(Node head) {
    if(head == null ){
        return null;
    }
    Node prev = head;
    while (prev.next != null && prev.next.next != null) {
        prev = prev.next;
    }
    prev.next = null;
    return head;
}

// 5 测试无傀儡节点的删除
//       head = remove1(head,1);
//       traversal(head);

//        int num = 1;
//        Node cur = head;
//        while (cur != null && cur.val != num) {
//                cur = cur.next;
//        }
//        head = remove2(head,cur);
//        traversal(head);

//        head = remove3(head,0);
//        traversal(head);

//	  head = removeTail(head);
//	  traversal(head);

注意:
  涉及到头节点的操作时,如果仅仅在方法中对形参 " head " 进行修改,在方法结束后,形参就会被销毁,实际的 " head " 并没有被改变。这时就要通过返回值把 " head " 的值返回回去。

5.2 有傀儡节点

// 1 按值删除
public static void removeWithDummy1(Node head,int val) {
    Node prev = head;
    while (prev.next != null && prev.next.val != val) {
        prev = prev.next;
    }
    Node toDelete = prev.next;
    prev.next = toDelete.next;
}

// 2 按位置删除
public static void removeWithDummy2(Node head,Node toDelete) {
    Node prev = head;
    while (prev.next != null && prev.next != toDelete) {
        prev = prev.next;
    }
    if (prev.next == null) {
        System.out.println("未找到,链表内容为:");
        return;
    }
    toDelete = prev.next;
    prev.next = toDelete.next;
}

// 3 按下标删除(从 0 开始)
public static void removeWithDummy3(Node head,int index) {
	Node prev = head;
	if(index < 0 || index > sizeWithDummy(head)) {
	    System.out.println("未找到,链表内容为:");
	    return;
	}
	for (int i = 0; i < index ; i++) {
	    prev = prev.next;
	}
	Node toDelete = prev.next;
	prev.next = toDelete.next;
}
public static int sizeWithDummy(Node head) {
    int size = 0;
    for(Node cur = head.next;cur != null;cur = cur.next) {
        size ++;
    }
    System.out.println(size);
    return size;
}

// 4 测试有傀儡节点的删除
//	Node head = createLinkedListWithDummy();
//	removeWithDummy1(head,5);
//	traversalWithDummy(head);

//	int num = 6;
//	Node cur = head.next;
//	while (cur != null && cur.val != num) {
// 	cur = cur.next;
//	}
//	removeWithDummy2(head,cur);
//	traversalWithDummy(head);

//	removeWithDummy3(head,0);
//	traversalWithDummy(head);

  相比之下有傀儡节点的比没有傀儡节点的代码较简单,因为有傀儡节点就不用改变" head ",避免一些容易出错的地方。

6 实现一个自己的双向链表

class ListNode {
    int val;
    ListNode prev = null;
    ListNode next = null;

    public ListNode(int val) {
        this.val = val;
    }
}

// 实现一个双向链表
public class MyLinkedList {
    // 记录头、尾节点的位置
    private ListNode head;
    private ListNode tail;
    // 记录长度,空间换时间
    private int length = 0;

    public MyLinkedList() {
        this.head = null;
        this.tail = null;
    }

    public int length(){
        return this.length;
    }

    // 1. 插入操作
    // 1.1 头插
    public void addFirst(int val){
        ListNode newNode = new ListNode(val);
        if (head == null) {
            head = newNode;
            tail = newNode;
        } else {
            newNode.next = head;
            head.prev = newNode;
            head = newNode;
        }
        length ++;

    }

    // 1.2 尾插
    public void addLast(int val) {
        ListNode newNode = new ListNode(val);
        if (head == null) {
            head = newNode;
            tail = newNode;
        } else {
            tail.next = newNode;
            newNode.prev = tail;
            tail = tail.next;
        }
        length ++;
    }

    // 1.3 指定位置插入
    public void add(int index,int val) {
        if (index < 0 || index >= length) {
            return;
        }
        if (index == 0) {
            addFirst(val);
        }
        if (index == length) {
            addLast(val);
        }
        ListNode newNode = new ListNode(val);
        ListNode nextNode = getNode(index);
        ListNode prevNode = getNode(index - 1);
        newNode.next = nextNode;
        nextNode.prev = newNode;

        newNode.prev = prevNode;
        prevNode.next = newNode;
        length ++;
    }

    // 2. 删除操作
    // 2.1 按下标删除
    public void removeByIndex(int index) {
        if (index < 0 || index > length) {
            return ;
        }
        if (index ==0) {
            removeFirst();
            return;
        }
        if (index == length - 1) {
            removeLast();
            return;
        }
        ListNode toDelete = getNode(index);
        ListNode prevNode = toDelete.prev;
        ListNode nextNode = toDelete.next;
        prevNode.next = nextNode;
        nextNode.prev = prevNode;
        length --;
    }

    // 2.2 按值删除
    public void removeByValue(int val) {
        int index = indexOf(val);
        if (index == -1){
            return ;
        }
        removeByIndex(index);
    }

    // 2.3 头删
    public void removeFirst() {
        if (head == null){
            return ;
        }
        if (head.next == null) {
            head = null;
            tail = null;
            length = 0;
            return ;
        }
        ListNode nextNode = head.next;
        nextNode.prev = null;
        head = nextNode;
        length --;
    }
    // 2.4 尾删
    public void removeLast() {
        if (head == null){
            return ;
        }
        if (head.next == null) {
            head = null;
            tail = null;
            length = 0;
            return ;
        }
        ListNode prevNode = tail.prev;
        prevNode.next = null;
        tail = prevNode;
        length --;
    }
    // 3. 查找
    // 3.1 给定下标去找节点
    public ListNode getNode(int index) {
        if (index < 0 || index >= length) {
            return null;
        }
        ListNode cur = head;
        for (int i = 0; i <= index; i++) {
            cur = cur.next;
        }
        return cur;
    }

    // 3.2 给定下标去找值
    public int indexOf(int index) {
        return getNode(index).val;

    }

    //3.3 给定值去找下标
    public int getVal(int val) {
        ListNode cur = head;
        for (int i = 0; i < length; i++) {
            if (cur.val == val) {
                return i;
            }
            cur = cur.next;
        }
        return -1;
    }

    // 4. 修改
    public void set(int index,int val) {
        ListNode listNode = getNode(index);
        listNode.val = val;
    }
}
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值