动态链表----单向链表的实现

1. 单向链表的定义:单向链表就是线性结构链式存储方式的具体实现,简称为:单链表。

2. 单向链表的实现:单向链表的方法与线性表的方法相似,因此我们可以使单向链表实现线性表List的接口。

//单向链表的实现
public class LinkedSinglyList<E> implements List<E> {

    /*将数据封装成节点 节点包括两部分 数据域和指针域
    数据域中存放的是数据内容
    指针域中存放的是当前节点的下一跳节点的地址 即指针域指向当前节点的下一跳节点
     */
    class Node{
        E data; //表示节点的数据域
        Node next; //表示节点的指针域

        public Node(){ //节点的无参构造
            this(null, null);
        }
        public Node(E element){ //节点的带参构造 参数为数据的内容
            this(element, null);
        }
        public Node(E element, Node next){ //节点的带参构造 参数为数据的内容和下一个节点的地址
            this.data = element;
            this.next = next;
        }

        //格式化节点输出时的格式 即输出数据内容即可
        @Override
        public String toString() {
            return data.toString();
        }
    }

    //表示头指针 指向链表中的头节点
    private Node head;
    //表示尾指针 指向链表中的尾节点
    private Node tail;
    //表示链表中有效元素的个数
    private int size;

    public LinkedSinglyList(){ //单向链表的无参构造 为head tail size初始化
        head = null;
        tail = null;
        size = 0;
    }

    public LinkedSinglyList(E[] arr){ //单向链表的带参构造 参数为一个数组 将数组中的元素依次添加到链表中
        if(arr == null || arr.length == 0){
            throw new IllegalArgumentException("arr is null");
        }
        for (int i = 0; i < arr.length; i++){
            add(arr[i]);
        }
    }

    //向链表的表尾追加元素
    @Override
    public void add(E element) {
        add(size, element);
    }

    //向链表的指定位置添加元素
    @Override
    public void add(int index, E element) {
        if(index < 0 || index > size){ //判断索引是否存在
            throw new IllegalArgumentException("add index out of range");
        }

        Node node = new Node(element); //创建节点对象

        if(size == 0) { //当表空时 添加元素 只需头尾指针同时指向要添加的节点即可
            head = node;
            tail = node;
        }else if(index == 0) { //向表头添加元素 先要将要添加节点的下一跳指向头节点 再将头指针指向要添加的节点
            node.next = head;
            head = node;
        }else if(index == size) { //向表尾添加元素 先将尾节点的下一跳指向要添加的节点 再将尾指针指向要添加的节点
            tail.next = node;
            tail = node;
        }else { //向表中间添加元素 先遍历到要添加的位置的前驱 将前驱节点的下一跳赋值给要添加节点的下一跳 前驱节点的下一跳指向要添加的节点
            Node p = head;
            for(int i = 0; i < index - 1; i++){
                p = p.next;
            }
            node.next = p.next;
            p.next = node;
        }
        size++;
    }

    //删除链表中的指定元素
    @Override
    public void remove(E element) {
        remove(indexOf(element));
    }

