一.概述
双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。
链表的头结点的数据域不存储数据,前指针域为null,其后指针域指向第一个真正存储数据的结点。
头节点:不存储真正的数据,其用指针域指向第一个存储数据的节点。头节点的作用主要是用来找到当前这条链表。
二.链表的优缺点(和数组相反)
优点:随机增删元素效率较高,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList(因为增删不涉及到大量元素位移)
缺点:查询效率较低,每一次查找某个元素的时候都需要从头节点开始往下遍历(链表的元素在的内存地址不连续,不能通过公式直接计算出元素的内存地址)
注意!!!
在顺序表中,由于数组是有索引的!在例如返回指定位置元素get(int i){}方法中,所传的参数 i 是指的索引。
而链表中是没有索引的!为了便于理解,故在此将链表中的元素位置(从0开始)视为索引,以下文中写的索引均为此意,并非是真的存在索引。
即第一个元素的“索引”为0,头结点的“索引”相当于-1
例如存入第一个元素“中国”,那么将这个元素的位置视为0,若要返回“中国”,则在get()方法中的输入的参数 i=0 ,与顺序表的使用相同。
三.代码实现
创建链表类,成员变量有头结点head,尾结点last,元素个数N
将头结点数据域、前指针域、后指针域都初始化为null,其中数据域和前指针域始终为null。
public class DLinkList<T> implements Iterable{
private Node head;//头节点
private Node last;//尾结点
private int N;
public DLinkList(){
this.head=new Node(null,null,null); //初始化头节点
this.N=0;
}
用成员内部类的方式建立结点类Node,一个结点包含数据域、前指针域、后指针域
public class Node{
private T data; //数据域
public Node pre;//前指针域
public Node next;//后指针域
public Node(T data,Node pre,Node next){
this.data=data;
this.pre=pre;
this.next=next;
}
}
清空链表:,主要是断开头结点和后面元素的连接即可。
public void clear(){
this.head.next=null; //令头节点指向空
this.head.pre=null;
this.head.data=null;
this.last=null;
this.N=0;
}
获取链表的长度:
public int length(){
return N;
}
是否为空:
public boolean isEmpty(){
return N==0;
}
获取第一个元素,即头结点后面一个元素,也就是“索引”为0的元素
public T getFirst(){
return(isEmpty()?null:head.next.data);}
获取最后一个元素,即“索引”为N-1的元素
public T getLast(){
return(isEmpty()?null:last.data);
}
向末尾添加元素:,注意最后要更新尾结点last
public void add(T t){//向末尾添加元素
if(isEmpty()){ //如果为空
Node newNode=new Node(t,head,null); //新结点的前指针域指向头节点
head.next=newNode; //头结点指向新结点
last=newNode;//更新尾结点
}else{ //如果不为空
Node oldlast=last;//记录原尾结点
Node newNode=new Node(t,oldlast,null); //新结点的前指针域指向尾结点
oldlast.next=newNode;//原尾结点指向新结点
last=newNode;//更新尾结点
}
N++;
}
指定位置插入元素:
①记录原“索引”为 i的元素的前结点front
②由front.next得到原“索引”为 i 的元素current
③创造新结点newNode,其前指针域指向front,后指针域指向current
④将current的前指针指向newNode,front的后指针指向newNode。
//指定位置插入
public void insert(T t,int i){
if (i<0||i>=N){
throw new RuntimeException("位置不合法");
}
Node front=head;//记录头结点
for(int time=0;time<i;time++){
front=front.next; // 前结点front的“索引”为i-1,则需要循环i次
}
Node current=front.next;// 记录i 位置结点
Node newNode=new Node(t,front,current); //先指定新结点的前/后指针域
current.pre=newNode; //i 位置前指针指向新结点
front.next=newNode; //前结点后指针指向 i位置结点
N++;
}
得到指定“索引”为 i 的元素:,for循环中 i 的大小正好对应“索引”,所以返回 i 即可。
public int indexOf(T t){
Node n=head; //记录头结点
for(int i=0;n.next!=null;i++){
n=n.next;
if(n.data.equals(t)){
return i; //i正好对应“索引”大小,所以返回i即可
}
}
return -1;
}
移除指定“索引”的元素:
情况一:移除的是最后一个元素
①记录原“索引”为 i的元素的前结点front
②由front.next得到原“索引”为 i 的元素current
③将front的指针域指向null,即断开和current的连接
④更新尾结点
情况二:非最后一个元素
①记录原“索引”为 i的元素的前结点front
②由front.next得到原“索引”为 i 的元素current
③由current.next得到 current后面的元素behind
④连接front和behind即可
public T remove(int i) {
//先找到i元素前面的元素front,其索引为i-1,需遍历i次
Node front = head;
for (int time = 0; time < i; time++) {
front = front.next;
}
//记录“索引”为i的元素current
Node current = front.next;
if (i == (N - 1)) {//如果remove的是最后一个元素
front.next=null; //使front的后指针域与最后一个元素断开
this.last=front;//更新尾结点
return current.data;
}
else {
Node behind = current.next;//记录“索引”为 i 的后一个元素behind
//让pre和behind连接
front.next = behind;
behind.pre = front;
//元素数量减一
N--;
return current.data;
}
}
双向链表的遍历:
@Override
public Iterator iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator{
private Node n;
public MyIterator(){
this.n=head;//即从头节点开始
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public Object next() {
n=n.next;
return n.data;
}
}
=============================================================================
完整代码及main测试:
public class DLinkList<T> implements Iterable{
private Node head;//头节点
private Node last;//尾结点
private int N;
public DLinkList(){
this.head=new Node(null,null,null); //初始化头节点
this.N=0;
}
public class Node{
private T data; //数据域
public Node pre;//前指针域
public Node next;//后指针域
public Node(T data,Node pre,Node next){
this.data=data;
this.pre=pre;
this.next=next;
}
}
//清空链表
public void clear(){
this.head.next=null; //令头节点指向空
this.head.pre=null;
this.head.data=null;
this.last=null;
this.N=0;
}
//获取链表的长度
public int length(){
return N;
}
//是否为空
public boolean isEmpty(){
return N==0;
}
//获取链表的第一个元素,即“索引”为0的元素,也是头节点的下一个元素
public T getFirst(){
return(isEmpty()?null:head.next.data);}
//获取最后一个元素,即“索引”为N-1的元素
public T getLast(){
return(isEmpty()?null:last.data);
}
public void add(T t){//向末尾添加元素
if(isEmpty()){ //如果为空
Node newNode=new Node(t,head,null); //新结点的前指针域指向头节点
head.next=newNode; //头结点指向新结点
last=newNode;//更新尾结点
}else{ //如果不为空
Node oldlast=last;//记录原尾结点
Node newNode=new Node(t,oldlast,null); //新结点的前指针域指向尾结点
oldlast.next=newNode;//原尾结点指向新结点
last=newNode;//更新尾结点
}
N++;
}
//指定位置插入
public void insert(T t,int i){
if (i<0||i>=N){
throw new RuntimeException("位置不合法");
}
Node front=head;//记录头结点
for(int time=0;time<i;time++){
front=front.next; // 前结点front的“索引”为i-1,则需要循环i次
}
Node current=front.next;// 记录i 位置结点
Node newNode=new Node(t,front,current); //先指定新结点的前/后指针域
current.pre=newNode; //i 位置前指针指向新结点
front.next=newNode; //前结点后指针指向 i位置结点
N++;
}
public T get(int i){ // i是位置
Node n=head;
for(int time=0;time<i+1;time++){ //查找“索引”为i的元素,要循环i+1次
n=n.next;
}
return n.data;//返回"索引"为 i 结点的数据
}
public int indexOf(T t){
Node n=head; //记录头结点
for(int i=0;n.next!=null;i++){
n=n.next;
if(n.data.equals(t)){
return i; //i正好对应“索引”大小,所以返回i即可
}
}
return -1;
}
public T remove(int i) {
//先找到i元素前面的元素front,其索引为i-1,需遍历i次
Node front = head;
for (int time = 0; time < i; time++) {
front = front.next;
}
//记录“索引”为i的元素current
Node current = front.next;
if (i == (N - 1)) {//如果remove的是最后一个元素
front.next=null; //使front的后指针域与最后一个元素断开
this.last=front;//更新尾结点
return current.data;
}
else {
Node behind = current.next;//记录“索引”为 i 的后一个元素behind
//让pre和behind连接
front.next = behind;
behind.pre = front;
//元素数量减一
N--;
return current.data;
}
}
@Override
public Iterator iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator{
private Node n;
public MyIterator(){
this.n=head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public Object next() {
n=n.next;
return n.data;
}
}
public static void main(String[] args) {
DLinkList<String> list=new DLinkList<>();
System.out.println("-------");
list.add("中国");
list.add("美国");
list.add("俄罗斯");
list.add("法国");
for (Object data : list) {
System.out.println(data);
}// 输出: 中国 美国 俄罗斯 法国
System.out.println(list.length());//输出: 4
System.out.println("---------");
list.insert("加拿大",2);
System.out.println(list.indexOf("加拿大"));//输出: 2
System.out.println(list.get(2));// 输出: 加拿大
System.out.println("----------------");
System.out.println(list.length()); //输出: 5
}
}
注意:
- 链表无索引,此处为了理解,将元素位置假设为索引,和索引一样从0开始
- 头结点“索引”为-1,第一个元素“索引”为0,最后一个元素索引为 i-1
- 要得到索引为 i 的元素,要遍历 i+1 次
- 当涉及最后一个元素变动的时候需要更新尾结点last