就是给每个节点保留两个引用prev和next,其中prev指向前一个节点,next指向后一个节点。该链表既可以向后依次访问每个节点,也可以向前其次访问每个节点,以这种形式保存的节点称为双向链表。
查找:
既可以从header(头节点),也可以从tail(尾节点)开始寻找指定的节点。一般的是根据搜索的index值来判断跟接近于头节点还是尾节点。即比较index和size(链表的大小)/2的大小。若是index<size/2,则从头节点header开始寻找,若是index>2,则从尾节点tail开始查找。
插入和删除:
都需要同时修改两个引用值。
如图(制作粗糙,请见谅):
参考代码:
public class DuLinkedList<T> {
//定义一个节点类
private class Node{
//保存数据
private T data;
//定义指向上一个节点 和 指向下一个节点的引用
private Node prev;
private Node next;
public Node(){
}
public Node(T data, Node prev, Node next){
this.data = data;
this.prev = prev;
this.next = next;
}
}
//保存头节点 和 尾节点
private Node header;
private Node trailer;
//链表中的节点数
private int size;
//创建空链表
public DuLinkedList(){
header = null;
trailer = null;
}
//根据指定元素创建链表
public DuLinkedList(T element){
header = new Node(element, null, null);
trailer = header;
size ++;
}
//返回链表的长度
public int length(){
return size;
}
//查找1 回去index处的节点
public T get(int index){
return getNodeByIndex(index).data;
}
//根据索引获取节点 可以从头 或者 尾开始查找吧 -- 二分法
private Node getNodeByIndex(int index) {
if(index < 0 || index > size - 1){
throw new IndexOutOfBoundsException("线性表越界");
}
if(index <= size / 2){
Node current = header;
for(int i = 0; i <= size / 2 && current != null; i ++, current = current.next){
if(i == index){
return current;
}
}
}
else{
Node current = trailer;
for(int i = size - 1; i > size / 2 && current != null; i --, current = current.prev){
if(i == index){
return current;
}
}
}
return null;
}
//查找指定的element
public int locate(T element){
Node current = header;
for(int i = 0; i < size && current != null; i ++, current = current.next){
if(current.data == element){
return i;
}
}
return -1;
}
//插入数据
public void insert(T element, int index){
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("线性表越界");
}
if(header == null){
addAtTrailer(element);//尾插法
}
else{
if(index == 0){
addAtHeader(element);//头插法
}
else{
//找到它的前一个和后一个元素
Node prevNode = getNodeByIndex(index - 1);
if(prevNode.next != null){//只有一个元素时
Node nextNode = prevNode.next;
//新建这个插入的节点
Node newNode = new Node(element, prevNode, nextNode);
prevNode.next = newNode;
nextNode.prev = newNode;
size ++;
}
else{
addAtTrailer(element);
}
}
}
}
//头插法
public void addAtHeader(T element) {
//作为一个新的头 新节点的next域指向原来的头节点
header = new Node(element, null, header);
if(trailer == null){
trailer = header;
}
size ++;
}
//尾插法 要说明为不改变之后的新尾部
public void addAtTrailer(T element){
//判断是不是空链表
if(header == null){
header = new Node(element, null, null);
trailer = header;
}
else{
//原来的尾节点作为插入节点的前一个节点元素
Node newNode = new Node(element, trailer, null);
trailer.next = newNode;
trailer = newNode;
}
size ++;
}
//删除数据
public T delete(int index){
if(index < 0 || index > size -1){
throw new IndexOutOfBoundsException("线性表越界");
}
Node delNode = null;
//被删除的是头节点
if(index == 0){
header = delNode;
header = header.next;
//同时释放新的头节点的prev引用
header.prev = null;
}
else{
//找到对应的头节点
Node prevNode = getNodeByIndex(index - 1);
delNode = prevNode.next;
//被删除的节点的前一个节点的next指向被删除节点的下一个节点
prevNode.next = delNode.next;
if(delNode.next != null){//判删除节点之后是不是空
//让删除节点的下一额节点的prev指向这个它前一个节点
delNode.next.prev = prevNode;
}
//被删除节点的next引用赋值为null
delNode.prev = null;
delNode.next = null;
}
size --;
return delNode.data;
}
public T remove(){
return delete(size - 1);
}
//判断是否为空
public boolean empty(){
return size == 0;
}
//清空列表
public void clear(){
header = null;
trailer = null;
size = 0;
}
//对于这种双向的数据结构 toString方法一般都要考虑前后关系
public String toString(){
if(size == 0){
return "[]";
}
else{
StringBuffer sb = new StringBuffer("[");
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();
}
}
public String reverseToString(){
if(size == 0){
return "[]";
}
else{
StringBuffer sb = new StringBuffer("[");
for(Node current = trailer; current != null; current = current.prev){
sb.append(current.data.toString() + ", ");
}
int len = sb.length();
return sb.delete(len - 2, len).append("]").toString();
}
}
}
测试部分:
public static void main(String[] args) {
DuLinkedList<Integer> dlq = new DuLinkedList<Integer>();
dlq.insert(1, 0);
// dlq.addAtTrailer(2);
dlq.insert(2, 1);
dlq.addAtTrailer(3);
System.out.println(dlq);
System.out.println("倒序输出的结果是:" + dlq.reverseToString());
System.out.println("序列的长度:" + dlq.length());
dlq.remove();
dlq.addAtHeader(4);
System.out.println("删除一个元素和再添加一个元素的队列:" + dlq);
}
注意:对应插入时可以选择头插法或者尾插法,在输出时,也可以分别从头(header)或者尾(tail)输出数据。
相比较于单链表,它克服了其指针单向性的缺点,其中每个节点既可以向前引用,也可以向后引用,更加方便插入和删除数据元素。
参考:《疯狂java突破程序员的基本功的16课》
以上就是这篇的内容,如果存在错误的地方或有可以优化的地方,请您指出,谢谢!