单链表的基本操作(17种操作方式)-Java实现

  

包含单链表的各种插入、包含和删除关键字key、判断是否有环、是否是回文结构、环的相遇点以及环的入口点、和合并有序的两个单链表等等

目录

1、单链表的头、尾插法

 2、得到单链表的长度

 3、单链表任意位置插入

4、清空单链表

5、单链表中是否包含key关键字

6、删除第一次出现key的关键字的节点

7、删除所有值为key的节点

8、反向链表

9、找到链表的中间节点

10、找到倒数第K个节点   

11、实现链表左边小于x值,右边大于等于x值(或左边大于x值,右边小于等于x值)

12、删除重复的节点

13、判断链表是否是回文结构

14、判定链表是否有环

 15、两个链表相遇的节点

16、链表中环的入口节点

17、合并有序两个有序的链表


1、单链表的头、尾插法

头插法

        头插法分为第一次插入和不是第一次插入

①第一次插入:直接将插入的节点node  赋值给头节点head

②不是第一次插入:将头节点head 连到插入节点 node.next

 尾插法

      尾插法也是分为第一次插入和不是第一次插入

①第一次插入:直接将插入的节点node  赋值给头节点head,这跟头插法的第一次插入是一样得

②不是第一次插入:定义一个新的节点cur指向头节点,

                                遍历cur节点,找到链表的的最后一个节点

                                最后再使插入的节点node指向cur.next

/**
 * Created with IntelliJ IDEA.
 * Description:
 *单链表: 由节点组成
 *每一个节点都是一个类  有data next
 *
 *
 * @User:Mingaho
 * @Date:2021/04/09
 * @Time:21:14
 */
class Node {
    public int data;
    public Node next;

    public Node(int data) {
        this.data = data;
        this.next = null;
    }
}


public class MyLinkedList {

    public Node head; //保存单链表的头节点的引用 代表的是整个链表的头 所以定义在这个地方

    //头插法
    public void addFirst(int data) {
        Node node = new Node(data);
        if(this.head == null) {
            //第一次插入节点  第一个节点
            this.head = node;
            return;
        }
        node.next = this.head;
        this.head = node;
    }


    //尾插法
    public void addLast(int data) {
        Node node = new Node(data);
        if(this.head == null) {
            this.head = node;
            return;
        }
        Node cur = this.head;
        while(cur.next != null) {
            cur = cur.next;
        }
        cur.next = node;
        
    }

    //打印单链表
    public void display() {
        Node cur = this.head;
        while(cur != null) {
            System.out.print(cur.data + " ");
            cur = cur.next;
        }
        System.out.println();
    }


}

 2、得到单链表的长度

遍历整个链表,当cur不为空时,使计数count++

//得到单链表的长度
    public int size() {
        int count = 0;
        Node cur = this.head;
        while (cur != null) {
            cur = cur.next;
            count++;
        }
        return count;
    }

 3、单链表任意位置插入

①利用searchIndex函数实现index的合法性检查和若合法找到index的前驱节点

②准备插入时,有两种情况:一个是index=0 和 index= size()时,可以利用头插和尾插实现插入

③其他的情况,在找到index的前驱位置后,插入要插入的节点node,就实现了index位置的插入

    //找index的前驱
    private Node searchIndex(int index) {
        //先对index进行合法性检查
        if(index < 0 || index > this.size()) {
            throw new RuntimeException("index's location is not right.");
        }
        Node cur = this.head; 

        //找到index的前驱,只需走index - 1步
        while (index > 1) {
            cur = cur.next;
            index--;
        }
        return cur;
    }

    //在任意位置插入 第一个数据节点为0号下标
    public void addIndex(int index, int data) {
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == this.size()) {
            addLast(data);
            return;
        }

        //先找到index的前一个结点的地址
        Node cur = searchIndex(index);

        //进行插入
        Node node = new Node(data);
        node.next = cur.next;
        cur.next = node;
    }

4、清空单链表

    //清空
    public void clear() {
        this.head = null;
    }

5、单链表中是否包含key关键字

遍历整个单链表是否包含key节点

