数据结构专题——线性表之双链表及其Java实现
2017-10-31 22:18
555 查看
在上一篇博客中,博主和大家一起实现了线性表的链式存储,由于链表中每个节点都只有一个指针域指向着后继节点,因此这种链表也叫单链表。
这种链表的缺点显而易见,因为只能够单向遍历,即使我们想要访问最后一个元素也不得不从第一个元素开始遍历到最后一个元素。
为了解决这个问题,双链表应运而生。和单链表十分相似,唯一的区别在于每个节点当中包含两个指针域,分别指向着这个节点的前驱节点和后继节点。
因此,对于每一个数据元素ai来说,除了存储其本身的数据信息之外,还需要存储一个指示其直接后继元素的内存位置和一个其直接前驱元素的内存位置。我们把存储数据元素信息的域称为数据域,把存储直接后继元素位置的域称为后继指针域,把存储直接前驱元素位置的域称为前驱指针域。这三部分信息组成数据元素ai的存储映像,称为节点(Node)。n个节点连接成一个链表(a1,a2, … ,an),因为这个链表的每个节点中包含两个指针域(指向它的直接后继元素和前驱元素),所以这种链表被称为双链表。
与单链表相比,如果我们需要访问较为靠后的元素数据,只需要从尾节点向前遍历即可。
下面我们就来一起用Java实现一个双链表MyDoublyLinkedList
接下来我们实现获取元素个数和查看是否为空的方法,很简单单行代码即可实现
然后是两种不同的插入方法
紧接着是两个不同的删除方法
之后是获取,修改,查看是否包含,以及清空整个列表这几个简单的方法了:
双链表相比单链表来说耗费了更多的空间去存储前驱节点的位置,以此来获得某些操作更好的时间性能。这种以少量空间换取更优的性能做法,在很多地方也都可以见到。
本节博客的源码全部放在这里,欢迎大家下载,下载后记得修改源码中的package名字哦。
这种链表的缺点显而易见,因为只能够单向遍历,即使我们想要访问最后一个元素也不得不从第一个元素开始遍历到最后一个元素。
为了解决这个问题,双链表应运而生。和单链表十分相似,唯一的区别在于每个节点当中包含两个指针域,分别指向着这个节点的前驱节点和后继节点。
因此,对于每一个数据元素ai来说,除了存储其本身的数据信息之外,还需要存储一个指示其直接后继元素的内存位置和一个其直接前驱元素的内存位置。我们把存储数据元素信息的域称为数据域,把存储直接后继元素位置的域称为后继指针域,把存储直接前驱元素位置的域称为前驱指针域。这三部分信息组成数据元素ai的存储映像,称为节点(Node)。n个节点连接成一个链表(a1,a2, … ,an),因为这个链表的每个节点中包含两个指针域(指向它的直接后继元素和前驱元素),所以这种链表被称为双链表。
与单链表相比,如果我们需要访问较为靠后的元素数据,只需要从尾节点向前遍历即可。
下面我们就来一起用Java实现一个双链表MyDoublyLinkedList
//双链表中的首标记节点,此节点不存放元素,前驱结点指向null,后继节点指向链表中的第一个元素节点 private Node firstFlagNode; //双链表中的尾标记节点,此节点不存放元素,后继结点指向null,前驱节点指向链表中的最后一个元素节点 private Node lastFlagNode; //双链表中元素的数量 private int size; public MyDoublyLinkedList() { firstFlagNode = new Node (null,null,null); //首标记节点的后继指向尾标记节点,尾标记节点的前驱指向首标记节点 lastFlagNode = new Node (null,firstFlagNode,null); firstFlagNode.nextNode = lastFlagNode; size = 0; } //内部类Node,用来表示双链表中每一个数据元素 class Node{ //数据域 E data; //前驱指针域,指向该节点的前一个节点 Node prevNode; //后继指针域,指向该节点的后一个节点 Node nextNode; public Node(E data, Node prevNode, Node nextNode) { this.data = data; this.prevNode = prevNode; 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 add(size(),e); return true; } @Override public void add(int index, E element) { // TODO Auto-generated method stub //当index=0时,相当于在第一个位置插入元素,当index=size时相当于在链表末尾插入一个元素 if(index<0 || index>size()) throw new IndexOutOfBoundsException(); Node newNode = new Node(element,null,null); Node n = getPrevNodeByIndex(index); //在它之后插入新的节点 n.nextNode.prevNode = newNode; newNode.prevNode = n; newNode.nextNode = n.nextNode; n.nextNode = newNode; this.size++; return; } //私有方法获取当前index位置的前一个节点 private Node getPrevNodeByIndex(int index) { //双链表能够从前后两个方向遍历,根据index的大小选择方向遍历 Node n = null; if(index<size()/2) { n = firstFlagNode; for(int i=0; i<index; i++) { n = n.nextNode; } }else { n = lastFlagNode; for(int i = size(); i>=index; i--) { n = n.prevNode; } } return n; }
紧接着是两个不同的删除方法
@Overri 4000 de public boolean remove(Object o) { // TODO Auto-generated method stub if(o == null) { for(Node n = firstFlagNode.nextNode; n!=null&&n!=lastFlagNode; n = n.nextNode) { if(n.data == null) { //删除本节点 n.prevNode.nextNode = n.nextNode; n.nextNode.prevNode = n.prevNode; //清空旧节点的数据域与指针域,方便GC工作 n.data = null; n.nextNode = null; n.prevNode = null; n = null; this.size--; return true; } } return false; }else { for(Node n = firstFlagNode.nextNode; n!=null&&n!=lastFlagNode; n = n.nextNode) { if(o.equals(n.data)) { //删除本节点 n.prevNode.nextNode = n.nextNode; n.nextNode.prevNode = n.prevNode; //清空旧节点的数据域与指针域,方便GC工作 n.data = null; n.nextNode = null; n.prevNode = null; n = null; this.size--; return true; } } return false; } } @Override public E remove(int index) { // TODO Auto-generated method stub //检查index的边界 checkIndexValidation(index); //获取index位置的节点 Node targetNode = getPrevNodeByIndex(index).nextNode; E oldElement = targetNode.data; //旧节点的前驱节点指向旧节点的后继节点,旧节点的后继节点指向旧节点的前驱节点 targetNode.prevNode.nextNode = targetNode.nextNode; targetNode.nextNode.prevNode = targetNode.prevNode; //清空旧节点的数据域与指针域,方便GC工作 targetNode.data = null; targetNode.nextNode = null; targetNode.prevNode = null; targetNode = null; this.size--; return oldElement; } private void checkIndexValidation(int index) { if(index<0 || index>=size()) throw new ArrayIndexOutOfBoundsException(); }
之后是获取,修改,查看是否包含,以及清空整个列表这几个简单的方法了:
@Override public void clear() { // TODO Auto-generated method stub for(Node n = firstFlagNode.nextNode; n!=null&&n!=lastFlagNode; ) { Node next = n.nextNode; n.data = null; n.nextNode = null; n.prevNode = null; n = next; } firstFlagNode.nextNode = lastFlagNode; lastFlagNode.prevNode = firstFlagNode; this.size = 0; } @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 data = targetNode.data; targetNode.data = element; return data; } @Override public boolean contains(Object o) { // TODO Auto-generated method stub if(o == null) { for(Node n = firstFlagNode.nextNode; n!=null&&n!=lastFlagNode; n = n.nextNode) { if(n.data == null) { return true; } } return false; }else { for(Node n = firstFlagNode.nextNode; n!=null&&n!=lastFlagNode; n = n.nextNode) { if(o.equals(n.data)) { return true; } } return false; } }
双链表相比单链表来说耗费了更多的空间去存储前驱节点的位置,以此来获得某些操作更好的时间性能。这种以少量空间换取更优的性能做法,在很多地方也都可以见到。
本节博客的源码全部放在这里,欢迎大家下载,下载后记得修改源码中的package名字哦。
相关文章推荐
- 数据结构专题——线性表之单链表及其Java实现
- 数据结构专题——栈与队列之链栈及其Java实现
- 数据结构学习----线性表的链式表示之升序排序的单链表(Java实现)
- 数据结构专题——栈与队列之顺序栈及其Java实现
- JAVA数据结构 线性表的链式存储及其实现
- 数据结构:线性表的链式存储(单向链表)--Java实现
- 【Java数据结构学习笔记之一】线性表的存储结构及其代码实现
- 线性表、链表、栈、队列之java实现
- java与数据结构(3)---java实现循环链表
- 数据结构:线性表的顺序存储--Java实现
- 线性表--双链表实现方式 (JAVA)
- java数据结构之实现双向链表的示例
- 数据结构java实现之双端链表
- java 实现双向链表(数据结构)
- 数据结构和算法之:图的深度优先和广度优先遍历及其Java实现
- java学习笔记4>顺序线性表的实现及其内存分配
- java实现 数据结构:链表、 栈、 队列、优先级队列、哈希表
- 【Java数据结构】2.4单循环链表的实现
- 数据结构:线性表的链式存储(双向链表)--Java实现
- 双向链表-数据结构-java实现