本节我们讨论常见常用的数据结构——表。

如果要通俗简单的说什么是表,那我们可以这样说:按顺序排好的元素集合就是表。

表的概述
抽象数据类型是带有一组操作的一些对象的结合

1、定义:
线性表是一个线性结构,它是一个含有n≥0个结点的有限序列,对于其中的结点,有且仅有一个开始结点没有前驱但有一个后继结点,有且仅有一个终端结点没有后继但有一个前驱结点,其它的结点都有且仅有一个前驱和一个后继结点。

2、特征/性质

1)集合中必存在唯一的一个第一个元素

2)集合中必存在唯一的一个最后元素

3)除最后一个元素之外,均有唯一的后继

4)除第一个元素之外,均有唯一的前驱

在上图中,a1是a2的前驱,ai+1 是ai的后继,a1没有前驱,an没有后继 ,n为线性表的长度 ,若n==0时,线性表为空表 ,下图就是一个标准的线性表

线性表分为如下几种:

  1. 顺序存储方式线性表

顺序存储方式线性表中,存储位置连续,可以很方便计算各个元素的地址
如每个元素占C个存储单元,那么有: Loc(An) = Loc(An-1) + C,于是有: Loc(An) = Loc(A1)+(i-1)*C;

优点:查询很快
缺点:插入和删除效率慢

下图很形象的表现了,插入和删除慢的特点

表的简单数组实现
顺序存储方式线性的典型就是数组,对于表的所有操作都可以通过使用数组来实现。虽然数组创建时就已经是固定大小,但在需要的使用可以用双倍的容量创建一个不同的数组。下面是扩容的伪代码:

int[] aar = new int[10];//扩大aarint[] newArr = new int[aar.length * 2];for (int i = 0; i < aar.length; i++) {newArr[i] = aar[i];}aar = newArr;

数组的实现使得printList以线性时间被执行,而findKth(返回特定位置上的元素)则花费常数的时间。
最坏的情形是,在位置0插入一个元素,需要将数组中所有元素向后移动一个位置,而删除一个元素,则需要将所有元素向前移动一个位置,两种情况复杂度都是O(n)。平均来看,两种操作都需要移动表一半的元素,因此需要线性时间,但是如果插入和删除都发生在数组的最尾,则插入和删除都只需要花费O(1)的时间。

如果频繁的插入和删除发生在表的最前端,则使用链表会更好。

2.链式存储方式线性表
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的

优点:相对于数组,删除还插入效率高
缺点:相对于数组,查询效率低

要执行插入操作,只需要如下的代码:

s->next = p->next
p-next = s ; 

执行删除操作,只需要如下的代码:

p->next = p->next->next 

3.循环链表

将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相连的单链表称为单循环链表,简称循环链表

4.双向循环链表
双向循环链表是单向循环链表的每个结点中,再设置一个指向其前驱结点的指针域

4.1对于空的双向循环链表

双向循环链表插入

Java Collection Api中的表

1.Iterator

Iterator接口的思路,通过Iterator方法,每个集合均创建并返回给客户一个实现Iterator接口的对象,并将当前位置的概念在对象内部存储下来。

public interface Iterator<E> {boolean hasNext();E next();default void remove() {throw new UnsupportedOperationException("remove");}}

Iterator中的方法有限,因此,很难使用Iterator做遍历Collection意外的任何工作。Iterator还包含一个remove()方法。该方法的作用是删除next()最新返回的项(此后不能再调用remove(),直到你下一次调用next())。

如果对正在被迭代的集合进行结构上的改变(即对该集合使用add,remove或clear),那么迭代器将不再合法(并且在其后使用该迭代器将出现ConcurrentModificationException异常被抛出),为了避免迭代器准备给出某一项作为下一项而该项此后或者被删除,所以只有在需要立即使用一个迭代器的时候,我们才应该获取迭代器。然而,如果迭代器调用了它自己的remove方法,那么这个迭代器就仍然合法的。

2.List接口

public interface List extends Collection {

int size();boolean isEmpty();Iterator<E> iterator();Object[] toArray();<T> T[] toArray(T[] a);boolean add(E e);boolean remove(Object o);boolean containsAll(Collection<?> c);boolean addAll(Collection<? extends E> c);boolean addAll(int index, Collection<? extends E> c);boolean removeAll(Collection<?> c);boolean retainAll(Collection<?> c);void clear();boolean equals(Object o);int hashCode();E get(int index);E set(int index, E element);void add(int index, E element);E remove(int index);int indexOf(Object o);int lastIndexOf(Object o);ListIterator<E> listIterator();

}

List ATD有两种流行的实现方式,ArrayList和LinkedList。

