表,栈和队列 —— 表

it2024-11-08  5

提示:如有错误请留下评论,谢谢。

文章目录

前言一、表1. 关于表的一些描述2. 表的常用操作3. 简单的实现4. 数组是表的一种恰当的表现形式,通常来说有以下几个特点⚠️⚠️⚠️⚠️⚠️⚠️ 二、链表1. 关于链表的一些描述2. 链表的特点以及和表的不同之处3. remove 方法4. Insert 方法插入一个元素如下图所示。5. 为了解决 3 中提到的问题,双链表诞生了,双链表与单链表不同的是,每个节点还会存储一个指向它在表中前驱节点的链。如下图所示 三、Java 中的应用1. 包含普通数据结构的部分,在Java API中称为 Collections API2. Collection 接口一些重要部分:3. Iterator 接口4. List 接口,ArrayList 类和 LinkedList 类5. 关于ListIterator 接口 总结


前言

熟练掌握数据结构是每个优秀程序员的必修课程,好的数据结构可以大大提高程序的运行效率,可见数据结构的重要性。之后将会陆续补充数据结构的知识点。 这部分将讲解什么是表,以及表在Java中的应用。


提示:以下是本篇文章正文内容,下面案例可供参考

一、表

1. 关于表的一些描述

这里将处理如 A0到 AN-1 的一般表。这个表的大小是 N,通常将大小为 0 的表称为 空表(empty list)Ai 后继是 Ai-1 , Ai-1 的前驱就是 Ai 。Ai 在表中的位置是 i+1。

2. 表的常用操作

printList, makeEmpty。find 返回某一项首次出现的位置。insert 和 remove 一般是表的某个位置插入和删除某个元素。findKth 则返回某个位置上的元素。

3. 简单的实现

int [] arr = new int [10]; int [] newArr = new int [arr.length * 2]; for (int i = 0; i < arr.length; i++) newArr[i] = arr[i]; arr = newArr;

4. 数组是表的一种恰当的表现形式,通常来说有以下几个特点⚠️⚠️⚠️⚠️⚠️⚠️

数组的实现可以使printList 以线性时间被执行,而fingKth操作则花费常数时间。插入和删除的开销却潜藏着隐藏的巨大开销。当插入或删除数据是表的第一个位置时,则需要将之后的数据都向后移动一个单位,而删除第一个元素,则需要将整个数组前移一个位置,即 O (N),这是最坏的情况。反之如果操作都发生在表的末端那么开销就是 O(1)。所以在高端插入或删除可以选择表,但是如果是其他位置插入或删除,这种表明显不是一个好的选择。

二、链表

1. 关于链表的一些描述

为了避免插入和删除的线形开销,那么就需要保证表可以不连续存储,否则就会出现整体移动的情况。链表的一般想法如图所示:

链表由一系列节点组成,这些节点不必在内存中相连。每一个节点均含有表元素和到包含该元素后继元的节点的链(link)。我们称为 next 链。最后一个单元的 next 链引用 null。

2. 链表的特点以及和表的不同之处

使用printLins 和 find(x)的操作显然也是线性的,但常数应该比数组实现的大些。findKthe 不如数组实现的效率高;findKth(i) 花费 O(i)的时间,并以这种方式遍历链表而完成。通常来说findKth(2),findKth(3),findKth(…)可以通过对链表的一次扫描完成。

3. remove 方法

该方法可以通过修改一个 next 引用来实现,如下图所示,删除 A2: 显然这个效率要比数组实现的效率要高得多。如果删除最后一项有些特殊,因为必须找到指向最后节点的项,把他的next 改成 null,在经典链表中,每个节点均有指向下一节点的链,而拥有指向最后节点的链并不提供最后节点的前驱节点的任何信息。

4. Insert 方法插入一个元素如下图所示。

5. 为了解决 3 中提到的问题,双链表诞生了,双链表与单链表不同的是,每个节点还会存储一个指向它在表中前驱节点的链。如下图所示

三、Java 中的应用

1. 包含普通数据结构的部分,在Java API中称为 Collections API

2. Collection 接口一些重要部分:

public interface Collection <AnyType> extends Iterable<AnyType> { int size(); bollean isEmpty() void clear(); boolean contains(AnyType x); boolean add(AnyType x); boolean remove (AnyType x); java.util.Iterator<AnyType> iterator(); }

