链表(Java描述)
来源:互联网 发布:ug钻孔编程后处理 编辑:程序博客网 时间:2024/06/06 11:10
原文地址:http://www.cs.cmu.edu/~adamchik/15-121/lectures/Linked%20Lists/linked%20lists.html
引言
使用数组来存储数据的一个缺点在于,数组是静态结构,因此不能很容易地扩展或缩小以适合数据集。对数组进行插入和删除的代价也比较高。本文考虑一种名为链表的数据结构,解决了数组的一些局限性。
链表是一种线性数据结构,每个元素都是一个单独的对象。
链表的每个元素(称之为一个节点)包括两个部分—数据域和指针域(链接到下一个节点)。最后一个节点链接到null。链表的起点称为头结点。应该注意的是,头结点不是一个单独的节点,而是链接到第一个节点。如果链表为空,则头结点为空引用。
链表是一种动态数据结构。一个链表中的节点数不是固定的,可按需增多和减少。任何要处理未知数目对象的应用需要使用链表。
链表的一个缺点是它不允许直接访问各个元素。如果要访问一个特定的元素,必须从头结点开始,沿着链接,直到得到该元素。
与数组相比,链表的另一个缺点是它占用更多的内存—需要额外的4个字节(32位CPU)来存储到下一个节点的引用。
链表的类型
单链表如上所述。
双向链表有两个引用,一个链接到下一个节点,另一个链接到前面的节点。
链表的另一种重要类型称为循环链表,其中链表的最后一个节点指向第一个节点(或头结点)。
结点类
在Java中,可以在一个类(例如A)的内部定义另一个类(例如B)。A类称为外部类,B类称为内部类。内部类的目的纯粹是为了在内部作为辅助类。下面是具有内部节点类的LinkedList类:
private static class Node<AnyType>{ private AnyType data; private Node<AnyType> next; public Node(AnyType data, Node<AnyType> next) { this.data = data; this.next = next; }}
一个内部类是它的外部类的成员,并可访问外部类的其他成员(包括私有成员),反之亦然,即外部类可以直接访问内部类的所有成员。一个内部类可以声明为私有,公共,保护或包权限。有两种类型的内部类:静态的和非静态的。静态内部类不能直接引用实例变量或在其外部类中定义的方法,它只能通过对象引用来使用它们。
我们用两个内部类来实现LinkedList类:静态节点类和非静态LinkedListIterator类。完整的实现参看LinkedList.java。
实例
对于上面的单链表,跟踪各片段,效果如下。在每一行执行之前链表恢复到其初始状态。
1. head = head.next;
2. head.next = head.next.next;
3. head.next.next.next.next= head;
链表操作
addFirst
该方法创建一个节点,并把它加在链表的开头。
public void addFirst(AnyType item){ head = new Node<AnyType>(item, head);}
遍历
从头结点开始,访问每个节点,直到到达null。不要更改头结点。
Node tmp = head;while(tmp != null) tmp = tmp.next;
addLast
该方法将节点加到链表的末尾。这需要遍历,但要确保停在最后一个节点。
public void addLast(AnyType item){ if(head == null) addFirst(item); else { Node<AnyType> tmp = head; while(tmp.next != null) tmp = tmp.next; tmp.next = new Node<AnyType>(item, null); }}
结点后插入
查找包含“key”的节点,在其后插入一个新的节点。在下图中,我们在“E”之后插入一个新的节点:
public void insertAfter(AnyType key, AnyType toInsert){ Node<AnyType> tmp = head; while(tmp != null && !tmp.data.equals(key)) tmp = tmp.next; if(tmp != null) tmp.next = new Node<AnyType>(toInsert, tmp.next);}
结点前插入
查找包含“key”的节点,在该节点之前插入一个新的节点。在下图中,我们在“A”之前插入一个新的节点:
为方便起见,维护两个引用(reference)prev 和cur。沿着链表移动,移动这两个引用,保持prev在cur的前一步。继续下去,直到cur到达需要在其之前插入的节点。如果cur到达null,我们不插入,否则在prev和cur之间插入一个新的节点。
观察这个实现:
public void insertBefore(AnyType key, AnyType toInsert){ if(head == null) return null; if(head.data.equals(key)) { addFirst(toInsert); return; } Node<AnyType> prev = null; Node<AnyType> cur = head; while(cur != null && !cur.data.equals(key)) { prev = cur; cur = cur.next; } //insert between cur and prev if(cur != null) prev.next = new Node<AnyType>(toInsert, cur);}
删除
查找包含"key"的节点,并删除它。在下图中我们删除包含“A”的节点。
该算法类似于在结点前插入的算法。使用两个引用prev和cur较为方便。沿着链表移动,移动这两个引用,保持prev在cur的前一步。继续下去,直到cur到达我们需要删除的节点。我们需要考虑三种特殊情况:
1 链表为空
2 删除头节点
3 节点不在链表中
public void remove(AnyType key){ if(head == null) throw new RuntimeException("cannot delete"); if( head.data.equals(key) ) { head = head.next; return; } Node<AnyType> cur = head; Node<AnyType> prev = null; while(cur != null && !cur.data.equals(key) ) { prev = cur; cur = cur.next; } if(cur == null) throw new RuntimeException("cannot delete"); //delete cur node prev.next = cur.next;}
迭代器
迭代器的思想是提供访问私有聚合数据的方式,同时隐藏底层表示。Java迭代器是一个对象,因此它的实现需要创建一个实现了Iterator 接口的类。通常这样的类被实现为一个私有内部类。该Iterator接口包含下列方法:
- AnyType next() - 返回容器中的下一个元素
- boolean hasNext() - 检查是否存在下一个元素
- void remove() -(可选操作)。删除由next()返回的元素
在本节中,我们在LinkedList类中实现了Iterator。首先在LinkedList类中添加一个新的方法:
public Iterator<AnyType> iterator(){ return new LinkedListIterator();}
这里LinkedListIterator 是LinkedList类中的私有类。
private class LinkedListIterator implements Iterator<AnyType>{ private Node<AnyType> nextNode; public LinkedListIterator() { nextNode = head; } ...}
该LinkedListIterator类必须提供next()和hasNext()方法的实现。next()方法如下:
public AnyType next(){ if(!hasNext()) throw new NoSuchElementException(); AnyType res = nextNode.data; nextNode = nextNode.next; return res;}
复制(Cloning)
像任何其他对象一样,我们需要学习如何复制链表。如果简单地使用Object类的clone()方法,我们会得到一个名为浅拷贝的结构:
Object类的clone()将创建第一个节点的副本,并共享其他结点。这并不是我们所说的“该对象的副本”。其实我们要的是下图所代表的副本:
由于数据是不可变的,可以在两个链表之间共享数据。有几种方法来实现链表复制。最简单的一种是遍历原始链表,并使用addFirst()方法复制每个节点。这个完成后,将得到一个具有相反顺序的新链表。最后,我们一定要反转链表:
public Object copy(){ LinkedList<AnyType> twin = new LinkedList<AnyType>(); Node<AnyType> tmp = head; while(tmp != null) { twin.addFirst( tmp.data ); tmp = tmp.next; } return twin.reverse();}
一个更好的方法是为新链表使用一个尾引用,在最后一个节点之后添加每个新节点。
public LinkedList<AnyType> copy3(){ if(head==null) return null; LinkedList<AnyType> twin = new LinkedList<AnyType>(); Node tmp = head; twin.head = new Node<AnyType>(head.data, null); Node tmpTwin = twin.head; while(tmp.next != null) { tmp = tmp.next; tmpTwin.next = new Node<AnyType>(tmp.data, null); tmpTwin = tmpTwin.next; } return twin;}
(多项式代数应用部分未翻译)
- 链表(Java描述)
- 链表(Java描述)
- 快速排序(Java描述)
- 归并排序(Java描述)
- 循环队列(java描述)
- 数据结构与算法(Java语言描述)--链表-01
- 数据结构与算法(Java语言描述)--链表-02
- 应用Hash函数(java描述)
- Java Collection 接口描述(一)
- 应用Hash函数(java描述)
- Windows系统错误代码(Java描述)
- 数据结构 JAVA描述(一) 线性表
- 数据结构 JAVA描述(二) 栈
- 数据结构 JAVA描述(九) 插入排序
- 数据结构 JAVA描述(十) 交换排序
- 数据结构 JAVA描述(十三) 排序总结
- 数据结构 JAVA描述(十七) 哈希表查找
- 插入排序(JAVA语言描述)
- inline函数
- Javascript面向对象编程(三):非构造函数的继承
- Javascript面向对象编程(二):构造函数的继承
- <mfc深入浅出>学习笔记一
- Python OpenCV 滤波器 使用(八)
- 链表(Java描述)
- Python生成黑客字典程序(一)
- 如何删除bde
- Javascript继承机制的设计思想
- linux ntpdate同步网络时间
- 纵横交错地考查C++功底
- 枚举类型和注解
- Android : assets与res/raw资源目录的区别
- APP国际化