JAVA实现具有迭代器的线性表(单链表)
一,迭代器的基本知识:
1,为什么要用迭代器?(迭代:即对每一个元素进行一次“问候”)
比如说,我们定义了一个ADT(抽象数据类型),作为ADT的一种实现,如单链表。而单链表的基本操作中,大部分需要用到依次遍历单链表中的每一个元素。一般而言,我们就是用for循环来实现遍历,这样,当你新增一个对单链表的操作并需要使用遍历时,你就得重新写一个for循环而实现遍历。那么,为什么不将迭代(遍历)作为一种基本的ADT操作(基本的ADT操作如:新增一个元素、删除一个元素)呢?于是,迭代器就出场了。
2,鉴于上述迭代器的用途,我们可以知道:迭代器是用来遍历元素的,那么说明这个数据结构应该有一组“相同”类型的元素(如,单链表、数组……)。因此,在JAVA中,为什么在学习集合(Collection)时,经常会有迭代器(Iterator)概念。
3,JAVA中实现迭代器的方式有几种?
主要有两种:a,定义一个公共类并 implements Iterator<T>接口;同时,该类与实现ADT的类(如本例中的 LinkListWithIterator<T>)分开。当然,这两个类还需要以某种方式交互。因为,迭代器需要知道我迭代的东东到底是谁嘛。下面代码粗糙描述了公共的实现迭代器的类:SeparateIterator<T>如何与实现ADT的类交互。
public Interface ListInterface<T>{ //此处定义了链表的基本操作 } public class LinkList<T> implements ListInterface<T>{ //LinkList类实现了ADT //此处实现链表的基本操作 } public class SeparateIterator<T> implements Iterator<T> { //此处实现了Iterator接口中的方法 } //将实现了ADT类(即,LinkList)的对象作为构造函数的参数传入到实现迭代器类的构造方法中 //从而实现它们之间的交互 Iterator<T> it = new SeparateIterator<T>(LinkList实例);
b,在实现ADT的类中定义一个私有内部类,该私有内部类实现迭代器的功能,使用私有内部类的好处是,在该私有内部类中可以直接访问待迭代元素的数据域(具体看下面代码中的注释),效率较高。以下例子就是这种方式。
4,如何为自定义的数据结构实现一个迭代器?
在JAVA类库中java.util.ArrayList<T> 类代表存储一组“相同”类型的数据元素的数组,ArrayList类从其父类AbstractList中继承了 iterator() 方法来返回一个可以对存放在ArrayList中的元素进行迭代的迭代器。那万一根据需要,需要自定义了一个数据结构,又如何给该数据结构实现迭代器呢?下面以单链表为例进行说明:
二,具体的实例的代码
1,定义一个ListInterface<T>接口(相当于一个ADT),指定了单链表中的各种基本操作:
public interface ListInterface<T> { //返回当前链表中元素个数 public int length(); ////获得索引为index处的元素值,注意:索引从0开始 public T get(int index); //返回链表中指定元素的索引,若指定元素不存在,返回-1 public int locate(T element); //在指定索引index处添加指定的element public void insert(T element, int index); //采用尾插法为链表增加新节点 public void add(T element); //采用头插法为链表增加新节点 public void addAtHeader(T element); //删除指定索引处的节点 public T delete(int index); //删除线性表中的最后一个元素 public T remove(); //清空线性表 public void clear(); }
2,定义ListWithIteratorInterface<T>接口并 extends ListInterface<T>,这样就可以将迭代器与ADT类联系起来。因为,实现ADT的类LinkListWithIterator<T> implements ListWithIteratorInterface<T>,从而以ListWithIteratorInterface为中界,将LinkListWithIterator(带有迭代器的ADT)与ListInterface<T>(线性表)联系起来了。进而,使得LinkListWithIterator<T>是一个:带有迭代器的单链表了。
这样,就可以通过①LinkListWithIterator<T>的对象调用 getIterator() 获得一个迭代器,②而该迭代器类又是LinkListWithIterator<T>的内部类,即可以直接迭代LinkListWithIterator的数据域。由①②,LinkListWithIterator<T>就表示一个带有迭代器的单链表了。
import java.util.Iterator; public interface ListWithIteratorInterface<T> extends ListInterface<T>{ public Iterator<T> getIterator(); }
3,定义了一个类LinkListWithIterator<T>实现了ListInterface<T>接口(相当于ADT的实现类),LinkListWithIterator<T>中定义了一个内部类:IteratorForLinkedList,该内部类 implements Iterator<T>,
import java.util.Iterator; import java.util.NoSuchElementException; public class LinkListWithIterator<T> implements ListWithIteratorInterface<T>{ private class Node{ private T data; private Node next; private Node(){ } private Node(T data, Node next){ this.data = data; this.next = next; } } /* * 该内部类用来实现迭代器,从而能够对单链表进行迭代 * 使用内部类好处:能够直接访问ADT(抽象数据类型,如此例中的链表)的数据域,迭代效率高 */ private class IteratorForLinkedList implements Iterator<T>{ private Node nextNode;//Node类型是通过私有内部类定义的表示链表结点的一种数据类型 private IteratorForLinkedList() { nextNode = header;//nextNode 用来跟踪迭代,将之初始化为头结点 } public boolean hasNext() { return nextNode != null; } public T next() { if(hasNext()){ Node returnNode = nextNode; nextNode = nextNode.next;//其实迭代器的实现方法就是按照遍历链表的方式 来实现的 return returnNode.data; } else throw new NoSuchElementException("Illegal call to next();" + "iteration is after end of list."); } //由于Iterator<T>接口中定义了remove()方法,由于 implements ,这里必须实现remove() //但这里并没有真正支持在迭代器操作下对单链表进行删除,而是抛异常 public void remove() { throw new UnsupportedOperationException("remove() is not "+ "supported by this iterator"); } } public Iterator<T> getIterator(){ return new IteratorForLinkedList(); } private Node header;//保存链表的头结点 private Node tail;//保存链表的尾结点,采用尾插法添加结点时,不需要遍历整个链表 private int size;//保存链表中已包含的节点数 public LinkListWithIterator(){ header = tail = null;//从构造器可以看出,些链表是一个不带表头结点的单链表 } public LinkListWithIterator(T element){ header = new Node(element, null); tail = header; size++; } public int length(){ return size; } //获得索引为index处的元素值,注意:索引从0开始 public T get(int index){ return getNodeByIndex(index).data; } private Node getNodeByIndex(int index){ if(index < 0 || index > size - 1) throw new IndexOutOfBoundsException("单链表越界"); Node current = header; for(int i = 0; (i < size) && (current != null); i++, current = current.next) if(i == index) return current; return null; } //返回链表中指定元素的索引,若指定元素不存在,返回-1 public int locate(T element){ Node current = header; for(int i = 0; i < size && current != null; i++, current = current.next) if(current.data.equals(element)) return i; return -1; } public void insert(T element, int index){ if(index < 0 || index > size) throw new IndexOutOfBoundsException("单链表索引越界"); if(header == null)//链表是空链表时 add(element); else{ if(index == 0)//在表头插入 addAtHeader(element); else{ Node prev = getNodeByIndex(index - 1);//获取插入结点的前驱 prev.next = new Node(element, prev.next); size++; } } } //采用尾插法为链表增加新节点 public void add(T element){ if(header == null){ header = new Node(element, null); tail = header; } else{ Node newNode = new Node(element, null); tail.next = newNode; tail = newNode; } size++; } //采用头插法为链表增加新节点 public void addAtHeader(T element){ header = new Node(element, header);//新建的结点的next 需要指向 header结点 if(tail == header)//如果插入之前是空链表 tail = header; size++; } public T delete(int index){ if(index < 0 || index > size - 1) throw new IndexOutOfBoundsException("链表索引越界"); Node del; //待删除的是header节点 if(index == 0){ del = header; header = header.next; } else{ Node prev = getNodeByIndex(index - 1);//获取待删节点的前驱 del = prev.next;//del 指向待删除的节点 prev.next = del.next; } del.next = null;//将待删节点从链表中脱离出去 size--; return del.data; } //根据指定的元素来删除节点 public boolean deleteByElement(T element){ //链表为空 if(empty()){ return false; } //待删元素为第一个元素 else if(header.data.equals(element)){ Node del = header; header = header.next; if(tail == header)//说明整个链表中只有一个元素,tail也应当置null tail = null; del.next = null; size --; return true; } //待删元素为链表中其他元素 else{ Node del = header.next; Node prev = header; while(del != null){ if(del.data.equals(element)){ if(tail == del)//如果待删元素是最后一个元素,需要将tail指针指向其前驱 tail = prev; prev.next = del.next; del.next = null; size--; return true; } //若没有找到element,则继续下一轮的循环 prev = del; del = del.next; } return false; } } //删除链表中的最后一个元素 public T remove(){ return delete(size - 1); } //判断链表是否为空 public boolean empty(){ boolean result; if(size == 0){ assert header == null;//当出现链表为空,但size不为0时,使用断言能够帮助找到逻辑错误 result = true; } else{ assert header != null; result = false; } return result; //return size == 0; } //清空链表 public void clear(){ header = tail = null; size = 0; } public String toString(){ if(empty()) return "[]"; else{ StringBuilder sb = new StringBuilder("["); for(Node current = header; current != null; current = current.next) sb.append(current.data.toString() + ", "); int len = sb.length(); //注意删除最后添加的两个多余的字符 return sb.delete(len - 2, len).append("]").toString(); } } }
最后,该程序可以作为 不带表头结点的链表 如何进行插入(头插法,尾插法、指定位置进行插入)、删除……一些基本操作的参考。
JAVA实现具有迭代器的线性表(单链表)的更多相关文章
- JAVA实现具有迭代器的线性表(顺序表)
1,先了解下JAVA类库中的迭代器:JAVA提供了两种基本类型的迭代器,分别用两个接口来表示:Iterator<T>,ListIterator<T>.其中,Iterator&l ...
- Python线性表——单链表
1. 线性表简介 线性表是一种线性结构,它是由零个或多个数据元素构成的有限序列.线性表的特征是在一个序列中,除了头尾元素,每个元素都有且只有一个直接前驱,有且只有一个直接后继,而序列头元素没有直接前驱 ...
- 数据结构之 线性表---单链表的操作B(先逆序+再删除重复元素)
数据结构上机测试2-2:单链表操作B Time Limit: 1000MS Memory limit: 65536K 题目描述 按照数据输入的相反顺序(逆位序)建立一个单链表,并将单链表中重复的元素删 ...
- 数据结构之 线性表---单链表操作A (删除链表中的指定元素)
数据结构上机测试2-1:单链表操作A Time Limit: 1000MS Memory limit: 4096K 题目描述 输入n个整数,先按照数据输入的顺序建立一个带头结点的单链表,再输入一个数据 ...
- java模拟表单上传文件,java通过模拟post方式提交表单实现图片上传功能实例
java模拟表单上传文件,java通过模拟post方式提交表单实现图片上传功能实例HttpClient 测试类,提供get post方法实例 package com.zdz.httpclient; i ...
- C语言 严蔚敏数据结构 线性表之链表实现
博主最近在考成都大学皇家计算机科学与技术专业,复习专业课数据结构,正好学习到线性结构中的线性表用链表这种存储结构来实现. 首先,数据结构包括1.数据的操作2.逻辑结构3.存储结构(数据结构三要素. 直 ...
- 【Java】 大话数据结构(2) 线性表之单链表
本文根据<大话数据结构>一书,实现了Java版的单链表. 每个结点中只包含一个指针域的链表,称为单链表. 单链表的结构如图所示: 单链表与顺序存储结构的对比: 实现程序: package ...
- Java核心技术及面试指南 线性表方面的面试题总结以及答案
3.2.7.1 请用ArrayList实现Stack以及Queue的功能. public class ArrayListStack extends ArrayList implements Stack ...
- 【Java】 大话数据结构(5) 线性表之双向链表
本文根据<大话数据结构>一书,实现了Java版的双向链表. 在每个数据结点中都有两个指针,分别指向直接后继和直接前驱,这样的链表称为双向链表. 双向链表的结构如图所示: 查找元素可以根据元 ...
随机推荐
- [微软官网]windows server 内存限制
Memory Limits for Windows and Windows Server Releases https://docs.microsoft.com/zh-cn/windows/deskt ...
- Linux标准输入、输出和错误和文件重定向 专题
当我们在shell中执行命令的时候,每个进程都和三个打开的文件相联系,并使用文件描述符来引用这些文件.由于文件描述符不容易记忆,shell同时也给出了相应的文件名. 下面就是这些文件描述符及它们通常所 ...
- Windows下 使用命令行的方式 设置主机的ip地址. 以及设置多ip地址的方法
1. 首先要查看一下网卡的设备名称 netsh interface ip show interfaces 结果为: 记住当前的网卡名称 进行后续操作. 其实 也可以通过 ipconfig /all 的 ...
- [转帖]数据中心网络里的Underlay和Overlay
数据中心网络里的Underlay和Overlay https://blog.csdn.net/zjc801blog/article/details/54289683 2017年01月09日 15:47 ...
- PostgreSQL之性能优化(转)
转载自:https://blog.csdn.net/huangwenyi1010/article/details/72853785 解决问题 前言 PostgreSQL的配置参数作为性能调优的一部分, ...
- rabbitmq线上服务器与项目结合的问题总结
一.特殊字符需要转义 只需要加个\反斜杠就可以了 二.zk的connectString 在rabbit web页面上登录上去,新增queue就可以了
- BZOJ4998星球联盟——LCT+并查集(LCT动态维护边双连通分量)
题目描述 在遥远的S星系中一共有N个星球,编号为1…N.其中的一些星球决定组成联盟,以方便相互间的交流.但是,组成 联盟的首要条件就是交通条件.初始时,在这N个星球间有M条太空隧道.每条太空隧道连接两 ...
- BZOJ2863[SHOI2012]魔法树——树链剖分+线段树
题目描述 输入 输出 样例输入 4 0 1 1 2 2 3 4 Add 1 3 1 Query 0 Query 1 Query 2 样例输出 3 3 2 树链剖分模板题,路径修改子树查询,注意节点 ...
- BZOJ4445 SCOI2015小凸想跑步(半平面交)
考虑怎样的点满足条件.设其为(xp,yp),则要满足(x0-xp,y0-yp)×(x1-xp,y1-yp)<=(xi-xp,yi-yp)×(xi+1-xp,yi+1-yp)对任意i成立.拆开式子 ...
- Android Studio中Git和GitHub使用详解
一.Git和GitHub简述 1.Git 分布式版本控制系统,最先使用于Linux社区,是一个开源免费的版本控制系统,功能类似于SVN和CVS.Git与其他版本管理工具最大的区别点和优点就是分布式: ...