一.概念
与上文的单链表不同,双向链表中有两个指针,next(指向下一个结点),pre(指向上一个结点),如图示
双向链表的好处:遍历时可从前往后也可以从后往前,弥补了单链表只能从前往后遍历的不足
二.双向链表操作思路分析
1.遍历
- 双向链表的遍历思路与单向链表相同,可以看我上篇文章
2.添加
- 找到最后一个结点
- temp.next=newNode
- newnode.pre=temp
3.修改
- 修改思路与单链表相同
4.插入
- 首先遍历到要插入点的前一个位置
- 对该节点进行操作,temp.next.pre = node;node.next=temp.next;temp.next=node;node.pre=temp;
5.删除
- 因为是双向链表,可以实现结点的自我删除,所以直接遍历到需要删除的结点(单链表需要遍历到删除的前一个)
- temp.pre.next=temp.next; temp.next.pre=temp.pre(此项有条件,如果被删除结点是最后一个,则不需要执行,否则会报空指针异常)
三.代码实现(含测试用例)
public class DoubleLinkedListDemo {
public static void main(String[] args) {
System.out.println("双向链表的测试");
Node2 node1 = new Node2("这是第一个结点");
Node2 node2 = new Node2("这是第二个结点");
Node2 node3 = new Node2("这是第三个结点");
Node2 node4 = new Node2("这是第四个结点");
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
System.out.println("这是添加的测试");
doubleLinkedList.add(node1);
doubleLinkedList.add(node2);
doubleLinkedList.add(node3);
doubleLinkedList.add(node4);
doubleLinkedList.list();
System.out.println("这是修改的测试");
doubleLinkedList.update(new Node2("我修改的是第二个结点"),2);
doubleLinkedList.list();
System.out.println("这是删除的测试");
doubleLinkedList.delete(2);
doubleLinkedList.list();
System.out.println("这是插入的测试");
doubleLinkedList.insert(new Node2("这是第二个节点"),1);
doubleLinkedList.list();
}
}
class DoubleLinkedList{
//先初始化一个头结点
private Node2 head = new Node2("");
public Node2 getHead() {
return head;
}
//链表插入
//找到插入的前一个位置
//temp.dat = node.next
public void insert(Node2 node,int n){
//因为头结点不能动,因此我们需要一个辅助的遍历temp
//因为单链表,因此我们找的temp是位于添加位置的前一个结点,否则插入不了
if(n <= 0){
System.out.println("数据不合法!");
}
Node2 temp = head;
boolean flag = false; //标志为是否符合插入条件
for(int i = 0; i < n; i++){
if(temp.next == null){
System.out.println("无法插入结点,插入点不存在");
break;
}
if(i == n - 1 && temp.next != null) {
flag = true;
}
temp = temp.next;
}
if(flag == true){
temp.next.pre = node;
node.next = temp.next;
temp.next = node;
node.pre = temp;
}
}
public int getLength(Node2 head){
if(head.next == null){
return 0;
}
Node2 temp = head;
int count = 0;
while(temp.next != null){
temp = temp.next;
count++;
}
return count;
}
public void add(Node2 node){
//因为head结点不能动,因此我们需要一个辅助的遍历temp
Node2 temp = head;
//遍历链表,找到最后
while (temp.next != null){ //如果temp还有下一个结点继续循环
temp = temp.next; //如果找到了,将结点后移
}
//当退出while循环时,temp就指向了链表的最后
//temp.next赋值就好
temp.next = node;
node.pre = temp;
}
//删除结点
//思路
//双向链表可以找到要删除的结点,实现自我删除
public void delete(int n){
Node2 temp = head.next;
for(int i = 0 ; i < n ; i++){
if(temp == null){
System.out.println("不存在该结点");
}
if(i == n-1) {
temp.pre.next = temp.next;
//这里有问题,有一个条件,如果是最后一个结点就不需要执行下面这句话,否则会出现空指针异常
if (temp.next != null) {
temp.next.pre = temp.pre;
}
}
temp = temp.next;
}
}
//和单向链表相同
//修改结点的信息,根据结点位置来修改
public void update(Node2 newNode,int n){
//判断是否为空
if(head.next == null){
System.out.println("链表为空");
return;
}
//因为头结点不能动,因此我们需要一个辅助的遍历temp
Node2 temp = head;
//找到需要修改的结点,根据传入的n值
for(int i = 0 ; i < n ; i++){
if(temp.next == null){
System.out.println("该结点不存在");
break;
}
temp = temp.next;
if(i == n-1){
temp.data = newNode.data;
}
}
}
//遍历双向链表的方法
//显示链表(遍历)
public void list(){
//判断链表是否为空
if(head.next == null){
System.out.println("链表为空");
return;
}
//因为头结点不能动,因此我们需要一个辅助的遍历temp
Node2 temp = head.next;
while (temp != null){
System.out.println(temp);
temp = temp.next;
}
}
}
class Node2{
public String data;
public Node2 next; //指向下一个结点
public Node2 pre;
//构造器
public Node2(String data) {
this.data = data;
}
//toString方法
@Override
public String toString() {
return "Node{" +
"data='" + data + '\'' +
'}';
}
}