数据结构-链表

目录

对于ArrayList的思考

链表

链表的种类

链表的实现

链表面试题

链表移除元素

反转一个单链表

链表的中间结点

链表中的倒数第k个结点

合并两个有序链表

链表分割

链表的回文结构

 相交链表

环形链表

LinkedList的模拟实现

LinkedList的构造方法

LinkedList的常用方法

ArrayList和LinkedList的区别 


对于ArrayList的思考

1.ArrayList底层使用的是连续的空间,任意位置插入或者删除元素的时候,都需要挪动元素.

2.扩容可能会造成空间的浪费.比如我就需要十一个元素,那么放第十一个元素的时候就需要扩容到15个空间,就会浪费四个空间.

所以ArrayList不适合使用在频繁的插入或者删除的场景,适合给定下标位置查找元素(随机访问),此时时间复杂度可以达到O(1).

解决上述问题,我们可以使用另一种数据结构-链表.链表可以做到随用随取和插入元素是不需要挪动元素的优点.


链表

将数据元素通过结点进行存储,然后使用一个叫做next的引用将这些结点串联起来,这样的结构我们就叫做链表.

数据结构-链表_第1张图片

注意:

  • 1.链式结构在逻辑上是连续的,但是在物理上不一定会连续.
  • 2.现实中的结点一般都是从堆上申请出来的.
  • 3.从堆上申请空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续.

链表的种类

1.单向或者双向

数据结构-链表_第2张图片

2.带头或者不带头

数据结构-链表_第3张图片

3.循环或者非循环

数据结构-链表_第4张图片

上述三种组合起来,一共有八种链表的结构.但是我们主要掌握无头单向非循环链表和无头双向非循环链表这两种.

带头结点的链表中的头节点的数据域是无意义的,并且它永远标志这这个链表的头,头节点的位置是一直不变的.比如要往这个链表中的第一个位置插入元素,插入的位置是在头结点之后的第一个位置,而不是头节点之前.

循环链表就是最后一个结点的指针域不在是null而是指向第一个结点(如果带头节点指向的是头节点之后的那个结点).

双向链表的结点还会多一个指针域指向此结点的前一个结点.


链表的实现

创建一个无头单向非循环的链表.

public class MySingleList {

  
    static class ListNode {
        public int val;
        public ListNode next;

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

    public ListNode head;//不初始化了 默认就是null

    public void createList() {
        ListNode listNode1 = new ListNode(12);
        ListNode listNode2 = new ListNode(23);
        ListNode listNode3 = new ListNode(34);
        ListNode listNode4 = new ListNode(45);
        ListNode listNode5 = new ListNode(56);
        listNode1.next = listNode2;
        listNode2.next = listNode3;
        listNode3.next = listNode4;
        listNode4.next = listNode5;
        this.head = listNode1;
    }

