Java数据结构——线性表

一、线性表

         线性表是n个具有相同特性的数据元素的有限序列,常见的线性表:顺序表、链表、栈、队列等。线性表在逻辑上是线性结构,但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

二、ArrayList 与顺序表

1、顺序表

        顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。

2、ArrayList

2.1、ArrayList 简介

        在集合框架中,ArrayList是一个普通的类,实现了List接口。

  1. ArrayList 是以泛型方式实现的,使用时必须要先实例化
  2. ArrayList 实现了 RandomAccess 接口,表明 ArrayList 支持随机访问
  3. ArrayList 实现了Cloneable接口,表明ArrayList是可以clone的
  4. ArrayList 实现了Serializable接口,表明ArrayList是支持序列化的
  5. 和 Vector 不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择 Vector 或者 CopyOnWriteArrayList
  6. ArrayList 底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表 

2.2、ArrayList 使用

2.2.1 构造方法

        无参、利用其他 Collection 构建 ArrayList 、指定顺序表初始容量,其他详细内容自行查阅帮助文档。

2.2.2 常用方法

        自行查阅帮助文档。

2.2.3 扩容机制

        ArrayList是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容。

  1.  检测是否真正需要扩容,如果是调用 grow 准备扩容
  2.  预估需要库容的大小,初步预估按照1.5倍大小扩容。如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容。真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
  3. 使用 copyOf 进行扩容

三、LinkedList 与链表

1、顺序表的缺陷

        顺序表的实现类 ArrayList 底层使用数组来存储元素。由于底层  是一段连续空间,当在 ArrayList 任意位置插入或者删除元素时,就需要将后续元素整体往前或者往后移动,时间复杂度为O(n) ,效率比较低。因此 ArrayList 不适合任意位置插入和删除操作比较多的场景。因此,Java 集合中又引入了 LinkedList,即链表结构。

2、链表

        链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。

注意:

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

        链表的结构非常多样,单向或双向、带头或不带头、循环或非循环,仅仅这些特性组合起来就有8种链表结构。重点需要掌握的有两种:无头单向非循环链表(结构简单,一般不单独用来存储数据,更多的是作为其他数据结构的子结构,在笔试面试中经常出现)、无头双向链表(Java的集合框架库中 LinkedList 底层实现就是无头双向循环链表)。

3、LinkedList

3.1、LinkedList 简介

        LinkedList 的底层是双向链表结构,由于链表没有将元素储存在连接的空间中,元素储存在单独的节点中,然后然后通过引用将结点连接起来,因此在任意位置插入或删除元素时,不需要搬移元素,效率比较高。

 关于 LinkedList :

  1. 实现了 List 接口
  2. 底层使用了双向链表
  3. 没有实现 RandomAccess 接口,因此 LinkedList 不支持随机访问
  4. 任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
  5. 比较适合任意位置插入的场景

3.2、LinkedList 使用

3.2.1 构造方法

        无参、使用其他集合容器中元素构造 List ,其他具体内容自行查阅帮助手册。

3.2.2 常用方法

        自行查阅帮助手册。

3.2.3 面试题
(1)使用递归和迭代两种方法分别实现反转链表。206. 反转链表 - 力扣(LeetCode)

1) 使用递归的方法实现反转链表

public ListNode reverseList(ListNode head) {
    if (head == null || head.next == null)   return head;
    ListNode newHead = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return newHead;
}

2) 使用迭代的方法实现反转链表

public ListNode reverseList(ListNode head) {
    ListNode last = null;
    ListNode next = head;
    while (next != null) {
        next = next.next;
        head.next = last;
        last = head;
        head = next;
    }
    return last;
}
(2)将链表按照给定值x,分割成两个链表。链表分割_牛客题霸_牛客网

public ListNode partition(ListNode pHead, int x) {
    if (pHead == null || pHead.next == null)    return pHead;
    ListNode bigHead = null;
    ListNode bigEnd = null;
    ListNode smallHead = null;
    ListNode smallEnd = null;
    ListNode cur = pHead;
    while (cur != null) {
        if (cur.val >= x) {
            if (bigHead == null) {
                bigHead = cur;
                bigEnd = cur;
            } else {
                bigEnd.next = cur;
                bigEnd = bigEnd.next;
            }
        } else {
            if (smallHead == null) {
                smallHead = cur;
                smallEnd =cur;
            } else {
                smallEnd.next = cur;
                smallEnd = smallEnd.next;
            }
        }
        cur = cur.next;
    }
    if (smallHead == null)  return bigHead;
    if (bigHead != null) {
        bigEnd.next = null;
    }
    smallEnd.next = bigHead;
    return smallHead;
}
(3)链表的回文结构。链表的回文结构_牛客题霸_牛客网

public boolean chkPalindrome(ListNode A) {
    if (A == null || A.next == null) {
        return true;
    }

    ListNode fast = A;
    ListNode slow = A;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
    }

    ListNode cur = slow.next;
    while (cur != null) {
        ListNode nextCur = cur.next;
        cur.next = slow;
        slow = cur;
        cur = nextCur;
    }

    while (A != slow) {
        if (A.val != slow.val) {
            return false;
        }
        A = A.next;
        slow = slow.next;
        if (A.next == slow) return true;
    }
    return true;
}
(4)输入一个链表,找出他们第一个相交的节点。160. 相交链表 - 力扣(LeetCode) 

 

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if(headA == null || headB == null)  return null;

    ListNode curA = headA;
    ListNode curB = headB;
    while(curA != curB) {
        curA = curA == null ? headB : curA.next;
        curB = curB == null ? headA : curB.next;
    }

    return curA;
}
(5) 判断链表中是否有环。141. 环形链表 - 力扣(LeetCode)

思路: 

        利用快慢指针,如果有环,快指针会与慢指针相遇;没有,快指针先到达链表尾。

public boolean hasCycle(ListNode head) {
    if (head == null || 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;
}
(6)(第(5)题的进阶)返回入环的第一个结点,无环则返回null。142. 环形链表 II - 力扣(LeetCode)

public ListNode detectCycle(ListNode head) {
    if (head == null || head.next == null) return null;
    ListNode fast = head.next.next;
    ListNode slow = head.next;
    while (fast != null && fast.next != null) {
        if (fast == slow) {
            while (head != slow) {
                head = head.next;
                slow = slow.next;
            }
            return head;
        }
        fast = fast.next.next;
        slow = slow.next;
    }
    return null;
}

 

四、ArrayList 和 LinkedList 的区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值