ArrayList的优点是,get和set调用花费常数时间。缺点是新项的插入和现有项的删除代价昂贵,除非变动的是ArrayList的末端。

LinkedList优点是在表的前端添加和删除都是常数时间,缺点是不容易作索引,get的调用是昂贵的,除非是接近表的端点

public static void makeList1(List<Integer> lst,int n){lst.clear();for (int i = 0; i < n; i++) {lst.add(i);}}

不管ArrayList还是LinkedList作为参数被传递,makeList1的运行时间都是O(N),因为对add的每次调用都是在表的末端进行从而花费常数时间(可以忽略对ArrayList偶尔扩展)。另一方面,如果我们通过在表的前端添加一些项来构造一个List:

 public static void makeList2(List<Integer> lst,int n){lst.clear();for (int i = 0; i < n; i++) {lst.add(i);}}

对于LinkedList它的运行时间是O(N),但是对于ArrayList其运行时间则是O(n^2),因为在ArrayList中,在前端进行添加是一个O(N)操作。

 public static int sum(List<Integer> lst){int total = 0;for (int i = 0; i < n; i++) {total+=lst.get(i);}return total;}

这里,ArrayList的运行时间是O(N),但对于LinkedList来说,其运行时间则是O(n^2),因为LinkedList中,对get的调用为O(N)操作。可是,要是使用一个增强的for循环,那么它对任意List的运行时间都是O(N),因为迭代器将有效地从一项到下一项推进。

对搜索而言,ArrayList和LinkedList都是低效,对Collection的contains和remove方法调用均花费线性时间。

例子:remove方法对LinkedList类的使用
例子1:假设现在有6,5,1,4,2五个数,需要在方法调用之后去除所有的偶数。

思路:
1.创建一张包含所有奇数的新表,清除原表,再将奇数拷贝回去。
2.直接在原表中进行遍历,遇到偶数时直接进行移除。

ArrayList和LinkedList针对于remove都是低效的,在LinkedList中,到达i位置的代价是昂贵的。

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

对于LinkedList来说,上面的解法运行时间则是O(n^2),使用迭代器的效率会更好,当然在使用迭代器时,我们不能直接使用List的 remove,否则会抛出异常,就像下面的写法(增强for循环底层还是用的迭代器)