Collection 接口扩展了 Iterable接口,实现Iterable接口的那些类可以拥有增强的 for 循环,该循环施加于这些类上一观察它们所有项。

3. Iterator 接口

public interface Iterator<AnyType> { boolean hasNext(); AnyType next(); void remove(); }

每次对 next 的调用都给出集合的下一项,hasNext 则用来告诉是否存在下一项

4. List 接口,ArrayList 类和 LinkedList 类

List 接口继承了 Collection 接口,因此它包含 Collection 接口的所有方法,外加其他一些方法。 public interface List<AnyType> extends Collection<AnyType> { AnyType get(int idx); AnyType set(int idx, AnyType newVal); void add(int idx, Anytype newVal); void remove(int idx); ListIterator<AnyType> listIterator(int pos); }

List ADT 有两种流行的实现方式,

a. ArrayList 类提供了 List ADT 的一种可增长数组的实现,其优点是对 get 和 set 调用花费常数时间,缺点是对新项的插入和现有项的删除代价昂贵,除非变动是在末端执行。

b. LinkedList 类则提供了List ADT 的双链表实现,使用它的优点在于,新项的插入和现有项的删除开销比较小,前提是变动项位置已知。缺点在于它不容易作为索引,所以get的调用代价昂贵。LinkedList 中提供了一些方法以便提高操作效率:addFirst(), removeFirst(), removeLast等。

c. 对搜索而言,两个都是低效的由于ArrayList可以自动增加数组大小,如果可以提前确定数组大小,那么用ensureCapacity 可以设置一个容量足够大的数组,以避免后边动态扩展。再有,trimToSize 可以在所有 ArrayList 添加操作完成之后使用以避免浪费空间。

d. 一个例子来说明两种实现方式: 现在假设将一个表中 6,5,1,4,2中的偶数删除,那么你该怎么做?最常见的方法是构建一个包含奇数的表,然后清除原表,再把新表拷贝给原表。但是该如何再不拷贝的情况下最大效率的完成这个任务呢?显然对于ArrayList来说,不适合解决这个问题,不过LinkedList却又希望解决这个问题。 解决的第一种办法:

public static void removeEvensVer1 (List<Integer> lst) { int i = 0; while(i < lst.size()) if( lst.get(i)%2 == 0) lst.remove(i); else i++; }

在一个ArrayList上前边提到过,remove的效率不是很高,因此该程序花费二次时间。但LinkedList暴露两个问题,首先对get调用效率不高,因此这个方法LinkedList也花费二次时间。 如果不用循环该用迭代器呢?

public static void removeEvensVer2(List<Integer> lst) { for(Integer x : lst) if(x % 2 == 0) lst.remove(i); }

初看貌似没什么问题,但是真正运行时会报错,为什么呢? ⚠️因为当一项被删除时,由增强for循环所使用的基础迭代器是非法的。 不过上边已经接近更优的方法了,在迭代器找到一个偶数后,我们可以使用该迭代器来删除这个刚看到的值。对于LinkedList来说花费时间变为常数,但是对于 ArrayList而言,花费时间依然是二次,因为需要删除数据。

public static void removeEvensVer3 (List<Integer> lst) { Interator<Integer> itr = lst.iterator(); while( itr.hasNext() ) if (itr.next()) //第一次调用next 返回第一个值,第二次调用返回第二个值 itr.remove(); }

经过计算,对于拥有400 000数据的操作中,LinkedList花费0.031s, 但是对于 ArrayList却花费了2.5分钟。可见正确的选择数据结构是多么重要。

5. 关于ListIterator 接口

Public interface ListIterator<AnyType> extends Iterator<AnyType> { boolean hasPrevious(); anyType previous(); void add(AnyType); void set(AnyType); }

总结

每一个有意义的程序都将至少使用一种这样的数据结构,而栈则在程序中总是要被间接地用到。 需要知道的知识点: 1. 什么是抽象数据类型(abstract data type, ADT)? 2. 阐述如何有效的执行表操作 3. 栈 ADT 及其在实现递归方面的应用 4. 队列 ADT 机器在操作系统和算法设计中的应用


《数据结构与算法分析–Java 语言描述》

最新回复(0)