线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结 构,常见的线性表:顺序表、链表、栈、队列...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物 理上存储时,通常以数组和链式结构的形式存储。如下图:
ArrayList是Java中的一个动态数组类, 即顺序表,可以动态地增加或减少数组的大小。它是一个可以动态改变大小的数组,可以根据需要自动地增加或减少数组的大小。ArrayList可以存储任意类型的对象,包括基本数据类型和引用类型, 它实现了List接口, 具体框架图如下:
[说明]
1. ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
2. ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
3. ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
4. 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者 CopyOnWriteArrayList
5. ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
使用ArrayList可以方便地进行元素的添加、删除和查找操作,是Java中常用的集合类之一。
ArrayList类包含许多常用的方法,以下是一些常见的方法和它们的使用:
常见的方法
- boolean add(E e):向ArrayList中尾插 一个元素e。
- void add(int index, E element): 将 e 插入到 index 位置
- E remove(int index):移除指定索引位置的元素。
- E get(int index):获取指定索引位置的元素。
- size():获取ArrayList的大小
- E set(int index, E element): 将下标 index 位置元素设置为 element
- void clear(): 清空
- boolean contains(Object o):判断ArrayList是否包含指定的元素。
- int indexOf(Object o):返回指定元素在ArrayList中第一次出现的索引。
- int lastIndexOf(Object o):?返回指定元素在ArrayList中最后一次出现的索引。
? 使用:
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// 创建一个ArrayList
ArrayList<String> list = new ArrayList<>();
// 向ArrayList中尾插元素
list.add("apple");
list.add("banana");
list.add("orange");
// 将元素插入到指定索引位置
list.add(1, "grape");
//遍历ArrayList
System.out.print("打印1: ");
for (String ret: list) {
System.out.print(ret+" ");
}
System.out.println();
// 移除指定索引位置的元素
list.remove(2);
//遍历ArrayList
System.out.print("打印2: ");
for (String ret: list) {
System.out.print(ret+" ");
}
System.out.println();
// 获取指定索引位置的元素
String fruit = list.get(0);
System.out.println("索引0处的水果: " + fruit);
// 获取ArrayList的大小
int size = list.size();
System.out.println("ArrayList的大小: " + size);
// 将指定索引位置的元素设置为新元素
list.set(2, "cherry");
// 判断ArrayList是否包含指定的元素
boolean contains = list.contains("apple");
System.out.println("是否包含'apple': " + contains);
// 返回指定元素在ArrayList中第一次出现的索引
int index = list.indexOf("banana");
System.out.println("'banana'第一次出现的索引: " + index);
// 返回指定元素在ArrayList中最后一次出现的索引
int lastIndex = list.lastIndexOf("apple");
System.out.println("'apple'最后一次出现的索引: " + lastIndex);
// 清空ArrayList
list.clear();
//遍历ArrayList
System.out.print("打印3: ");
for (String ret: list) {
System.out.print(ret+" ");
}
System.out.println();
}
}
ArrayList可以使用三种方式遍历:for循环+下标、foreach、使用迭代器, 代码如下:
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// 创建一个ArrayList对象,它是List接口的实现类
List<String> list = new ArrayList<>();
// 将指定的元素添加到列表的末尾
list.add("apple");
list.add("banana");
list.add("cherry");
//for循环+下标
System.out.print("for循环+下标: ");
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+" ");
}
System.out.println();
//foreach遍历
System.out.print("foreach遍历: ");
for (String ret : list) {
System.out.print(ret+" ");
}
System.out.println();
//使用迭代器
System.out.print("迭代器遍历: ");
Iterator<String> iter = list.iterator();
while (iter.hasNext()){
String ret = iter.next();
System.out.print(ret+" ");
}
System.out.println();
}
}
package List;
import java.util.Arrays;
// 自定义的ArrayList类
public class MyArrayList {
public int[] arr; // 用于存储元素的数组
public int size; // 记录当前数组中的元素个数
public static final int Max = 10; // 默认最大容量
// 构造方法,初始化数组
public MyArrayList() {
this.arr = new int[10];
}
// 打印元素
public void display() {
for (int i = 0; i < this.size; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// 增加元素
public void add(int data) {
if (isFull()) {
arr = Arrays.copyOf(arr, arr.length * 2); // 如果数组已满,扩容为原来的两倍
}
arr[size++] = data;
}
// 判断数组是否已满
public boolean isFull() {
if (size == Max) {
return true;
}
return false;
}
// 检查插入位置是否合法
private void checkAddPos(int pos) {
if (pos < 0 || pos > size) {
throw new PosIndexNotLegalException("pos位置不合法");
}
}
// 在指定位置插入元素
public void add(int pos, int data) {
try {
checkAddPos(pos);
if (isFull()) {
arr = Arrays.copyOf(arr, 2 * arr.length); // 如果数组已满,扩容为原来的两倍
}
for (int i = size - 1; i >= pos; i--) {
arr[i + 1] = arr[i]; // 元素后移
}
arr[pos] = data; // 在指定位置插入新元素
size++;
} catch (PosIndexNotLegalException e) {
e.printStackTrace();
}
}
// 判断是否包含某个元素
public boolean contains(int toFind) {
for (int i = 0; i < size; i++) {
if (arr[i] == toFind) {
return true;
}
}
return false;
}
// 查找某个元素对应的位置
public int indexOf(int toFind) {
for (int i = 0; i < size; i++) {
if (arr[i] == toFind) {
return i;
}
}
return -1; // 如果找不到返回-1
}
// 获取指定位置的元素
public int get(int pos) {
checkAddPos(pos);
return arr[pos];
}
// 给指定位置的元素赋值
public void set(int pos, int value) {
checkAddPos(pos);
arr[pos] = value;
}
// 删除第一次出现的指定元素
public void remove(int toRemove) {
int index = indexOf(toRemove);
if (index == -1) {
System.out.println("没有你要删除的数字!");
return;
}
for (int j = index; j < size; j++) {
arr[j] = arr[j + 1]; // 元素前移
}
size--;
}
// 获取顺序表长度
public int size() {
int ret = size;
return ret;
}
// 清空顺序表
public void clear() {
size = 0; // 将元素个数置为0,实现清空操作
}
}
优点:
缺点:
总结
ArrayList 适合需要频繁随机访问元素、对大小变化较为频繁的场景,但在大量插入和删除操作时性能较差。
在前面ArrayList我们知道,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java 集合中又引入了LinkedList,即链表结构。
LinkedList 是 Java 中的一个无头双向链表。实现,实现了 List 和 Deque 接口,LinkedList 内部是使用无头双向循环链表。表来存储元素,每个节点包含对前一个节点和后一个节点的引用。LinkedList 支持在任意位置进行高效的插入和删除操作,时间复杂度为 O(1)。无头双向链表结构图:
双向链表的每个节点都有 3 个属性:
data : 实际存放的内容;
prev : 指向前一节点的指针;
next : 指向后一节点的指针。
实际中链表的结构非常多样,单向或者双向, 带头或者不带头, 循环或者非循环
虽然有这么多的链表的结构,但是我们重点掌握两种:
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如 哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。
LinkedList 类实现了 List 接口,因此它包含了 List 接口定义的所有方法,例如 add、remove、get、set 等。此外,LinkedList 还包含了一些特有的方法,如 addFirst、addLast等,用于在链表的开头或结尾进行操作
常见方法
- boolean add(E e): 在链表的末尾添加元素。
- void addFirst(E e): 在链表的开头添加元素。
- void addLast(E e): 在链表的末尾添加元素。
- E get(int index): 获取指定位置的元素。
- E set(int index, E element) 将下标 index 位置元素设置为 element
- boolean remove(): 删除并返回链表的第一个元素。
- String removeFirst(): 删除并返回链表的第一个元素。
- String removeLast(): 删除并返回链表的最后一个元素。
- int size(): 获取链表的大小。
- boolean contains(Object o): 判断链表是否包含指定的元素。
- int indexOf(Object o): 返回指定元素在链表中第一次出现的位置。
- int lastIndexOf(Object o) 返回最后一个 o 的下标
- void clear() 清空
import java.util.LinkedList;
class Main {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
// 添加元素
linkedList.add("A");
System.out.println("添加 'A' 后的链表: " + linkedList);
linkedList.addFirst("B");
System.out.println("在链表开头添加 'B' 后的链表: " + linkedList);
linkedList.addLast("C");
System.out.println("在链表末尾添加 'C' 后的链表: " + linkedList);
// 获取元素
String elementAtIndex1 = linkedList.get(1);
System.out.println("索引为 1 的元素: " + elementAtIndex1);
// 设置元素
linkedList.set(0, "D");
System.out.println("将索引为 0 的元素设置为 'D' 后的链表: " + linkedList);
// 删除元素
String removedElement = linkedList.remove();
System.out.println("删除的元素: " + removedElement);
String removedFirst = linkedList.removeFirst();
System.out.println("删除的第一个元素: " + removedFirst);
String removedLast = linkedList.removeLast();
System.out.println("删除的最后一个元素: " + removedLast);
// 获取链表大小
int size = linkedList.size();
System.out.println("链表的大小: " + size);
// 判断是否包含元素
boolean contains = linkedList.contains("A");
System.out.println("链表是否包含 'A': " + contains);
// 获取元素的索引
int index = linkedList.indexOf("B");
System.out.println("'B' 在链表中的索引: " + index);
// 清空链表
linkedList.clear();
System.out.println("清空链表后的结果: " + linkedList);
}
}
可以使用foreach、迭代器来访问链表中的每个元素。?代码如下:
import java.util.LinkedList;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
// 使用迭代器遍历
Iterator<String> iterator = linkedList.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println("Element: " + element);
}
// 使用foreach遍历
for (String element : linkedList) {
System.out.println("Element: " + element);
}
}
}
无头单向非循环链表代码如下:
public class SingleLinkedList {
// 定义节点类
static class Node {
public int val;
public Node next;
public Node(int val) {
this.val = val;
}
}
public Node head; // 链表头节点
// 头插法
public void addFirst(int data) {
Node node = new Node(data);
if (head == null) {
head = node;
} else {
node.next = head;
head = node;
}
}
// 尾插法
public void addLast(int data) {
Node node = new Node(data);
if (head == null) {
head = node;
return;
} else {
Node cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
}
// 任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) {
checkIndex(index);
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
Node node = new Node(data);
// 找到要插入的前一个位置
Node cur = prevIndex(index - 1);
node.next = cur.next;
cur.next = node;
}
private void checkIndex(int index) {
if (index < 0 || index > size()) {
throw new IndexNotLegalException("index位置不合法!");
}
}
private Node prevIndex(int index) {
Node cur = head;
while (index > 0) {
cur = cur.next;
index--;
}
return cur;
}
// 查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
if (head == null) return false;
Node cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
// 删除第一次出现关键字为key的节点
public void remove(int key) {
if (head == null) return;
if (head.val == key) {
head = head.next;
return;
}
Node cur = head.next;
Node prev = head;
while (cur != null) {
if (cur.val == key) {
prev.next = cur.next;
return;
} else {
prev = cur;
cur = cur.next;
}
}
}
// 删除所有值为key的节点
public void removeAllKey(int key) {
if (head == null) return;
Node cur = head.next;
Node prev = head;
while (cur != null) {
if (cur.val == key) {
prev.next = cur.next;
cur = cur.next;
} else {
prev = cur;
cur = cur.next;
}
}
if (head.val == key) {
head = head.next;
}
}
// 得到单链表的长度
public int size() {
Node cur = head;
int count = 0;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
// 打印链表
public void display() {
Node cur = head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
// 清空链表
public void clear() {
if (head == null) return;
Node cur = head;
while (cur != null) {
Node curNext = cur.next;
cur.next = null;
cur = curNext;
}
head = null;
}
// 一道练习题 -- 单链表的逆置
// 递归.单链表的逆置
public Node reverseList(Node head) {
if (head == null) {
return head;
}
if (head.next == null) {
return head;
}
Node newHead = reverseList(head.next);
head.next.next = head;
// 要注意的是的下一个节点必须指向?。如果忽略了这一点,链表中会产生环,所以每反转一个,要把原来下个结点置null;
head.next = null;
return newHead;
}
// 非递归.单链表的逆置
public Node reverseList1() {
Node cur = head;
Node prev = null;
while (cur != null) {
Node nextCur = cur.next;
cur.next = prev;
prev = cur;
cur = nextCur;
}
return prev;
}
}
无头双向循环链表的实现如下:
package MyLinkedList;
// 无头双向链表实现
public class MyTwoLinkedList {
// 定义节点类
static class ListNode {
public int val;
public ListNode next; //指向下一个节点
public ListNode prev; //指向上一个节点
public ListNode(int val) {
this.val = val;
}
}
public ListNode head; // 表示存储当前链表的头节点的引用
public ListNode last; // 尾节点
// 打印链表
public void display() {
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
// 头插法
public void addFirst(int data) {
ListNode node = new ListNode(data);
if (head == null) {
head = node;
last = node;
} else {
node.next = head;
head.prev = node;
head = node;
}
}
// 尾插法
public void addLast(int data) {
ListNode node = new ListNode(data);
if (head == null) {
head = node;
last = node;
return;
}
last.next = node;
node.prev = last;
last = last.next;
}
// 根据索引查找节点
private ListNode findIndex(int index) {
ListNode cur = head;
while (index > 0) {
index--;
cur = cur.next;
}
return cur;
}
// 任意位置插入,第一个数据节点为0号下标
public void addIndex(int index, int data) {
ListNode node = new ListNode(data);
if (index < 0 || index > this.size()) {
throw new IndexNotLegalException("双向链表index不合法!");
}
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
// 获取到当前的index位置的节点的地址
ListNode cur = findIndex(index);
node.next = cur;
cur.prev.next = node;
node.prev = cur.prev;
cur.prev = node;
}
// 查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
// 删除第一次出现关键字为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 {
cur.prev.next = cur.next;
// 删除的不是尾巴节点
if (last.val != key) {
cur.next.prev = cur.prev;
} else {
last = 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 {
cur.prev.next = cur.next;
// 删除的不是尾巴节点
if (cur.next != null) {
cur.next.prev = cur.prev;
} else {
// 删除的是尾结点
last = cur.prev;
}
}
}
cur = cur.next;
}
}
// 得到单链表的长度
public int size() {
ListNode cur = head;
int len = 0;
while (cur != null) {
len++;
cur = cur.next;
}
return len;
}
// 清空链表
public void clear() {
ListNode cur = head;
while (cur != null) {
ListNode nextCur = cur.next;
cur.next = null;
cur.prev = null;
cur = nextCur;
}
head = null;
last = null;
}
}
优点:
缺点:
总结
LinkedList 适合需要频繁插入和删除操作、对随机访问要求不高的场景,但在需要频繁随机访问元素时性能较差。
ArrayList 和 LinkedList 都是 Java 中的集合类,用于存储一组元素。它们之间的主要区别在于底层的数据结构和对元素的访问方式。
ArrayList 是基于数组实现的动态数组,它提供了快速的随机访问和遍历。在 ArrayList 中,每个元素都有一个索引,可以通过索引快速访问元素,但在插入和删除元素时需要移动其他元素,因此插入和删除操作的效率较低。
LinkedList 是基于链表实现的双向链表,它提供了快速的插入和删除操作。在 LinkedList 中,每个元素都包含对前一个元素和后一个元素的引用,因此插入和删除操作的效率比较高。但是在访问元素时需要从头或尾开始遍历,因此随机访问的效率较低。
如果需要频繁进行插入和删除操作,可以选择 LinkedList;如果需要频繁进行随机访问和遍历操作,可以选择 ArrayList。