public static void removEventVer2(List<Integer> lst) {for (Integer x : lst) {if (x % 2 == 0) {lst.remove(x);     //这种方式会报错,下面的方式itr.remove()则不会报错}}}

原因:

Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。

所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

为了解决上面的问题,我们可以直接使用迭代器的remove方法,这样做是合法的

public static void removEventVer3(List<Integer> lst) {Iterator<Integer> itr = lst.iterator();while (itr.hasNext()){if (itr.next()%2==0){itr.remove();}}}

使用了Iterator以后,LinkedList的remove操作消耗的就是O(n)时间,因为Iterator已经位于需要被删除的节点上。
而即使使用Iterator,ArrayList的remove方法还是O(n^2),因为删除,数组的数还是需要进行移动。

3.ListIterator接口

ListIterator接口扩展了Iterator,hasNext和hasPrevious方法,使得既可以从前遍历也可以从尾巴进行遍历,add在当前位置插入一个新的项,set方法改变Iterator调用hasNext或hasPrevious返回的当前值。

public interface ListIterator<E> extends Iterator<E> {boolean hasNext();boolean hasPrevious();void remove();void set(E e);void add(E e);

实现一个ArrayList
下面,我们自己手写一个ArrayList,且支持泛型,代码如下:

public class MyArrayList<T> implements Iterable<T> {private static final int DEFAULT_CAPACITY = 10;private T[] mArray;private int mArraySize;@Overridepublic Iterator<T> iterator() {return new ArrayIterator();}private class ArrayIterator implements Iterator<T> {private int currentPositon;@Overridepublic boolean hasNext() {return currentPositon < mArraySize;}@Overridepublic T next() {if (!hasNext()) {throw new NoSuchElementException();}return mArray[currentPositon++];}@Overridepublic void remove() {MyArrayList.this.remove(--currentPositon);}}public void trimTosize() {ensureCapacity(size());}public int size() {return mArraySize;}public boolean isEmpty() {return mArraySize == 0;}public MyArrayList(int size) {if (size < DEFAULT_CAPACITY) {mArraySize = size;} else {ensureCapacity(DEFAULT_CAPACITY);}}private void ensureCapacity(int newCapacity) {T[] newArray = (T[]) new Object[newCapacity];for (int i = 0; i < mArray.length; i++) {newArray[i] = mArray[i];}mArray = newArray;}public boolean add(T t) {add(t, mArraySize);return true;}public void add(T t, int position) {if (mArraySize == mArray.length) {ensureCapacity(mArraySize * 2 + 1);}for (int i = position; i < mArraySize - 1; i++) {mArray[i + 1] = mArray[i];}mArray[position] = t;++mArraySize;}public T reomve() {return remove(mArraySize);}private T remove(int position) {T t = mArray[position];for (int i = position; i < mArraySize - 1; i++) {mArray[i] = mArray[i + 1];}--mArraySize;return t;}public T get(int position) {if (position < 0 || position > mArraySize) {throw new ArrayIndexOutOfBoundsException();}return mArray[position];}public T set(T t) {return set(t, mArraySize - 1);}public T set(T t, int position) {if (position < 0 || position > mArraySize) {throw new ArrayIndexOutOfBoundsException();}T old = mArray[position];mArray[position] = t;return old;}
}

值得一提的是,我们不能直接new T[],而是需要通过下面的代码创建一个泛型的数组

T[] newArray = (T[]) new Object[newCapacity];

还有一点值得说明的是,在ArrayIterator中使用MyArrayList.this.remove是为了避免和迭代器自身的remove冲突

 @Overridepublic void remove() {MyArrayList.this.remove(--currentPositon);}

实现LinkedList

在LinkedList中,最前端的节点叫做头节点,最末端的节点叫做尾节点。这两个额外的节点的存在,排除许多特殊情况,极大简化了编码。
例如:如果不使用头节点,那么删除第一个节点就是特殊情况,因为在删除时需要重新调整链表到第一个节点的链,还因为删除算法一般还要访问被删除节点前面的那个节点(如果没有头节点的话,第一个节点就会出现前面没有节点的特殊情况)。

public class MyLinkedList<T> implements Iterable<T> {private Node<T> headNote;private Node<T> endNote;private int mSize;private int modCount;public MyLinkedList() {init();}private void init() {headNote = new Node<>(null, null, null);endNote = new Node<>(null, headNote, null);headNote.mNext = endNote;mSize = 0;modCount++;}public int size() {return mSize;}public boolean isEmpty() {return mSize == 0;}public boolean add(T t) {addBefore(t, size());return true;}public T get(int index) {Node<T> temp = getNode(index, 0, size());return temp.mData;}public T remove(int position) {Node<T> tempNode = getNode(position);return remove(tempNode);}private T remove(Node<T> tempNode) {tempNode.mPre.mNext = tempNode.mNext;tempNode.mNext.mPre = tempNode.mPre;mSize--;modCount++;return tempNode.mData;}public T set(int index, T t) {Node<T> tempNode = getNode(index);T old = tempNode.mData;tempNode.mData = t;return old;}private Node<T> getNode(int index) {return getNode(index, 0, size() - 1);}private Node<T> getNode(int index, int lower, int upper) {Node<T> tempNode;if (lower < 0 || upper > mSize) {throw new IndexOutOfBoundsException();}if (index < mSize / 2) {tempNode = headNote.mNext;for (int i = 0; i < index; i++) {tempNode = tempNode.mNext;}} else {tempNode = endNote;for (int i = mSize; i > index; i--) {tempNode = tempNode.mPre;}}return tempNode;}private static class Node<T> {private Node<T> mNext;private T mData;private Node<T> mPre;public Node(T data, Node<T> pre, Node<T> next) {mData = data;mPre = pre;mNext = next;}}private class LinkedListIterator implements Iterator<T> {private Node<T> currentNode = headNote.mNext;private int expectedModCount = modCount;private boolean okToMove;@Overridepublic boolean hasNext() {return currentNode != endNote;}@Overridepublic T next() {if (modCount != expectedModCount) {throw new ConcurrentModificationException();}if (!hasNext()) {throw new NoSuchElementException();}T t = currentNode.mData;currentNode = currentNode.mNext;okToMove = true;return t;}@Overridepublic void remove() {if (modCount != expectedModCount) {throw new ConcurrentModificationException();}if (!okToMove) {throw new IllegalStateException();}MyLinkedList.this.remove(currentNode.mPre);expectedModCount++;okToMove = false;}@Overridepublic Iterator<T> iterator() {return new LinkedListIterator();}
}

1.modCount代表自从构造以来对链表所做改变的次数。每次对add或remove的调用都将更新modCount。想法在于,当一个迭代器被建立时,他将存储集合的modCount。每次个迭代器方法(next或remove)的调用都将该链表内的当前modCount检测在迭代器内存储的modCount,并且当两个计数不匹配时,抛出一个ConcurrentModificationException异常。

2.在LinkedListIterator中,currentNode表示包含由调用next所返回的项的节点。注意,当currentNode被定位在endNote,对next调用是非法的。

在LinkedListIterator的remove方法中,currentNode是保持不变的,因为currentNode节点不受前面节点被删除的影响,与ArrayIterator不同,(在ArrayIterator中,项被移动,要求更新current)

参考书籍:
《数据结构与算法分析》

转载地址:
http://blog.csdn.net/u012124438/article/details/77618861

Java数据结构与算法解析(一)——表相关推荐

  1. java数据结构与算法之顺序表与链表深入分析

    转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/52953190 出自[zejian的博客] 关联文章: java数据结 ...

  2. Java数据结构和算法:线性表

    线性表的定义 线性表(linear-list)是最常用最简单的一种数据结构.一个线性表是n (n≥0)个相同类型数据元素的有限序列.记为: L= (a1, a2 , - , an ). 其中,L是表名 ...

  3. Java数据结构与算法_线性表_顺序表与链表

    文章目录 线性表 顺序表 顺序表API设计 顺序表的代码实现 链表 单向链表 双向链表 总结 线性表 概述 线性表是最基本.最简单.也是最常用的一种数据结构. 一个线性表是n个具有相同特性的数据元素的 ...

  4. Java数据结构与算法解析(二)——栈

    栈是限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫做栈顶.对栈的基本操作有push(进栈)和pop(出栈),对空栈进行push和pop,一般被认为栈ADT的一个错误.当push时空间用尽 ...

  5. java数据结构与算法之双链表设计与实现

    转载请注明出处(万分感谢!): http://blog.csdn.net/javazejian/article/details/53047590 出自[zejian的博客] 关联文章: java数据结 ...

  6. java数据结构与算法之(Queue)队列设计与实现

    [版权申明]转载请注明出处(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/53375004 出自[zejian的博客] ...

  7. Java数据结构和算法(第二版)

    Java数据结构和算法(第二版) 下载地址 https://pan.baidu.com/s/112D5houIgu0eMs_i5o0Ujw 扫码下面二维码关注公众号回复 100066获取分享码 本书目 ...

  8. 数据结构 - Java -韩顺平 图解Java数据结构和算法

    数据结构 Lesson 1 数据结构的知识总结 1. 几个经典的算法面试题 2. 线性结构与非线性结构 2.1 稀疏数组 sparsearray 2.2 队列 2.2.1 顺序队列: 2.2.2 环形 ...

  9. Java数据结构与算法学习 目前30170字

    文章借鉴于[尚硅谷]数据结构与算法(Java数据结构与算法),在内容方面会做更改,但是核心依然是他们的学习视频.在这里声明. 1. 线性结构和非线性结构 1.1 线性结构 数据结构包括两大部分,一个叫 ...

最新文章

  1. 黑客基础知识与防护(一)
  2. 线下教育地位遭冲击?“AI+教育”公司同台讲了这些事实
  3. 傅里叶变换才是本质?谷歌这项研究GPU上快7倍、TPU上快2倍
  4. 概览屏幕(最新动态屏幕、最近任务列表)
  5. aix cpu java_AIX cpu理解
  6. python杨辉三角_干货|杨辉三角与二项式定理
  7. keil 函数 默认 外部 内部 博客_5.9 C++内部函数与外部函数
  8. Ubuntu 16.04下Caffe-SSD的应用——常见训练时报错总结
  9. 快评《19家网站内容低俗被曝光》
  10. 服务器php 启动命令_服务端的cli方式运行
  11. java map 泛型 反射_Java通过反射读取泛型
  12. 大量开发者会将访问token和API密钥硬编码至Android应用
  13. 多进程服务器(python 版)
  14. 你自己的事,你不操心谁操心?
  15. 题目1168:字符串的查找删除(字符串操作)
  16. tp-03 模板显示
  17. 最难的几道Java面试题,看看你跪在第几个?
  18. mysql 存储过程 高并发_解决数据库高并发常见方案
  19. matlab 安装matpower,MATPOWER的安装详细教程
  20. java数独求交集方法,标准数独解题之旅(用一道数独题讲解最基本的5种解题技巧)(二)...

热门文章

  1. 已有项目要不要迁移到Addressable系统?
  2. python格式化字符串漏洞_Python字符串格式化的方法(两种)
  3. 在线闹钟html代码复制,html5时钟实现代码
  4. OpenShift 4 - 锁定被保护的 OpenShift 资源,禁止删除和修改操作
  5. (三)用于构建AI语言翻译系统的工具
  6. ASP.NET Gridview的简单的Bootstrap分页
  7. html正方形对话框素材,10种展示效果的弹出层对话框插件method.js
  8. vue createApp(),mount(),生命周期钩子函数执行顺序
  9. wap_list.php,织梦DEDECMS生成静态手机页面
  10. canvas 两个圆相交重叠区域颜色填充_「译」Canvas中的环绕规则 -Winding rules in Canvas...