数据结构专题——线性表之单链表及其Java实现
来源:互联网 发布:app store付费软件 编辑:程序博客网 时间:2024/06/05 18:45
在上一篇博客中,博主和同学们一起认识了本系列中的第一个数据结构——线性表。在提供了抽象数据类型(ADT List)和Java版的MyList之后,我们又基于Java语言实现了在内存中顺序存储的线性表(也称为顺序表)。
尽管我们在遇到频繁访问列表中元素时,ArrayList是一个很好的选择;但当我们需要频繁添加、删除列表中元素,尤其是在表的前端进行增删操作时,ArrayList就不是一个特别好的选择了。
为了解决添加、删除元素时需要大量其它元素移动的问题,我们今天一起来接触在内存中链式存储的线性表(也称为链表)。
链式存储的线性表同样需要一组存储单元去存储数据元素,但是这组存储单元可以是内存上连续的,也可以是内存上不连续的。
对于顺序存储的线性表来说,每个存储单元只需要存储数据元素即可,因为它们在内存上是连续的,第一个数据元素所占的内存空间之后,一定是下一个数据元素。
但是对于链式存储的线性表来说,除了要存数据元素之外,还需要存它的后继元素的存储地址,因为下一个数据元素很可能在内存中任意位置。
因此,对于每一个数据元素ai来说,除了存储其本身的数据信息之外,还需要存储一个指示其直接后继元素的内存位置。我们把存储数据元素信息的域称为数据域,把存储直接后继元素位置的域称为指针域。这两部分信息组成数据元素ai的存储映像,称为节点(Node)。n个节点连接成一个链表(a1,a2, … ,an),因为这个链表的每个节点中只包含一个指针域(指向它的直接后继元素),所以这种链表也成为单链表。
- 下面我们就来一起用Java实现一个单链表MySinglyLinkedList
public class MySinglyLinkedList<E> implements MyList<E> { //单链表中的首节点,此节点不存放元素,只用来指向单链表中的第一个节点 private Node firstNode; //单链表的元素数量 private int size; public MySinglyLinkedList() { firstNode = new Node(null, null); this.size = 0; } //内部类Node,用来表示单链表中每一个数据元素 class Node{ //数据域 E data; //后继指针域,指向下一个数据元素 Node nextNode; //提供一个构造函数 Node(E data, Node nextNode){ this.data = data; this.nextNode = nextNode; } }
- 接下来我们实现获取元素个数和查看是否为空的方法,很简单单行代码即可实现
@Override public int size() { // TODO Auto-generated method stub return this.size; } @Override public boolean isEmpty() { // TODO Auto-generated method stub return size()==0; }
- 然后是两种不同的插入方法
@Override public boolean add(E e) { // TODO Auto-generated method stub //在单链表的结尾插入一个元素,就相当于在index为size的位置处插入元素 add(size(),e); return true; } @Override public void add(int index, E element) { // TODO Auto-generated method stub //一旦要插入元素的位置为负或大于目前的元素数量就抛出异常 //此处允许index等于size,相当于在单链表末尾插入元素 if(index<0 || index>size()) throw new IndexOutOfBoundsException(); //需要插入的元素 Node newNode = new Node(element, null); //由于单链表只能从前往后遍历,因此要想在某个位置插入元素,必须先获得它前一个位置上的元素节点 //私有方法getPrevNodeByIndex可以用来获取该元素节点 Node posNode = getPrevNodeByIndex(index); //需要被插入元素的后继元素指向索引位置的原节点 newNode.nextNode = posNode.nextNode; //前一个位置的节点的后继元素指向被插入的节点 posNode.nextNode = newNode; this.size++; } //私有方法getPrevNodeByIndex获取index前一个位置的节点 private Node getPrevNodeByIndex(int index) { Node posNode = firstNode; for(int i=0; i<index;i++) { posNode = posNode.nextNode; } return posNode; }
- 紧接着是两个不同的删除方法
@Override public boolean remove(Object o) { // TODO Auto-generated method stub if(isEmpty()) return false; //如果需要被删除的元素为null if(o == null) { for(Node n = firstNode; n.nextNode!=null; n = n.nextNode) { if(n.nextNode.data == null) { Node targetNode = n.nextNode; n.nextNode = targetNode.nextNode; //让被删除节点的数据域,指针域和它的引用变量都指向null,方便GC工作 targetNode.data = null; targetNode.nextNode = null; targetNode = null; this.size--; return true; } } return false; }else { for(Node n = firstNode; n.nextNode!=null; n = n.nextNode) { if(o.equals(n.nextNode.data)) { Node targetNode = n.nextNode; n.nextNode = targetNode.nextNode; //让被删除节点的数据域,指针域和它的引用变量都指向null,方便GC工作 targetNode.data = null; targetNode.nextNode = null; targetNode = null; this.size--; return true; } } return false; } } @Override public E remove(int index) { // TODO Auto-generated method stub //一旦要插入元素的位置为负或大于目前的元素数量就抛出异常 //此处不允许index等于size checkIndexValidation(index); //获取要被删除节点的前驱节点 Node prevNode = getPrevNodeByIndex(index); //获取要被删除的节点 Node targetNode = prevNode.nextNode; //取出要被删除节点的数据域 E data = targetNode.data; //要被删除节点的前驱节点的后继节点指向要被删除节点的后继节点 prevNode.nextNode = targetNode.nextNode; //让被删除节点的数据域,指针域和它的引用变量都指向null,方便GC工作 targetNode.data = null; targetNode.nextNode = null; targetNode = null; this.size--; return data; }
- 之后是获取,修改,查看是否包含,以及清空整个列表这几个简单的方法了:
@Override public E get(int index) { // TODO Auto-generated method stub checkIndexValidation(index); return getPrevNodeByIndex(index).nextNode.data; } @Override public E set(int index, E element) { // TODO Auto-generated method stub checkIndexValidation(index); Node targetNode = getPrevNodeByIndex(index).nextNode; E oldData = targetNode.data; targetNode.data = element; return oldData; } @Override public boolean contains(Object o) { // TODO Auto-generated method stub if(isEmpty()) return false; if(o == null) { for(Node n = firstNode; n.nextNode!=null; n = n.nextNode) { if(n.nextNode.data == null) { return true; } } return false; }else { for(Node n = firstNode; n.nextNode!=null; n = n.nextNode) { if(o.equals(n.nextNode.data)) { return true; } } return false; } } @Override public void clear() { // TODO Auto-generated method stub for(Node n = firstNode.nextNode; n !=null;) { Node next = n.nextNode; n.data = null; n.nextNode = null; n = next; } firstNode.nextNode = null; this.size = 0; }
我们在这里也分析一下MyLinkedList的特点:
我们要读取或修改某一个位置的节点需要花费线性时间 O(n)去遍历列表;
我们要增加或删除某一个(已知位置的)节点最坏情况下要花费常数时间 O(1)
因此,当我们在遇到频繁访问列表中元素时,ArrayList是一个很好的选择;但当我们需要频繁在已知位置添加、删除列表中元素时,LinkedList有更好的性能。
本节博客的源码全部放在这里,欢迎大家下载,下载后记得修改源码中的package名字哦。
- 数据结构专题——线性表之单链表及其Java实现
- 数据结构专题——线性表之顺序表及其Java实现
- 数据结构专题——线性表之双链表及其Java实现
- 数据结构与算法专题之线性表——栈及其应用
- 数据结构与算法专题之线性表——队列及其应用
- 数据结构专题——栈与队列之顺序栈及其Java实现
- 数据结构专题——栈与队列之链栈及其Java实现
- 【数据结构专题】线性表之单链表
- 数据结构专题——线性表
- 数据结构与算法专题之线性表——链表(一)单链表
- 数据结构—线性结构—线性及其实现
- 数据结构-线性结构-线性表及其实现
- 数据结构之线性表的链式表示及其实现
- 【数据结构专题】线性表之顺序表
- java数据结构:线性表之数组实现
- java数据结构之线性表代码实现
- java 实现数据结构之线性表
- 数据结构-线性表之单链表(Java实现)
- JSP(7)—EL和JSTL
- C
- C#语言笔记-1
- android MVVM开发框架——(4)DataBinding绑定view自定义属性
- javascript的封装jQuery
- 数据结构专题——线性表之单链表及其Java实现
- Freertos消息队列接收源码xQueueGenericReceive分析
- 作业2
- Linux rar unrar的安装
- css经典布局一
- 音视频基础知识之PCM数据
- HashMap的实现原理
- Leetcode:258.Add Digits 加数字,直到为个位数
- 初探hadoop框架