    //删除链表中的指定位置的元素 并返回要删除元素的内容
    @Override
    public E remove(int index) {
        if(index < 0 || index >= size){ //判断索引是否存在
            throw new IllegalArgumentException("remove index out of range");
        }

        E ele = null;
        if(size == 1) { //当表中只有一个元素时删除
            /*
            当表中只有一个元素时删除:只需要先取出那一个元素 再将头指针head 尾指针tail同时指向null即可
             */
            ele = head.data;
            head = null;
            tail = null;
        }else if(index == 0) { //删除表头元素
            /*
            删除表头元素:
            1.定义一个新的节点使其也指向头节点
            2.取出这个新节点的元素
            3.更新头指针 使其指向当先头节点的下一个节点
            4.使新节点的指针域指向null
             */
            Node n = head;
            ele = n.data;
            head = n.next;
            n.next = null;
        }else if(index == size - 1) { //删除表尾元素
            /*
            删除表尾元素:
            1.先定义一个新节点p 使其开始指向头节点 遍历链表 直至新节点p指向链表的尾节点前驱节点
            2.取出尾节点的数据
            3.使当前新节点p的下一跳指null
            4.更新尾指针 是尾指针指向p节点
             */
            Node p = head;
            while (p.next != tail){
                p = p.next;
            }
            ele = tail.data;
            p.next = null;
            tail = p;
        }else { //删除表中间元素
            /*
            删除表中间元素:
            1.先定义一个新节点p 使其开始指向头节点 遍历链表 直至遍历到指定索引的前驱节点的索引 每轮更新p节点
            2.再定义一个节点为要删除的节点n 即当前p节点的下一跳节点 取出要删除的节点n的内容
            3.使p节点的下一跳重新指向要删除的节点n的下一跳
            4.使要删除的节点n的下一跳指向null
             */
            Node p = head;
            for(int i = 0; i < index - 1; i++){
                p = p.next;
            }
            Node n = p.next;
            ele = n.data;
            p.next = n.next;
            n.next = null;
        }
        size--;
        return ele;
    }

    //获取指定索引处的节点的值
    @Override
    public E get(int index) {
        if(index < 0 || index >= size){ //判断索引是否存在
            throw new IllegalArgumentException("get index out of range");
        }
        if(index == 0){ //获取表头元素 即头指针指向的节点的值
            return head.data;
        }else if(index == size - 1){ //获取表尾元素 即尾指针指向的节点的值
            return tail.data;
        }else{ //获取表中间某一索引的值
            /*
            获取表中间某一索引的值:定义一个新节点p使其指向头节点 从表头开始遍历 遍历到索引位置 每次更新p节点
            最后p节点就是要获取节点 返回该节点的值即可
             */
            Node p = head;
            for(int i = 0; i < index; i++){
                p = p.next;
            }
            return p.data;
        }
    }

    //修改指定位置的节点的值 并返回要修改节点的值
    @Override
    public E set(int index, E element) {
        if(index < 0 || index >= size){ //判断索引是否存在
            throw new IllegalArgumentException("set index out of range");
        }
        E ele = null;
        if(index == 0){ //修改表头节点的值 即先获取表头结点的值 再将表头结点的值修改为指定的值element即可
            ele = head.data;
            head.data = element;
        }else if(index == size - 1){ //修改表尾节点的值 即先获取表尾结点的值 再将表尾结点的值修改为指定的值element即可
            ele = tail.data;
            tail.data = element;
        }else{ //修改表中间位置的节点的值
            /*
            修改表中间位置的节点的值:定义一个新节点p使其指向头节点
            从头节点开始遍历 遍历到索引位置的前驱位置 每次更新p节点为p节点的下一个节点
            遍历结束后 p节点即指定要修改的索引处的节点 获取p节点的值
            修改p节点的值为element
             */
            Node p = head;
            for(int i = 0; i < index; i++){
                p = p.next;
            }
            ele = p.data;
            p.data = element;
        }
        return ele;
    }

    //获取链表有效元素的个数
    @Override
    public int size() {
        return size;
    }

    //获取指定元素第一次在链表出现的索引 如果链表中没有 即返回-1
    @Override
    public int indexOf(E element) {
        /*
        定义一个新节点p使其指向头节点
        遍历链表 直到p节点的数据与指定数据相等即可
        每次更新p节点为p节点的下一个节点 索引index的值
         */
        int index = 0;
        Node p = head;
        while (!p.data.equals(element)){
            p = p.next;
            index++;
            if(p == null){
                return -1;
            }
        }
        return index;
    }

    //判断链表中是否包含指定元素
    @Override
    public boolean contains(E element) {
        return indexOf(element) != -1;
    }

    //判断链表是否为空
    @Override
    public boolean isEmpty() {
        return size == 0 && (head == null && tail == null);
    }

    //清空链表中的所有元素
    @Override
    public void clear() {
        head = null;
        tail = null;
        size = 0;
    }

