表,栈和队列 —— 表

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


前言

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


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

一、表

1. 关于表的一些描述

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

2. 表的常用操作

  1. printList, makeEmpty。
  2. find 返回某一项首次出现的位置。
  3. insert 和 remove 一般是表的某个位置插入和删除某个元素。
  4. 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 类

  1. 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);
}
  1. 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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值