双向链表的Java实现

一.概述
双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。
链表的头结点的数据域不存储数据,前指针域为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
    }
}

注意:

  1. 链表无索引,此处为了理解,将元素位置假设为索引,和索引一样从0开始
  2. 头结点“索引”为-1,第一个元素“索引”为0,最后一个元素索引为 i-1
  3. 要得到索引为 i 的元素,要遍历 i+1 次
  4. 当涉及最后一个元素变动的时候需要更新尾结点last
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值