Java学习笔记:数据结构之线性表(双向链表)

目录


Java数据结构之线性表

  1. 链表结构

    链式存储结构是基于指针实现,我们把一个数据元素和一个指针成为节点

    链式存储结构是用指针把相互直接关联的结点(即直接前驱节点或直接后继节点)链接起来。 链式存储结构的线性表成为链表

  2. 链表类型

    根据链表的构造方式的不同可以分为:

    ​ 1. 单向链表

    ​ 2. 循环链表

    ​ 3. 双向链表


线性表的抽象数据类型

1、线性表的置空操作clear()
2、线性表判空操作:isEmpty()
3、求线性表的长度:size( )
4、取元素操作:get( i )
5、插入操作:insert( i, x )
6、删除操作:delete( i)

双向链表

1、基本概念

​ 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表

2、图形理解

相对于单链表而言,每个节点不仅有一个next,而且有一个指向前一个元素的pre

3、链表特性

每个数据结点中都有两个指针,分别指向直接后继和直接前驱

4、优缺点
优点

​ 可以找到前驱和后继,可进可退

缺点

​ 增加删除节点复杂,多需要分配一个指针存储空间

优缺点总结

​ 适用于需要双向查找节点值的情况

5、代码展示
5.1、基础代码
/** 保存该链表的头结点 */
private Node head;
/** 保存该链表的尾结点 */
private Node tail;
/** 保存该链表中已包含的节点数 */
private int size;

/** 内部类 */
private class Node {
    // 保存节点数据
    private T data;
    // 指向上个节点的引用
    private Node prev;
    // 指向下个节点的引用
    private Node next;
    // 无参构造器
    public Node() {

    }
    // 初始化构造器
    public Node(T data) {
        this.data = data;
    }
    public Node(T data, Node prev) {
        this.data = data;
        this.prev = prev;
    }
    public Node(T data, Node prev, Node next) {
        this.data = data;
        this.prev = prev;
        this.next = next;
    }
}
5.2、插入数据
a、链表为空时
/**
 * <p>以指定数据元素来创建链表,Ps.(当前链表为空)</p>
 *
 * @param element   数据
 * @author Kallen
 * @since 2020/12/23 13:33
*/
public DoubleLink(T element) {
    head = new Node(element);
    tail = head;
    size++;
}
b、根据索引添加

添加节点

/**
 * <p>根据索引插入元素</p>
 *
 * @param element   元素
 * @param index     索引
 * @author Kallen
 * @since 2020/12/28 16:05
*/
public void insert(T element, int index) {
    
    if (index < 0 || index > size - 1) {
        throw new IndexOutOfBoundsException("索引越界");
    }
    // 判断当前链表是否为空
    if (head == null) {
        DoubleLink(element);
    }else {
        if (index == 0) {
            // 当index为0时,即为在链表头插入
            addAtHeader(element);
        }else {
            // 获取插入点的前一个节点
            Node prev = getNodeByIndex(index - 1);
            // 获取插入点的节点
            Node next = prev.next;
            // 让新节点的next引用指向next节点,prev引用指向prev节点
            Node newNode = new Node(element , prev , next);
            // 让prev的next指向新节点。
            prev.next = newNode;
            // 让prev的下一个节点的prev指向新节点
            next.prev = newNode;
            size++;
        }
    }
}
c、尾插法
/**
 * <p>尾插法</p>
 *
 * @param element       元素
 * @author Kallen
 * @since 2020/12/28 16:25
*/
public void add(T element) {
    // 如果该链表为空
    if (head == null) {
        DoubleLink(element);
    }else {
        // 创建新节点,新节点的pre引用指向原tail节点
        Node newNode = new Node(element , tail , null);
        // 让尾节点的next指向新增的节点
        tail.next = newNode;
        // 以新节点作为新的尾节点
        tail = newNode;
    }
    size++;
}
d、头插法
/**
 * <p>头插法</p>
 *
 * @param element       元素
 * @author Kallen
 * @since 2020/12/28 16:10
*/
public void addAtHeader(T element) {
    // 创建新节点,让新节点的next指向原来的head,并以新节点作为新的head
    head = new Node(element, head);
    // 如果插入之前是空链表
    if (tail == null)
    {
        tail = head;
    }
    size++;
}
5.3、查询数据
a、根据索引查询
/**
 * <p>获取指定索引处的元素</p>
 *
 * @param index         指定索引
 * @return {@link T}    元素
 * @author Kallen
 * @since 2020/12/23 13:35
*/
public T get(int index) {
    return getNodeByIndex(index).data;
}
b、根据指定元素查找索引
/**
 * <p>查询指定元素的索引</p>
 *
 * @param element       元素
 * @return {@link int}  索引
 * @author Kallen
 * @since 2020/12/25 15:48
*/
public int locate(T element) {
    Node current = head;
    for (int i = 0; i < size && current != null; i++, current = current.next) {
        if (current.data.equals(element)) {
            return i;
        }
    }
    return -1;
}
c、获取当前链表长度
/**
 * <p>获取链表长度</p>
 *
 * @return {@link int}  链表长度
 * @author Kallen
 * @since 2020/12/23 13:34
*/
public int size() {
    return size;
}
5.4、删除数据
a、根据索引删除

删除数据

/**
 * <p>根据索引删除</p>
 *
 * @param index     索引
 * @author Kallen
 * @since 2020/12/28 17:08
*/
public void delete(int index) {
    if (index < 0 || index > size - 1) {
        throw new IndexOutOfBoundsException("索引越界");
    }

    if (index == 0) {
        head = head.next;
        head.prev = null;
    }else {
        // 获取删除节点的前一个节点
        Node prev = getNodeByIndex(index-1);
        // 让prev节点的next指向被删除节点的next
        prev.next = prev.next.next;
        // 让被删除节点的next节点的prev指向prev节点 Ps(被删除节点的next节点不为尾节点)
        if (prev.next != null) {
            prev.next.prev = prev;
        }
        // 将被删除节点的prev、next引用赋为null
        prev.next.prev = null;
        prev.next.next = null;
    }
    size--;
}
b、删除最后一个元素
/**
 * <p>删除链表的最后一个元素</p>
 *
 * @author Kallen
 * @since 2020/12/28 17:15
*/
public void remove() {
    delete(size-1);
}
5.5、判断是否为空
/**
 * <p>判断链表是否为空链表</p>
 *
 * @author Kallen
 * @since 2020/12/28 17:16
*/
public boolean isEmpty() {
    return size == 0;
}
5.6、清空链表
/**
 * <p>清空链表</p>
 *
 * @author Kallen
 * @since 2020/12/28 17:17
*/
public void clear() {
    head = null;
    tail = null;
    size = 0;
}
5.7、私有方法
/**
 * <p>根据指定索引获取结点</p>
 *
 * @param index     					索引
 * @return {@link DoubleLink<T>.Node}   节点
 * @author Kallen
 * @since 2020/12/23 13:36
*/
private Node getNodeByIndex(int index) {
    if (index < 0 || index > size - 1) {
        throw new IndexOutOfBoundsException("索引越界");
    }

    // 当索引值小于该链表长度的一半时,应从头结点开始搜索,PS(值相同从头结点开始搜索)
    if (index <= size / 2) {
        Node current = head;
        for (int i = 0; i <= size / 2 && current != null; i++, current = current.next) {
            if (i == index) {
                return current;
            }
        }
    }else {
        // 从tail节点开始搜索
        Node current = tail;
        for (int i = size - 1; i > size / 2 && current != null; i++, current = current.prev) {
            if (i == index) {
                return current;
            }
        }
    }
    return null;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值