    //对链表进行排序 选择排序的思想
    @Override
    public void sort(Comparator<E> c) {
        if(c == null){ //判断比较器是否为空
            throw new IllegalArgumentException("comparator can not be null");
        }
        if(size == 0 || size == 1){ //如果链表中有效元素的个数等于0或1 就不需要在对链表进行排序
            return;
        }
        Node nodeA = head; //定义一个节点A 使其指向头节点
        Node nodeB = nodeA.next; //定义一个节点B 使其指向节点A的下一个节点
        while (true){ //第一层循环 操作节点A
            while (true){ //第二层循环 操作节点B
                if(c.compare(nodeA.data, nodeB.data) > 0){ //如果节点A的值大于节点B的值 就交换节点A的数据和节点B的数据的位置
                    swap(nodeA, nodeB);
                }
                if(nodeB == tail){ //如果节点B等于尾节点 就中断内层循环
                    break;
                }
                //更新节点B为当前节点B的下一个节点
                nodeB = nodeB.next;
            }
            if(nodeA.next == tail){ //如果节点A的下一跳指向尾节点 就中断外层循环
                break;
            }
            nodeA = nodeA.next; //更新节点A为当前节点A的下一个节点
            nodeB = nodeA.next; //更新节点B为当前节点B的下一个节点
        }
    }

    //交换两个节点的值的位置
    private void swap(Node nodeA, Node nodeB) {
        E temp = nodeA.data;
        nodeA.data = nodeB.data;
        nodeB.data = temp;
    }

    //获取链表中指定索引范围的子链表 [fromIndex, toIndex]
    /*
    先定义一个节点A 使其指向头节点 遍历链表 更新节点A 使其到达fromIndex位置处
    再定义一个节点B 也使其指向头节点 遍历链表 更新节点B 使其到达toIndex位置处
    定义一个新节点使其指向节点A 从fromIndex位置遍历到toIndex位置 最后使其指向节点B
    将[fromIndex, toIndex]范围内的节点重新依次加入到新的子链表中
     */
    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        if(fromIndex < 0 || fromIndex > toIndex || toIndex >= size){ //判断两个索引是否存在
            throw new IllegalArgumentException("must 0 <= fromIndex <= toIndex <= size - 1");
        }
        LinkedSinglyList<E> linkedList = new LinkedSinglyList<E>();
        Node nodeA = head;
        for(int i = 0; i < fromIndex; i++){
            nodeA = nodeA.next;
        }
        Node nodeB = head;
        for(int i = 0; i < toIndex; i++){
            nodeB = nodeB.next;
        }
        Node p = nodeA;
        for(int i = fromIndex; i < toIndex; i++){
            linkedList.add(p.data);
            p = p.next;
        }
        return linkedList;
    }

    //格式化链表输出是的格式
    @Override
    public String toString() {
        StringBuilder str = new StringBuilder();
        str.append("LinkedSinglyList: " + size + " [");
        if(isEmpty()){
            str.append(']');
        }
        Node p = head;
        while (true){
            str.append(p.data);
            p = p.next;
            if(p == tail){
                str.append(']');
                break;
            }
            str.append(',');
            str.append(' ');
        }
        return str.toString();
    }

    //获取当前这个数据结构/容器 的 迭代器
    //通过迭代器对象 更方便挨个取出每一个元素
    //同时 实现了Iterable 可以让当前的数据结构/容器 被foreach循环遍历
    @Override
    public Iterator<E> iterator() {
        return new LinkedSinglyListIterator();
    }

    class LinkedSinglyListIterator implements Iterator<E>{

        private Node cur; //定义一个游标节点

        @Override
        public boolean hasNext() { //判断是否有下一个节点
            return cur != null; //当cur不为null时 表示有下一个节点
        }

        @Override
        public E next() { //获取下一个节点的值
            E ele = cur.data; //先获取节点cur的值
            cur = cur.next; //再更新cur节点为当前cur节点的下一个节点
            return ele;
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值