双向链表
首先我们来了解什么是循环链表,单链表尾结点的下一引用是为空,而循环链表就是让尾结点的引用指向头节点,从而使整个链表成为一个环,可以循环遍历链表。但是循环链表又有什么用呢,查询链表还是需要从头节点开始依次向后遍历,效率并没有多大提升,于是就提出了双向链表。双向链表就是在循环链表的基础上再让每个节点引用前一结点。从而每个结点同时有前一结点引用和后一结点引用,那就可以实现向前与向后遍历的操作,那对于一有序双向链表当我们需要查询某一结点时就可以不一定要从头节点开始遍历,可以从某一结点开始遍历,从而减少遍历次数。
双向链表实现
- 双向链表
2.双向链表结点类
class Node
{
//存放数据
private Object data;
//上一数据元素引用
private Node prior;;
//下一数据元素引用
private Node next;
public Node(Object data)
{
this.data = data;
next = null;
prior = null;
}
//data的get,set方法
public void setData(Object data)
{
this.data = data;
}
public Object getData()
{
return data;
}
//prior的get,set方法
public void setPrior(Object prior)
{
this.prior = prior;
}
public Object getPrior()
{
return prior;
}
//next的get,set方法
public void setNext(Node data)
{
this.next = next;
}
public Node getNext()
{
return next;
}
}
结点类由三个区域组成:数据区域、前引用区域、后引用区域
3.双向链表类基本构造
public class List
{
//单链表头引用
private Node head;
//单链表尾引用
private Node rail;
//单链表长度
private int length;
//当前结点引用
private Node now;
public List(Object element)
{
//创造一个元素结点实例作为头结点
head = new Node(element);
head.setPrir(head);
head.setNext(head);
rail = head;
now = head;
length = 0;
}
//双链表相关操作
}
4.查询双向链表长度
public int getLength()
{
return length;
}
5.在双向链表第index处插入一个结点
//将元素element插入到List最前
public void addHead(Object element)
{
var q = new Node(element);
q.setNext(head.getNext());
q.setPrior(head);
head.setNext(q);
//链表表为空时,插入结点改变tail
if(tail == head)
{
tail = q;
}
length++;
}
//将元素element插入到List最后
public void addTail(Object element)
{
var q = new Node(element);
q.setPrior(tail);
q.setNext(tail.getNext());
(tail.getNext()).setPrior(q);
tail.setNext(q);
tail = q;
length++;
}
//将元素element插入到indexz处
private boolean add(int index,Object element)
{
//索引错误
if(index < 0 || index > length)
{
return false;
}
//在List前插入
else if(index == 0)
{
addHead(element);
}
//在List尾插入
else if(index == length)
{
addTail(element);
}
else
{
Node p = head;
var j = -1;
//使p引用第index-1个结点
while(p.getNext() && j<index-1)
{
p = p.getNext();
j++;
}
var q = new Node(element);
//插入结点前驱等于p
q.setPrior(p);
//插入结点后驱等于p的后驱
q.setNext( p.getNext() );
//p原后驱结点的前驱等于插入结点
( p.getNext() ).setPrior(q);
//p的后驱等于插入结点
p.setNext(q);
length++;
}
return true;
}
双向链表插入结点,就是让插入结点与插入前后结点连接关系,给结点的前后结点引用赋值,但是要注意的是给结点前后结点引用赋值的顺序,先要给插入结点前后结点引用赋值,再改变原链表的前后驱,如果先改变p的后驱,则之后无法找到插入结点的后驱,这是很容易错的。
6.删除双向链表第index结点
//删除List第index个结点
public Object remove(int index)
{
//索引错误
if(index < 0 || index > length || length == 0)
{
return null;
}
else
{
Node q = null;
//删除List第一个结点
if(index == 0)
{
q = head.getNext();
head.setNext(q.getNext());
( q.getNext() ).setPrior(head);
}
//删除List最后一个结点
else if(index == length)
{
q = tail;
tail = q.getPrior();
tail.setNext(q.getNext());
(q.getNext()).setPrior(tail);
}
//删除结点在0~length结点之间
else
{
Node p = head;
var j = -1;
//使p引用第index个结点
while(p.getNext() && j < index-1)
{
p = p.getNext();
j++;
}
q = p.getNext();
p.setNext(q.getNext());
q.setPrior(p);
}
length--;
return q.getData();
}
}
插入与删除结点共同重要步骤是找到正确位置,这就要设置正确的循环条件
7.返回当前查询结点的前结点与后结点
//返回上一数据元素
public Object getPriorNow()
{
now = now.getPrior();
if(now == head)
now = getPrior();
return now;
}
//返回下一数据元素
public Object getNextNow()
{
now = now.getNext();
if(now == head)
now = now.getNext();
return now;
}
8.返回双向链表index处结点
//返回List中index索引处的结点
public Object get(int index)
{
//索引错误
if(index < 0 || index >= length)
{
return null;
}
else
{
var j = 0;
Node p = head.getNext();
while(j < index)
{
p = p.getNext();
j++;
}
return p;
}
}