    /**
     * 打印链表里面的数据,默认从头开始打印
     */
    public void display() {
        ListNode cur = this.head;
        while (cur != null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

    /**
     * 从指定节点newHead开始打印链表
     * @param newHead
     */
    public void display(ListNode newHead) {
        ListNode cur = newHead;
        while (cur != null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

    public void display2(ListNode head) {
        if(head == null) {
            return;
        }
        if(head.next == null) {
            System.out.print(head.val+" ");
            return;
        }
        display2(head.next);
        System.out.print(head.val+" ");
    }

    

    //得到单链表的长度
    public int size(){
        int count = 0;
        ListNode cur = this.head;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        ListNode cur = this.head;
        while (cur != null) {
            if(cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    //头插法
    public void addFirst(int data){
        ListNode node = new ListNode(data);
        node.next = head;
        head = node;
    }

    //尾插法
    public void addLast(int data){
        ListNode node = new ListNode(data);
        ListNode cur = this.head;
        if(cur == null) {
            this.head = node;
        }else {
            while (cur.next != null) {
                cur = cur.next;
            }
            //cur已经是尾巴节点了
            cur.next = node;
        }
    }

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        if(index < 0 || index > size()) {
            System.out.println("index位置不合法!");
        }
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == size()) {
            addLast(data);
            return;
        }
        //1、先走index-1步,找到cur
        ListNode cur = findIndexSubOne(index);
        ListNode node = new ListNode(data);
        //2、修改指向
        node.next = cur.next;
        cur.next = node;
    }

    private ListNode findIndexSubOne(int index) {
        ListNode cur = this.head;
        while (index-1 != 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }

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

        ListNode cur = findPrevOfKey(key);
        if(cur == null) {
            System.out.println("没有你要删除的数字!");
            return;
        }
        ListNode del = cur.next;
        cur.next = del.next;
    }

    private ListNode findPrevOfKey(int key) {
        ListNode cur = this.head;
        while (cur.next != null) {
            if(cur.next.val == key){
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }

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

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

链表面试题

链表移除元素

数据结构-链表_第5张图片

class Solution {

    public ListNode removeElements(ListNode head, int val) {

        //对于链表为空,直接返回null

        if(head == null){

            return null;

        }

        //遍历整个链表

        ListNode cur = head.next;

        ListNode prev = head;

        while(cur != null){

            //当遇到要删除的结点就删除

            if(cur.val == val){

                prev.next = cur.next;

                cur = cur.next;

            }else{

                //不是要删除的结点就往下遍历

            prev = cur;

            cur = cur.next;

            }

        }

        //上述遍历走完之后,第一个结点要不要删除没有判断

        //所以在对第一个结点进行判断

        if(head.val == val){

            head = head.next;

        }

        return head;

    }

}


反转一个单链表

数据结构-链表_第6张图片 

class Solution {

    public ListNode reverseList(ListNode head) {

        //对后面的结点采用头插法即可完成对链表的反转

        if(head == null){

            return null;

        }

        ListNode cur = head.next;

        //对头节点的next进行置空

        //因为这个结点之后是反转后的尾结点

        head.next = null;

        while(cur != null){

            //进行头插

            ListNode curNext = cur.next;

            cur.next = head;

            head = cur;

            cur = curNext;

        }

        return head;

    }

}

 


链表的中间结点

数据结构-链表_第7张图片

 

class Solution {

    public ListNode middleNode(ListNode head) {

        //利用快慢指针,找到中间结点

        //fast一次走两步

        //slow一次走一步

        //当fast为空或者fast.next为空的时候

        //此时slow刚好在中间结点的位置

        ListNode fast = head;

        ListNode slow = head;

        //注意循环条件的顺序不能变

        //一旦fast.next!=null在前,fast!=null在后,就有可能会出现空指针异常

        while(fast!=null && fast.next!=null){

            fast = fast.next.next;

            slow = slow.next;

        }

        return slow;

    }

}


链表中的倒数第k个结点

数据结构-链表_第8张图片

public class Solution {

    public ListNode FindKthToTail(ListNode head,int k) {

        //利用快慢指针

        //先让fast走k-1步

        //在让fast和slow同速走,直到fast.next==null

        if(k<=0 || head == null){

            //判断k的合法性

            return null;

        }

        ListNode fast = head;

        ListNode slow = head;

        //fast先走k-1步

        while(k-1!=0){

            fast = fast.next;

         //if条件要写到 fast = fast.next;后

        //否则就可能会空指针异常

        //{1,2,3,4,5} 6

        //最后的一次fast是null

        //要是写到前面最后一次就没有判断

        //while循环条件里的fast.next就会空指针异常了       

            if(fast == null){

                //k值过大

                return null;

            }

            k--;

        }

        while(fast.next!=null){

            fast = fast.next;

            slow = slow.next;

        }

        return slow;

    }

}

 


合并两个有序链表

数据结构-链表_第9张图片

class Solution {

    public ListNode mergeTwoLists(ListNode head1, ListNode head2) {

            //创建一个虚拟头结点

            //比较两个结点大小,小的就放在虚拟头结点的链表中

            if(head1==null && head2 == null){

                return null;

            }

            if(head1 == null){

                return head2;

            }

            if(head2 == null){

                return head1;

            }

            ListNode newHead = new ListNode(-1);

            //创建一个结点始终指向链表的尾部

            ListNode tail = newHead;

            while(head1!=null && head2!=null){

                if(head1.val 

                    tail.next = head1;

                    tail = head1;

                    head1 = head1.next;

                }else{

                    tail.next = head2;

                    tail = head2;

                    head2 = head2.next;

                }

            }

            if(head1!=null){

                tail.next = head1;

            }

            if(head2!=null){

                tail.next = head2;

            }

            return newHead.next;

    }

}

 


链表分割

数据结构-链表_第10张图片

public class Partition {

    public ListNode partition(ListNode pHead, int x) {

        //创建两段链表

        //小于x的放在第一段,大于等于x的放在第二段,最后将两段连起来

        ListNode bs = null;

        ListNode be = null;

        ListNode as = null;

        ListNode ae = null;

        //遍历链表

        ListNode cur = pHead;

        while(cur != null){

            if(cur.val < x){

                //插入第一段

                //第一次插入

                if(bs == null){

                    bs = cur;

                    be = cur;

                    cur = cur.next;

                }else{

                    //不是第一次插入

                    be.next = cur;

                    be = be.next;

                    cur =  cur.next;

                }

            }else{

                //插入第二段

                //第一次插入

                if(as == null){

                    as = cur;

                    ae = cur;

                    cur = cur.next;

                }else{

                    //不是第一次插入

                    ae.next = cur;

                    ae = ae.next;

                    cur =cur.next;

                }

            }

        }

        //对于第一段为空的处理

        if(bs == null){

            return as;

        }

        //对于第二段的最后一个结点

        //可能不是原链表的最后一个结点的处理

        //要把第二段的最后一个结点的next手动置空

        if(as != null){

            ae.next = null;

        }

        be.next = as;

        return bs;

    }

}


链表的回文结构

数据结构-链表_第11张图片 

public class PalindromeList {

    public boolean chkPalindrome(ListNode head) {

        // write code here

        //链表为空的时候

        if(head==null){

            return false;

        }

        if(head.next== null){

            return true;

        }

        //利用快慢指针找到链表的中间

        ListNode fast=head;

        ListNode slow=head;

        while(fast!=null&&fast.next!=null){

            fast=fast.next.next;

            slow=slow.next;

        }

        //开始链表的反转

        ListNode cur=slow.next;

        while(cur!=null){

            ListNode curNext=cur.next;

            cur.next=slow;

            slow=cur;

            cur=curNext;

        }

        //开始判断回文

        while(head!=slow){

            if(head.val!=slow.val){

                return false;

            }

            if(head.next==slow){

                return true;

            }

            head=head.next;

            slow=slow.next;

        }

        return true;

    }

}


 相交链表

数据结构-链表_第12张图片

 

public class Solution {

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {

        //求出两个链表长度差

        //让长的链表的头指针先走长度差步

        //在同时走,相遇的时候就是公共结点

        //p1始终指向长的链表

        //p2始终指向短的链表

        ListNode p1 = headA;

        ListNode p2 = headB;

        int len1 = 0;

        int len2 = 0;

        while(p1!=null){

            len1++;

            p1 = p1.next;

        }

        while(p2!=null){

            len2++;

            p2 = p2.next;

        }

        int len = len1 -  len2;

         //这里需要注意的是

        //如果是大于也要将p1和p2重新指向

        //因为它们的指向在上述过程中已经发生了变化

        p1 = headA;

        p2 = headB;

        if(len < 0){

            //小于0说明A短B长

            p1 = headB;

            p2 = headA;

            len = len2 - len1;

        }

        while(len!=0){

            p1 = p1.next;

            len--;

        }

        while(p1 != p2){

            p1 = p1.next;

            p2 = p2.next;

        }

        if(p1 == null){

            return null;

        }

        return p1;

    }

}


环形链表

数据结构-链表_第13张图片

public class Solution {

    public boolean hasCycle(ListNode head) {

        //利用快慢指针

        //fast一次走两步

        //slow一次走一步

        //如果fast和slow能相遇说明有环

        if(head == null){

            return false;

        }

        if(head.next == null){

            return false;

        }

        ListNode fast = head;

        ListNode slow = head;

        while(fast!=null && fast.next!=null){

            fast = fast.next.next;

            slow = slow.next;

            if(fast == slow){

                return true;

            }

        }

        return false;

    }

}


LinkedList的模拟实现

LinkedList的底层是一个无头的双向循环链表.

public class MyLinkedList {
    static class ListNode {
        public int val;
        public ListNode prev;
        public ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }
    public ListNode head;//标记双向链表的头部
    public ListNode tail;//标记双向链表的尾部


    /**
     * 打印双向链表的每个节点 的值
     */
    public void display(){
        ListNode cur = this.head;
        while (cur != null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

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

    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        ListNode cur = this.head;
        while (cur != null) {
            if(cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }


    public void addFirst(int data){
        ListNode node = new ListNode(data);
        if(this.head == null) {
            this.head = node;
            this.tail = node;
        }else {
            node.next = this.head;
            this.head.prev = node;
            head = node;
        }
    }

    //尾插法
    public void addLast(int data){
        ListNode node = new ListNode(data);
        if(this.head == null) {
            this.head = node;
            this.tail = node;
        }else {
            tail.next = node;
            node.prev = tail;
            tail = node;
        }
    }
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        //1、判断Index位置的合法性
        if(index < 0 || index > size()) {
            throw new IndexWrongFulException("index位置不合法!");
        }
        //2、判断特殊位置,头插 和 尾插
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == size()) {
            addLast(data);
            return;
        }
        //3、找到index位置节点的地址
        ListNode cur = findIndexListNode(index);
        //4、修改4个指向
        ListNode node = new ListNode(data);
        node.next = cur;
        cur.prev.next = node;
        node.prev = cur.prev;
        cur.prev = node;
    }

    private ListNode findIndexListNode(int index) {
        ListNode cur = head;
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }


    //删除第一次出现关键字为key的节点
    public void remove(int key){
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key) {
                if(cur == head) {
                    head = head.next;
                    if(head != null) {
                        head.prev = null;
                    }else {
                        tail = null;
                    }
                }else {
                    cur.prev.next = cur.next;
                    if(cur.next != null) {
                        cur.next.prev = cur.prev;
                    }else {
                        this.tail = cur.prev;
                    }
                }
                return;
            }
            cur = cur.next;
        }
    }
    //删除所有值为key的节点
    public void removeAllKey(int key){
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key) {
                if(cur == head) {
                    head = head.next;
                    if(head != null) {
                        head.prev = null;
                    }else {
                        tail = null;
                    }
                }else {
                    cur.prev.next = cur.next;
                    if(cur.next != null) {
                        cur.next.prev = cur.prev;
                    }else {
                        this.tail = cur.prev;
                    }
                }
            }
            cur = cur.next;
        }
    }

    public void clear(){
        ListNode cur = head;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.prev = null;
            cur.next = null;
            cur = curNext;
        }
        head = null;
        tail = null;
    }
}

LinkedList的构造方法


LinkedList的常用方法

数据结构-链表_第14张图片 


ArrayListLinkedList的区别 

数据结构-链表_第15张图片


 

你可能感兴趣的:(链表,数据结构)