public boolean contains(int key) {
        Node cur = this.head;
        while(cur != null) {
            if(cur.data == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

  

6、删除第一次出现key的关键字的节点

//找删除第一次出现关键字的前驱
    private Node searchPrev(int key) {
        Node prev = this.head;
        while(prev.next != null) {
            if(prev.next.data == key) {
                break;   //return prev;
            }else {
                prev = prev.next;
            }
        }
        return prev;     //return null;
    }

//删除第一次出现key的关键字的节点
    public void remove(int key) {
        if(this.head == null) {   //链表为空 直接return
            return;
        }

        //key如果是头节点 头节点直接指向下一个节点
        if(this.head.data == key) {   
            this.head = this.head.next;
            return;
        }
        
        Node prev = searchPrev(key);
        if(prev.next == null) {
            System.out.println("is not exists the key.");
            return;
        }
        Node del = prev.next;
        prev.next = del.next;

    }

 7、删除所有值为key的节点

//删除所有值为key的节点
    public void removeAllKey(int key) {
        if(this.head == null) {
            return;
        }
        Node prev = this.head;
        Node cur = this.head.next;  //代表要删除的节点
        while(cur != null) {
            if(cur.data == key) {
                prev.next = cur.next;
                cur = cur.next;
            }else {
                prev = cur;
                cur = cur.next;
            }
        }
        if(this.head.data == key) {
            this.head = this.head.next;
        }
    }

8、反向链表

//反向链表
    public Node reverseList() {
        Node prev = null;
        Node cur = this.head;
        Node newHead = null;
        while(cur != null) {
            Node curNext = cur.next;
            if(curNext == null) {
                newHead = cur;
            }
            cur.next = prev;
            prev = cur;
            cur = curNext;
        }
        return newHead;
    }

下面是我牛客网写的另一种反转 

import java.util.*;
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        Stack<ListNode> stack = new Stack<>();
        if(head == null) {
            return null;
        }
        ListNode cur = head;      //定义一个cur节点 指向头节点
        while (cur != null) {      //直至链表节点完全压入栈中
            stack.push(cur);
            cur = cur.next;
        }
        head.next = null;          //原来的头节点置为null
        ListNode ret = stack.pop();  //ret为反向链表的头节点 记录下来,用作链表的返回
        ListNode cur1 = ret;         //node为剩余栈中的节点
        while (!stack.isEmpty()) {    //直至栈为空 完全pop()出来为止
            cur1.next = stack.pop();
            cur1 = cur1.next;
        }
        return ret;
    }
}

9、找到链表的中间节点

定义快、慢的节点,fast一次走两步,slow一次走一步

//找到该链表的中间节点
    public Node findMinIndexKey() {
        Node fast = this.head;
        Node slow = this.head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return  slow;
    }

10、找到倒数第K个节点   

先让fast走K-1步,为什么呢?你品,你细品 可以画图看看

然后fast和slow一次走一步,直到不满足fast.next!=null时,退出循环

//找到倒数第K个节点   
    public Node FindKNode(int K) {
        if(this.head == null) {
            return null;
        }
        if(K <= 0) {
            System.out.println("K is not right.");
            return null;
        }
        Node fast = this.head;
        Node slow = this.head;
        while (K - 1 > 0) {
            if(fast.next != null) {
                fast = fast.next;
                K--;
            }else {
                System.out.println("没有这个节点");
                return null;
            }
        }
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

11、实现链表左边小于x值,右边大于等于x值(或左边大于x值,右边小于等于x值)

定义两个链表 链表1的头节点为begins  尾部节点为beginEnd, 链表2的头节点为as,尾节点为ae,刚开始begins 和beginEnd指向相同位置      as和ae指向相同位置

定义cur指向原先的链表头

小于x值的节点放入链表1中,然后beginEnd指向begins.next

大于x值的节点放入链表2中,然后ae指向as.next

cur遍历完后,再将链表1 和链表2进行拼接,最后返回链表1 的头节点begins就好了

//给定一个x,  x的左边或右边 都大于等于/小于等于  x  
    public Node partition(int x) {
        Node begins = null;
        Node beginEnd = null;
        Node as = null;
        Node ae = null;
        Node cur = this.head;
        while(cur != null) {    //没有遍历完
            if(cur.data < x) {            //小于x时的插入

                if(begins != null) {      //第一段分为第一次插入还是第二次
                    beginEnd.next = cur;
                    beginEnd = beginEnd.next;
                }else {
                    begins = cur;
                    beginEnd = cur;
                } 
            }else {                       //大于x时的插入
                if(as != null) {          //第二段分为第一次插入还是第二次
                    ae.next = cur;
                    ae = ae.next;
                }else {
                    as = cur;
                    ae = cur;
                }
            }
            cur = cur.next;
        }
        //1、如果bs为空 返回as
        if(begins == null) {
            return as;
        }
        //2、如果bs不为空  需要拼接
            beginEnd.next = as;
        //3、如果ae不为空  ae.next置为null
        if(ae != null) {
            ae.next = null;
        }
        return begins;
    }

12、删除重复的节点

//删除重复的节点 1->2->2->3->3->4->5   1->4->5   
    public Node deleteDuplication() {
        Node cur = this.head;
        Node newHead = new Node(-1);
        Node temp = newHead;
        while(cur != null) {
            if(cur.next != null && cur.data != cur.next.data) {
                while(cur.next != null && cur.data == cur.next.data) {
                    cur = cur.next;
                }
                cur = cur.next;
            }else {
                temp.next = cur.next;
                temp = temp.next;
                cur = cur.next;
            }
        }
        temp.next = null;
        return newHead;
    }

13、判断链表是否是回文结构

1、找到中间节点

2、反转单链表的后半部分,slow肯定是在中间节点

3、开始一个从头走一个从尾走

以下是基于链表长度是奇数的情况;偶数的话,在第3步加一个if判断就好了

//判断链表是否是回文结构           节点为奇数的时候12321
    public boolean chkPalindrome() {
        if(this.head == null) {  //单链表为空
            return  false;
        }
        if(this.head.next == null) {//只有一个节点
            return true;
        }
        //1、找到中间节点
        Node fast = this.head;
        Node slow = this.head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //2、反转单链表的后半部分,slow肯定是在中间节点
        Node cur = slow.next;
        while(cur != null) {
            Node curNext = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }//完了之后slow是最后一个节点了

        //3、开始一个从头走一个从尾走
        while(slow != this.head) {
            if(slow.data != this.head.data) {
                return false;
            }

            if(this.head.next == slow) {  //判断偶数的情况
                return true;
            }
            
            slow = slow.next;
            this.head = this.head.next;
        }
        return true;
    }

14、判定链表是否有环

定义快、慢节点fast  slow,fast一次走两步,slow一次走一步,当有fast = slow时,链表便有环

//判定链表是否有环
    public boolean isHaveCircle() {
        Node fast = this.head;
        Node slow = this.head;
        if (fast == null) {
            return false;
        }
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (slow == fast) {
                return true;   //break;
            }
        }
        //if(fast == null || fast.next == null) {
              //return null;
        //}
        return false;
    }

 15、两个链表相遇的节点

//创建两个相交的链表
    public static void createCut(Node headA, Node headB) {
        headA.next =headB.next.next;
    }

    //两个链表相遇的节点
    public static Node getIntersectionNode(Node headA, Node headB) {
        //1、求长度 走差值步
        int lenA = 0;
        int lenB = 0;
        Node pl = headA;
        Node ps = headB;
        while(pl != null) {
            lenA++;
            pl = pl.next;            //求链表A的长度
        }
        while(ps != null) {
            lenB++;
            ps = ps.next;            //求链表B的长度
        }
        pl = headA;                   //重新指向各自头节点,为走差值做准备
        ps = headB;
        int len = lenA - lenB;
        if(len < 0) {
            pl = headB;
            ps = headA;
            len = lenB - lenA;
        }
        //pl为最长的链表
        for(int i = 0; i < len; i++) {
            pl = pl.next;
        }
        //2、pl ps 在同一起跑线上
        while(ps != pl && pl != null && ps != null) {
            pl = pl.next;
            ps = ps.next;
        }
        if(pl == ps && pl != null) {
            return pl;
        }
        return null;
    }

16、链表中环的入口节点

先判断是否有环,也是利用fast、slow快慢节点判断是否有环

经过数学推导:环外的X长度 =  环内的X长度,推导见下图

在有环的情况下 让slow=this.head,然后fast和slow再一步一步走,直到相遇

//链表中环的入口节点
    public Node detectCircle() {
        Node fast = this.head;
        Node slow = this.head;
        if(fast == null) {
            return null;
        }
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(slow == fast) {
                break;
            }
        }
        if(fast == null || fast.next == null) {
            return null;
        }
                     //以上判断链表有环,但不能判断入口节点在哪里

        slow = this.head;   //有环的情况下 让slow=this.head
        while (fast != slow) {  //相遇时既是环的入口点
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

17、合并有序两个有序的链表

//合并有序两个有序的链表
    public static Node mergeTwoLists(Node headA, Node headB) {
        Node newHead = new Node(-1);//定义一个虚拟的头节点,
        Node temp = newHead;//定义一个temp节点指向newHead
        while (headA != null && headB != null) {//循环条件
            if(headA.data < headB.data) {
                temp.next = headA;
                temp = temp.next;
                headA = headA.next;
            }else {
                temp.next = headB;
                temp = temp.next;
                headB = headB.next;
            }
        }
        if(headA == null) {//以下两个if判断哪个为空了,
            temp.next = headB;//就将另一个链表接到temp
        }
        if(headB == null) {
            temp.next = headA;
        }
        return newHead.next;//newHead的头节点为-1,所以返回newHead.next
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值