双向链表
概念
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
双向链表示意图如下:
双向链表的优缺点:
★优点:对于链表中一个给定的结点,可以从两个方向进行操作。
☆缺点:每个结点添加一个额外的指针,因此需要更多的空间开销;结点的插入或删除更加费时(需要更多的指针操作)
双向链表的类型声明,代码如下:
class DLLNode
{
private int data;
private DLLNode next;
private DLLNode previous;
public DLLNode(int data){
this.data=data;
}
public void setData(int data){
this.data=data;
}
public int getData(){
return data;
}
public void setNext(DLLNode next){
this.next=next;
}
public DLLNode getNext(){
return this.next;
}
public void setPrevious(DLLNode previous){
this.previous=previous;
}
public DLLNode getPreviouds(){
return this.previous;
}
}
双向链表的插入
◇在双向链表的开始插入一个结点
- 将新结点的后继指针更新为指向当前的表头结点,将新结点的前驱指针赋值为NULL
- 将表头结点前驱指针更新为指向新结点,然后将新结点作为表头结点
◇在双向链表的末尾插入一个结点
- 新结点的后续指针指向NULL,其前驱指针指向尾结点
- 更新最后一个结点的后续指针,使其指向新结点
◇在双向链表的中间插入一个结点
- s的后续指针指向需要插入s的后续指针,然后令s的前驱指针指向p
p的后续结点的前驱指针指向s,p的后续指针指向s
双向链表插入的主要代码如下:
DLLNode DLLInsert(DLLNode headNode,DLLNode nodeToInsert,int position){
if(headNode==null){
return nodeToInsert;
}
int size=getDLLLength(headNode);
if(position>size+1 || position<1){
System.out.println("插入位置无效");
retrun headNode;
}
if(position==1){ //在链表开头插入
nodeToInsert.setNext(headNode);
headNode.setPrevious(nodeToInsert);
return nodeToInsert;
}else{ //在链表中间或末尾插入
DLLNode previousNode=headNode;
int count=1;
while(count<position-1){
previousNode=previousNode.getNext();
count++;
}
DLLNode currentNode=previousNode.getNext();
nodeToInsert.setNext(currentNode);
if(currentNode!=null){
currentNode.setPrevious(nodeToInsert);
}
previousNode.setNext(nodeToInsert);
nodeToInsert.setPrevious(previousNode);
}
return headNode;
}
时间复杂度为0(n)
空间复杂度为0(1),用于创建临时变量
双向链表的删除
◆删除双向链表的第一个结点
- 修改表头结点的next指针为NULL
- 修改表头结点的后续结点的前驱指针为NULL
◆删除双向链表的最后一个结点
- 遍历链表,在遍历时还要保存前驱结点的地址。当遍历到表尾时,有两个指针分别是指向表尾结点的tail指针和指向表尾结点的前驱结点的指针。
- 更新表尾的前驱结点的next指针为NULL
◆删除双向链表的中间一个结点
- 在遍历链表时保存前驱结点。一旦找到要删除的结点,更新前驱结点的next指针使其指向要被删除结点的下一个结点,更改被删除结点的后续结点的previous指针指向被删除结点的前驱结点
移除被删除的结点
双向链表删除的主要代码如下:
DLLNode DLLDelete(DLLNode headNode,int position){
int size=getDLLLength(headNode);
if(position>size || position<1){
System.out.println("删除位置无效");
retrun headNode;
}
if(position==1){ //删除链表的第一个结点
DLLNode currentNode=headNode.getNext();
headNode=null;
currentNode.setPrevious(null);
return currentNode;
}else{ //删除中间或表尾结点
DLLNode previousNode=headNode;
int count=1;
while(count<position-1){
previousNode=previousNode.getNext();
count++;
}
DLLNode currentNode=previousNode.getNext();
DLLNode laterNode=currentNode.getNext();
previousNode.setNext(laterNode);
if(laterNode!=null){
laterNode.setPrevious(previousNode);
}
currentNode=null;
}
return headNode;
}
时间复杂度为0(n)
【结论】
双向链表相对于单链表来说,要更复杂一些,多了前驱指针,所以对于插入和删除来说,需要格外小心。另外由于它的每个结点都需要记录两份指针,所以要占用更多的空间。不过,由于它有良好的对称性,使得对某个结点的前后结点的操作,带来了方便,可以有效的提高算法的时间性能。这也是采用空间换时间性能的一种方法。