一、表ADT(Abstract Data Type)
我们将处理形如我们将处理形如A0,A1,A2,…,AN-1的一般的表。我们说这个表的大小是N。我们将大小为0的特殊的表称为空表(empty list)。
对于除空表外的任何表,我们说A1后继A1-1(或继A1-1之后,i<N)并称A1-1前驱A;(i>0)。表中的第一个元素是A0,而最后一个元素是AN-10我们将不定义A0的前驱元,也不定义AN-1的后继元。元素A2在表中的位置为i+1。为了简单起见,我们假设表中的元素是整数,但一般说来任意的复元素也是允许的(而且容易由java泛型类处理)
与这些“定义”相关的是要在表ADT上进行操作的集合。printList 和makeEmpty是常用的操作,其功能显而易见;find返回某一项首次出现的位置;insert 和remove一般是从表的某个位置插入和删除某个元素;而findKth则返回(作为参数而被指定的)某个位置上的元素。如果34,12,52,16,12是一个表,则find(52)会返回2;insert(x,2)可把表变34.12.x.52.16.12(如果我们插入到给定位置上的话);而remove(52)则又将该表变为34,12,x,16,12.
当然,一个方法的功能怎样才算恰当,完全要由程序设计者来确定,就像对特殊情况的处理那样(例如,上述find(1)返回什么?)。我们还可以添加一些操作,比如next 和previous,它们会取一个位置作为参数并分别返回其后继元和前驱元的位置。
1.1表的简单数组实现
对表的所有这些操作都可以通过使用数组来实现。虽然数组是由固定容量创建的,但在需要的时候可以用双倍的容量创建一个不同的数组。它解决由于使用数组而产生的最严重的问题,即从历史上看为了使用一个数组,需要对表的大小进行估计。而这种估计在Java或任何现代编程语言中都是不需要的。下列程序段解释一个数组arr在必要的时候如何被扩展
int[] arr =new int[10];
//下面我们决定需要扩大arr
int[] newArr = new int[arr.length * 2];
for(int i=0;i<arr.length;i++){
newArr[i]=arr[i];
}
arr=newArr;
数组的实现可以使得printList以线性时间被执行,而findKth操作则花费常数时间,
这正是我们所能够预期的。不过,插入和删除的花费却潜藏着昂贵的开销,这要看插入和删除发生在什么地方。最坏的情形下,在位置0的插入(即在表的前端插入)首先需要将整个数组后移一个位置以空出空间来,而删除第一个元素则需要将表中的所有元素前移一个位置,因此这两种操作的最坏情况为0(N)。平均来看,这两种操作都需要移动表的一半的元素,因此仍然需要线性时间。另一方面,如果所有的操作都发生在表的高端,那就没有元素需要移动,而添加和删除则只花费O(1)时间。
存在许多情形,在这些情形下的表是通过在高端进行插入操作建成的,其后只发生对数组的访问(即只有findKth操作)。在这种情况下,数组是表的一种恰当的实现。然而,如果发生对表的一些插入和删除操作,特别是对表的前端进行,那么数组就不是一种好的选择。下面处理另一种数据结构–链表(linked list)。
1.2 简单链表
为了避免插入和删除的线性开销,我们需要保证表可以不连续存储,否则表的每个部分都可能需要整体移动。下图指出链表的一般想法。
链表由一系列节点组成,这些节点不必在内存中相连。每一个节点均含有表元素和到包含该元素后继元的节点的链(link)。我们称之为next链。最后一个单元的next链引用null。为了执行printList或find(x),只要从表的第一个节点开始然后用一些后继的next链遍历该表即可。这种操作显然是线性时间的,和在数组实现时一样,不过其中的常数可能会比用数组实现时要大。findKth操作不如数组实现时的效率高;findKth(i)花费O(i)的时间并以这种明显的方式遍历链表而完成。在实践中这个界是保守的,因为调用findKth常常是以(按i)排序后的方式进行。
例如,findKth(2),findKth(3),findKth(4)以及findKth(6)可通过对表的一次扫描同时实现。
remove方法可以通过修改一个next引用来实现。下图给出在原表中删除第三个元素的结果。
insert方法需要使用new操作符从系统取得一个新节点,此后执行两次引用的调整。其一般想法在图3-3中给出,其中的虚线表示原来的next引用。
我们看到,在实践中如果知道变动将要发生的地方,那么向链表插入或从链表中删除一项的操作不需要移动很多的项,而只涉及常数个节点链的改变。
在表的前端添加项或删除第一项的特殊情形此时也属于常数时间的操作,当然要假设到链表前端的链是存在的。只要我们拥有到链表最后节点的链,那么在链表末尾进行添加操作的特殊情形(即让新的项成为最后一项)可以花费常数时间。因此,典型的链表拥有到该表两端的链。删除最后一项比较复杂,因为必须找出指向最后节点的项,把它的next链改成null,然后再更新持有最后节点的链。在经典的链表中,每个节点均存储到其下一节点的链,而拥有指向最后节点的链并不提供最后节点的前驱节点的任何信息。
保留指向最后节点的节点的第3个链的想法行不通,因为它在删除操作期间也需要更新。我们的做法是,让每一个节点持有一个指向它在表中的前驱节点的链,如图3-4所示,我们称之为双链表(doubly linked list)。关于双链表的知识会在本文后面为大家介绍,接下来我向大家介绍Java Collections API 中的表。
二、Java Collections API 中的表
在类库中,Java语言包含有一些普通数据结构的实现。该语言的这一部分通常叫作
Collections API。表ADT是在Collections API中实现的数据结构之一。
本图中显示了所有的类与接口,在本文,主要介绍关于表相关的类与接口,其他我将后续介绍
2.1 Collection接口
Collections API位于java. util包中。集合(collection)的概念在Collection接口中得到抽象,它存储一组类型相同的对象。下面显示该接口一些最重要的部分(但一些方法未被显示)。
方法声明 | 功能 |
---|---|
int size() | 此方法用于返回集合中元素的数量。例如,对于一个ArrayList,调用size()会返回列表中当前存储的元素个数。 |
boolean isEmpty() | 该方法用于判断集合是否为空。如果集合中没有元素,返回true;否则,返回false |
void clear() | 此方法用于移除集合中的所有元素,使集合变为空集合 |
boolean contains(AnyType x) | 用于检查集合中是否包含指定元素x。如果集合包含该元素,返回true;否则,返回false。 |
add(AnyType x) | 用于向集合中添加元素x。如果添加成功,返回true;如果由于某种原因(例如集合不允许重复且元素已存在)添加失败,返回false。 |
remove(AnyType x) | 用于从集合中移除指定元素x。如果集合中存在该元素并成功移除,返回true;否则,返回false。 |
iterator() | 此方法返回一个迭代器,用于遍历集合中的元素。通过迭代器,可以逐个访问集合中的元素 |
其中iterator()方法示例代码如下
Collection<String> collection = new ArrayList<>();
collection.add("apple");
collection.add("banana");
Iterator<String> it = collection.iterator();
while (it.hasNext()) {
String element = it.next();
System.out.println(element);
}
上述都是java。util包中Collection接口的子集
在Collection接口中的许多方法所做的工作由它们的英文名称可以看出,因此size返回集合中的项数;isEmpty返回true当且仅当集合的大小为0。如果x在集合中,则contains返回true。注意,这个接口并不规定集合如何决定x是否属于该集合 – 这要由实现该Collection接口的具体的类来确定。add和remove从集合中添加和删除x,如果操
作成功则返回true,如果因某个看似有理(非异常)的原因失败则返回false。例如,如果要
删除的项不在集合中,则remove可能失败,而如果特定的集合不允许重复,那么当企图插入一项重复项时,add操作就可能失败。
Collection接口扩展了Iterable接口。实现Iterable接口的那些类可以拥有增强的for循环,该循环施于这些类之上以观察它们所有的项。例如,下图代码中的例程可以用来打印任意集合中的所有的项。这种方式的print的实现和当coll具有类型AnyType[]时能够使用的相应的实现是完全相同的,它们逐个字符都是一样的。
public static <AnyType> void print( Collection<AnyType> coll ){
for( AnyType item : coll )
System.out.println( item );
}
2.2Iterator接口
实现Iterable接口的集合必须提供一个称为iterator的方法,该方法返回一个Iterator类型的对象。该Iterator是一个在java.util包中定义的接口,Iterator接口的思路是,通过iterator方法,每个集合均可创建并返回给客户一个实现Iterator接口的对象,并将当前位置的概念在对象内部存储下来。
每次对next的调用都给出集合的(尚未见到的)下一项。因此,第1次调用next给出第1项,第2次调用给出第2项,等等。hasNext用来告诉是否存在下一项。当编译器见到一个正在用于Iterable的对象的增强的for循环的时候,它用对iterator方法的那些调用代替增强的for循环以得到一个Iterator对象,然后调用next 和hasNext。因此,前面看到的print例程由编译器重写,代码如下:
public static<AnyType> void print(Collection<AnyType> coll){
Iterator<AnyType> itr= coll.iterator();
while(itr.hasNext){
AnyType item=itr,next();
System.out.println(item);
}
//通过一个编译器使用一个迭代器改写的Iterator类型上的增强的for循环
}
由于Iterator接口中的现有方法有限,因此,很难使用Iterator做简单遍历Collection以外的任何工作。 Iterator接口还包含一个方法,叫作remove。该方法可以删除由next最新返回的项(此后,我们不能再调用remove,直到对next再一次调用以后)。虽然Collection接口也包含一个remove方法,但是,使用Iterator的remove方法可能有更多的优点。
Iterator的remove方法的主要优点在于,Collection的remove方法必须首先找出要被删除的项。如果知道所要删除的项的准确位置,那么删除它的开销很可能要小得多。有的代码要求,是在集合中每隔一项删除一项。这个程序用迭代器(iterator)很容易编写,而且比用Collection的remove方法潜藏着更高的效率。
2.3 List接口、ArrayList 类和LinkedList类
本文跟我们关系最大的集合就是表(list),它由java.util包中的List接口指定。List接口继承了Collection接口,因此它包含Collection接口的所有方法,外加其他一些方法。下面的表格为大家解释其中最重要的一些方法。
方法声明 | 功能 |
---|---|
AnyType get(int idx) | 该方法用于获取列表中指定索引idx位置的元素。它返回列表中该位置的元素,类型为AnyType(任意类型,取决于这里需要使用什么类型) |
AnyType set(int idx, AnyType newVal) | 方法用于将列表中指定索引idx位置的元素替换为newVal,并返回被替换的旧元素 |
void add(int idx, AnyType x) | 该方法用于在列表的指定索引idx位置插入元素x。它会将x插入到指定位置,原位置及其后的元素会相应后移 |
void remove(int idx) | 此方法用于移除列表中指定索引idx位置的元素。移除后,其后的元素会相应前移 |
ListIterator listIterator(int pos) | 该方法返回一个从指定位置pos开始的列表迭代器ListIterator。列表迭代器允许双向遍历列表,并且可以在遍历过程中进行元素的添加、删除和修改操作 |
get 和set 使得用户可以访问或改变通过由位置索引idx给定的表中指定位置上的项。索引0位于表的前端,索引size()-1代表表中的最后一项,而索引size()则表示新添加的项可以被放置的位置。add使得在位置idx处置入一个新的项(并把其后的项向后推移一个位置)。于是,在位置0处add是在表的前端进行的添加,而在位置size()处的add是把被添加项作为新的最后项添入表中。除以AnyType作为参数的标准的remove外,remove还被重载以删除指定位置上的项。最后,List接口指定listIterator方法,它将产生比通常认为的还要复杂的迭代器。
2.3.1 List的介绍
在集合框架中,List是一个接口,继承自Collection。站在数据结构的角度来看,List就是一个线性表,即n个具有相同类型元素的有限序列,在该序列上可以执行增删改查以及变量等操作。List中提供了好的方法,常用的具体如下:
方法 | 说明 |
---|---|
boolean add(E e) | 尾插 e |
void add(int index, E element) | 将 e 插入到 index 位置 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E get(int index) | 获取下标 index 位置元素 |
E set(int index, E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个 o 的下标 |
List subList(int fromIndex, int toIndex) | 截取部分 list |
List是个接口,并不能直接用来实例化。
如果要使用,必须去实例化List的实现类。在集合框架中,ArrayList和LinkedList都实现了List接口。
2.3.2 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底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
ArrayList的构造
方法 | 解释 |
---|---|
ArrayList() | 无参构造 |
ArrayList(Collection<? extends E> c) | 利用其他 Collection 构建 ArrayList |
ArrayList(int initialCapacity) | 指定顺序表初始容量 |
代码示例如下
public static void main(String[] args) {
// ArrayList创建,推荐写法
// 构造一个空的列表
List<Integer> list1 = new ArrayList<>();
// 构造一个具有10个容量的列表
List<Integer> list2 = new ArrayList<>(10);
list2.add(1);
list2.add(2);
list2.add(3);
// list2.add("hello");
// 编译失败,List<Integer>已经限定了,list2中只能存储整形元素
// list3构造好之后,与list中的元素一致
ArrayList<Integer> list3 = newArrayList<>(list2);
// 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
List list4 = new ArrayList();
list4.add("111");
list4.add(100);
}
ArrayList常见操作
ArrayList虽然提供的方法比较多,但是常用方法如下所示,需要用到其他方法时,可以查看ArrayList的帮助文档
方法 | 解释 |
---|---|
boolean add(E e) | 尾插 e |
void add(int index, E element) | 将 e 插入到 index 位置E |
boolean addAll(Collection<? extends E> c) | 尾插 c 中的元素 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E get(int index) | 获取下标 index 位置元素 |
E set(int index, E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个 o 的下标 |
List subList(int fromIndex, int toIndex) | 截取部分 list |
下面是代码示例
class MyArrayList<E> implements List<E> {
private static final int DEFAULT_CAPACITY = 10;
private Object[] data;
private int size;
public MyArrayList() {
data = new Object[DEFAULT_CAPACITY];
size = 0;
}
// 尾插e
@Override
public boolean add(E e) {
ensureCapacity(size + 1);
data[size++] = e;
return true;
}
// 将e插入到index位置
@Override
public void add(int index, E element) {
ensureCapacity(size + 1);
for (int i = size; i > index; i--) {
data[i] = data[i - 1];
}
data[index] = element;
size++;
}
// 尾插c中的元素
@Override
public boolean addAll(Collection<? extends E> c) {
int numNew = c.size();
ensureCapacity(size + numNew);
Iterator<? extends E> it = c.iterator();
while (it.hasNext()) {
add(it.next());
}
return numNew > 0;
}
// 删除index位置元素
@Override
public E remove(int index) {
E removed = get(index);
for (int i = index; i < size - 1; i++) {
data[i] = data[i + 1];
}
data[--size] = null;
return removed;
}
// 删除遇到的第一个o
@Override
public boolean remove(Object o) {
for (int i = 0; i < size; i++) {
if (o == null? data[i] == null : o.equals(data[i])) {
remove(i);
return true;
}
}
return false;
}
// 获取下标index位置元素
@Override
public E get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
return (E) data[index];
}
// 将下标index位置元素设置为element
@Override
public E set(int index, E element) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
E oldValue = (E) data[index];
data[index] = element;
return oldValue;
}
// 清空
@Override
public void clear() {
for (int i = 0; i < size; i++) {
data[i] = null;
}
size = 0;
}
// 判断o是否在线性表中
@Override
public boolean contains(Object o) {
for (int i = 0; i < size; i++) {
if (o == null? data[i] == null : o.equals(data[i])) {
return true;
}
}
return false;
}
// 返回第一个o所在下标
@Override
public int indexOf(Object o) {
for (int i = 0; i < size; i++) {
if (o == null? data[i] == null : o.equals(data[i])) {
return i;
}
}
return -1;
}
// 返回最后一个o的下标
@Override
public int lastIndexOf(Object o) {
for (int i = size - 1; i >= 0; i--) {
if (o == null? data[i] == null : o.equals(data[i])) {
return i;
}
}
return -1;
}
// 截取部分list
@Override
public List<E> subList(int fromIndex, int toIndex) {
if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) {
throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + ", toIndex: " + toIndex);
}
MyArrayList<E> subList = new MyArrayList<>();
for (int i = fromIndex; i < toIndex; i++) {
subList.add(get(i));
}
return subList;
}
ArrayList的遍历
ArrayList 可以使用三方方式遍历:for循环+下标、foreach、使用迭代器
代码如下
public class ArrayListTraversal {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("apple");
arrayList.add("banana");
arrayList.add("cherry");
// 使用for循环 + 下标遍历
for (int i = 0; i < arrayList.size(); i++) {
System.out.println(arrayList.get(i));
}
// 使用foreach循环遍历
for (String fruit : arrayList) {
System.out.println(fruit);
}
// 使用迭代器遍历
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
注意
- ArrayList最长使用的遍历方式是:for循环+下标 以及 foreach
2.迭代器是设计模式的一种,后序容器接触多了再给大家铺垫
ArrayList的扩容机制
ArrayList是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容。具体体现在源码中
对Array的扩容机制做总结有以下几点:
- 检测是否真正需要扩容,如果是调用grow准备扩容
- 预估需要库容的大小
1)初步预估按照1.5倍大小扩容
2)如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容
3)真正扩容之前检测是否能扩容成功,防止太大导致扩容失败- 使用copyOf进行扩容
2.3.3 LinkedList的介绍
先来了解链表这个概念
链表的概念及结构
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。类似下图:
实际中链表的结构非常多样,有以下几种链表结构:
-
单向或者双向
-
带头或者不带头
-
循环或者非循环
但是我们一般需要重点掌握两种
1.无头单向非循环链表:结构简单,一般不会单独用来储存数据。但是在实际中,更多作为其他数据结构的子结构
2.无头双向链表:在java集合框架库中LinkedList底层实现的就是无头双向循环链表。
链表的实现
// 1、无头单向非循环链表及其部分方法实现
public class MySingleList {
static class ListNode {
public int val;//节点的值域
public ListNode next;//下一个节点的地址
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;//表示当前链表的头节点
public void createList() {
ListNode node1 = new ListNode(12);
ListNode node2 = new ListNode(23);
ListNode node3 = new ListNode(34);
ListNode node4 = new ListNode(45);
ListNode node5 = new ListNode(56);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
this.head = node1;
}
public void display() {
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
/**
* 从指定位置 开始打印
* @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 int size(){
int count = 0;
ListNode cur = head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
ListNode cur = 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 = head;
if(cur == null) {
head = node;
return;
}
//找到链表的尾巴 注意是cur.next 不是cur
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
if(index < 0 || index > size()) {
System.out.println("index 不合法");
return;
}
if(index == 0) {
addFirst(data);
return;
}
if(index == size()) {
addLast(data);
return;
}
ListNode cur = findIndexSubOne(index);
ListNode node = new ListNode(data);
node.next = cur.next;
cur.next = node;
}
/**
* 找到要删除节点位置的前一个节点
* @param index
* @return
*/
private ListNode findIndexSubOne(int index) {
ListNode cur = head;
while (index-1 != 0) {
cur = cur.next;
index--;
}
return cur;
}
//删除第一次出现关键字为key的节点
public void remove(int key){
if(head == null) {
return;
}
//单独删除头节点
if(head.val == key) {
head = head.next;
return;
}
ListNode cur = searchPrev(key);
if(cur == null) {
System.out.println("没有你要删除的数字");
return;
}
ListNode del = cur.next;
cur.next = del.next;
}
/**
* 找到关键字 key的前驱
* @param key
* @return
*/
private ListNode searchPrev(int key) {
ListNode cur = head;
while (cur.next != null) {
if(cur.next.val == key) {
return cur;
}
cur = cur.next;
}
return null;
}
//删除所有值为key的节点
public void removeAllKey(int key){
if(head == null) {
return;
}
ListNode cur = head.next;
ListNode 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 void clear() {
this.head = null;
}
LinkedList的模拟实现
无头双向链表及其部分方法的实现
public class MyLinkedList{
//public class MyLinkedList {
static class ListNode {
private int val;
private ListNode prev;
private ListNode next;
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;//双向链表的头节点
public ListNode last;//双向链表的尾巴
//得到链表的长度
public int size(){
ListNode cur = head;
int count = 0;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
public void display(){
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
//查找是否包含关键字key是否在链表当中
public boolean contains(int key){
ListNode cur = 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(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;
}else {
last.next = node;
node.prev = last;
last = last.next;
}
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
checkIndex(index);
if(index == 0) {
addFirst(data);
return;
}
if(index == size()) {
addLast(data);
return;
}
ListNode cur = searchIndex(index);
ListNode node = new ListNode(data);
node.next = cur;
cur.prev.next = node;
node.prev = cur.prev;
cur.prev = node;
}
private ListNode searchIndex(int index) {
ListNode cur = head;
while (index != 0) {
cur = cur.next;
index--;
}
return cur;
}
private void checkIndex(int index) {
if(index < 0 || index > size()) {
throw new IndexOutOfException("index 不合法!"+index);
}
}
//删除第一次出现关键字为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 {
last = null;
}
}else {
//删除中间节点 和 尾巴节点
if(cur.next != null) {
//删除中间节点
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}else {
//尾巴节点
cur.prev.next = cur.next;
last = last.prev;
}
}
return;
}else {
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 {
last = null;
}
}else {
//删除中间节点 和 尾巴节点
if(cur.next != null) {
//删除中间节点
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}else {
//尾巴节点
cur.prev.next = cur.next;
last = last.prev;
}
}
cur = cur.next;
//return;
}else {
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;
last = null;
}
}
LinkedList的使用
LinkedList的底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高.同样,在集合框架中,LinkedList也实现了List接口
- LinkedList实现了List接口
- LinkedList的底层使用了双向链表
- LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
- LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
- LinkedList比较适合任意位置插入的场景
LinkedList的构造
方法 | 解释 |
---|---|
LinkedList | 无参构造 |
public LinkedList(Collection<? extends E> c) | 使用其他集合容器中元素构造List |
public static void main(String[] args) {
// 构造一个空的LinkedList
List<Integer> list1 = new LinkedList<>();
List<String> list2 = new java.util.ArrayList<>();
list2.add("JavaSE");
list2.add("JavaWeb");
list2.add("JavaEE");
// 使用ArrayList构造LinkedList
List<String> list3 = new LinkedList<>(list2); }
LinkedList的其他常用方法介绍
方法 | 解释 |
---|---|
boolean add(E e) | 尾插 e |
void add(int index, E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection<? extends E> c) | 尾插 c 中的元素 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E get(int index) | 获取下标 index 位置元素 |
E set(int index, E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
int indexOf(Object o) | 返回第一个o所在下标 |
int lastIndexOf(Object o) | 返回最后一个o所在下标 |
List subList(int fromIndex, int toIndex) | 截取部分List |
boolean contains(Object o) | 判断o是否在线性表中 |
2.3.4ArrayList和LinkedList的区别
总结
本文主要介绍了数据结构中的表,以及表所延申出来的一些常用结构以及结构的方法扩展.
以上就是今天要讲的内容,大家再见,作者在这里向大家祝好!
我们下期再见!