数据结构和算法(二)--单向(循环)链表(LinkedList)

数据结构和算法(二)–单向(循环)链表(LinkedList)

动态数组的缺点
  • 扩容和缩容时,会循环遍历所有元素,当数据量很大时,此操作会有一些效率问题
  • 再扩容和缩容之前会预留一部分空间,此空间是不存储内容的,而且数组长度越大预留的空间就会越多(按比例),会造成内存浪费
链表(是一种链式存储的线性表,内存地址不一定连续)
  • 链表本身有一个内部类表示一个一个的节点(Node),链表本身不存储数据,只是指向头节点
  • 节点是存储数据内容的真正的类,同时它也会指向下一个节点
  • 链表在进行添加操作时,会新建一个Node类来存储数据,并指向下一个节点,因此每一个节点的内存地址不一定是连续的
  • 通过控制节点的指针可以实现删除,改变顺序,增加等操作
    链表数据结构
链表方法设计思路
  1. getNode(int index)
    传入index,遍历此链表,返回一个node节点,这个方法是为了在链表内部可以快速找到任何index处的节点对象
  2. add(E element)
    调用add(E element,int index)方法,使用参数新建一个node节点获取到最后一个节点,并使最后一个节点的next指向新建的节点
  3. remove(int index)
    找到index-1处的节点,并使其next指向index+1处的节点,注意index==0时要特殊处理
  4. set(int index,E element)
    找到index处的节点,设置其element属性为传入的参数
  5. indexOf(E element)
    循环遍历每一个节点,使用equals判断,当传入是null时需要特殊处理
  6. clear()
    firstNode设置为null,size设置为0
代码实现(配合上面设计思路观看)
public class CustomLinkedList<E> implements CustomList<E> {

    private Node<E> firstNode; // 指向第一个节点

    private int size; // 表示链表长度

    public CustomLinkedList() {
        size = 0;
        firstNode = null;
    }

    /**
     * 内部类,表示每一个节点
     */
    private static class Node<E> {
        E element; // 存放数据的内容
        Node<E> next; // 指向下一个节点

        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public boolean contains(E element) {
        return indexOf(element) != -1;
    }

    @Override
    public void add(E element) {
        add(size,element);
    }

    @Override
    public E get(int index) {
        Node<E> node = getNode(index);
        return node.element;
    }

    @Override
    public E set(int index, E element) {
        E oldObj = get(index);
        getNode(index).element = element;
        return oldObj;
    }

    @Override
    public void add(int index, E element) {
        if (index < 0 || index > size) {
            throw new RuntimeException("索引越界错误");
        }
        if (index == 0){
            firstNode = new Node<E>(element, firstNode);
        }else {
            getNode(index - 1).next = new Node<E>(element, getNode(index - 1).next);
        }

        size++;
    }

    @Override
    public E remove(int index) {
        Object oldObj = get(index);
        if (index == 0){
            firstNode = firstNode.next;
        }else {
            getNode(index - 1).next = getNode(index).next;
        }
        size--;
        return (E) oldObj;
    }

    @Override
    public int indexOf(E element) {
        Node<E> node = this.firstNode;
        for (int i = 0; i < size; i++) {
            if (element == node.element){
                return i;
            }else if (element != null && element.equals(node.element)) {
                return i;
            }
            node = node.next;
        }
        return -1;
    }

    @Override
    public void clear() {
        size = 0;
        firstNode = null;
    }


    private Node<E> getNode(int index) {
        checkIndex(index);
        Node<E> node = this.firstNode;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }


    private void checkIndex(int index) {
        if (index < 0 || index >= size) {
            throw new RuntimeException("索引越界错误");
        }
    }

    @Override
    public String toString() {
        Node<E> node = this.firstNode;
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < size; i++) {
            if (i == 0) {
                stringBuilder.append("[");
            } else {
                stringBuilder.append(",");
            }
            stringBuilder.append(node.element);
            node = node.next;
        }
        stringBuilder.append("]").append(" ==> 链表长度为:").append(size);
        return stringBuilder.toString();
    }
}

时间复杂度分析
  1. add(int index, E element)
    • 最大时间复杂度:O(n)
    • 最小时间复杂度:O(1)
    • 平均时间复杂度:O(n)
  2. get(int index)
    • 最大时间复杂度:O(n)
    • 最小时间复杂度:O(1)
    • 平均时间复杂度:O(n)
  3. set(int index, E element)
    • 最大时间复杂度:O(n)
    • 最小时间复杂度:O(1)
    • 平均时间复杂度:O(n)
  4. remove(int index)
    • 最大时间复杂度:O(n)
    • 最小时间复杂度:O(1)
    • 平均时间复杂度:O(n)
  5. indexOf(E element)
    • 最大时间复杂度:O(n)
    • 最小时间复杂度:O(1)
    • 平均时间复杂度:O(n)
链表改造(不推荐)
  • 增加虚拟头节点,可以统一处理节点逻辑
    增加虚拟头节点
练习
1. 传入一个节点对象,删除一个节点
  • 链接:https://leetcode-cn.com/problems/delete-node-in-a-linked-list/
  • 思路:获取传入节点的下一个节点的element,覆盖掉传入节点的element,并使传入节点的next指向下一个节点的下一个节点
  • 代码实现:
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
}
2. 反转链表
  • 链接:https://leetcode-cn.com/problems/reverse-linked-list/
  • 思路1:递归执行,先保存head的值,后面的值往前一个节点移动,再把最后一个节点的值变为保存的值
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode fir = head;
        if(head != null){
            if(head.next != null){
                reverseList(head.next);
            }
            int a = head.val;
            while (head.next != null){
                head.val = head.next.val;
                head = head.next;
            }
            head.val = a;
        }
        return fir;
    }
}
  • 思路2:递归执行,变换指针,把节点看成一个一个的个体,别管执行过程中一个节点有多个指针指向的问题,关键是最后返回一个newHead(如果想返回原来的head会变得更复杂)
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next==null)return head;
        ListNode node = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return node;
    }
}
  • 思路3:迭代执行,创建临时遍历存储head的下一个节点,遍历每一个节点,先使当前节点的next指向newHead,再让newHead指向当前节点,再把当前节点指向零时存储的下一个节点
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode temp = null;
        ListNode now = head;
        ListNode newHead = null;
        while (now !=null ){
            temp = now.next;
            now.next = newHead;
            newHead = now;
            now = temp;
        }
        return newHead;
    }
}
3. 判断链表是否有环
  • 链接:https://leetcode-cn.com/problems/linked-list-cycle/
  • 思路:快慢指针
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) return false;
        ListNode fast = head.next;
        ListNode low = head;
        while (fast != null) {
            low = low.next;
            if(fast.next ==  null){
                return false;
            }
            fast = fast.next.next;
            if (low == fast){
                return true;
            }
        }
        return false;
    }
}
单向循环链表
  • 单向循环链表和单向链表的区别就是,在尾节点的next原本指向的是null,现在指向的是firstNode
循环链表实现思路分析
  1. 其中大部分方法和单向链表相同,只有增加或者删除的时候需要注意设置最后一个节点的next属性
  2. add(int index,E element)
    往index==0处添加节点时需要注意,先把最后一个节点找出来,然后添加新节点到0处,firstNode指向它,再把之前的最后一个节点指向firstNode节点。其中需要注意的是,要先查询出最后一个节点才能改变firstNode的值。
  3. remove(int index)
    对index==0处的节点进行删除操作时,先把最后一个节点查出来,然后把firstNode指向下一个节点,再把最后一个节点指向现在的firstNode。注意,查询最后一个节点必须要再移动firstNode之前,因为查询是传入的是size-1,而移动了firstNode之后size没有马上-1从而造成索引越界。
代码实现(重点看add和remove方法和原来的有什么区别)
/**
 * 单向循环链表
 * @author maolin yuan
 * @version 1.0
 * @date 2020/12/17 17:10
 */
public class CustomCircleLinkedList<E> implements CustomList<E>{

    private Node<E> firstNode; // 指向第一个节点

    private int size; // 表示链表长度

    public CustomCircleLinkedList() {
        size = 0;
        firstNode = null;
    }

    /**
     * 内部类,表示每一个节点
     */
    private static class Node<E> {
        E element; // 存放数据的内容
        Node<E> next; // 指向下一个节点

        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "element=" + element +
                    ", next=" + next.element +
                    '}';
        }
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public boolean contains(E element) {
        return indexOf(element) != -1;
    }

    @Override
    public void add(E element) {
        add(size,element);
    }

    @Override
    public E get(int index) {
        Node<E> node = getNode(index);
        return node.element;
    }

    @Override
    public E set(int index, E element) {
        E oldObj = get(index);
        getNode(index).element = element;
        return oldObj;
    }

    @Override
    public void add(int index, E element) {
        if (index < 0 || index > size) {
            throw new RuntimeException("索引越界错误");
        }
        if (index == 0){
            if (size ==0){
                firstNode = new Node<E>(element, firstNode);
                firstNode.next = firstNode;
            }else {
                Node<E> node = getNode(size - 1);
                firstNode = new Node<E>(element, firstNode);
                node.next = firstNode;
            }
        }else {
            getNode(index - 1).next = new Node<E>(element, getNode(index - 1).next);
        }

        size++;
    }

    @Override
    public E remove(int index) {
        Object oldObj = get(index);
        if (index == 0){
            Node<E> node = getNode(size - 1);
            firstNode = firstNode.next;
            node.next = firstNode;

        }else {
            getNode(index - 1).next = getNode(index).next;
        }
        size--;
        return (E) oldObj;
    }

    @Override
    public int indexOf(E element) {
        Node<E> node = this.firstNode;
        for (int i = 0; i < size; i++) {
            if (element == node.element){
                return i;
            }else if (element != null && element.equals(node.element)) {
                return i;
            }
            node = node.next;
        }
        return -1;
    }

    @Override
    public void clear() {
        size = 0;
        firstNode = null;
    }


    private Node<E> getNode(int index) {
        checkIndex(index);
        Node<E> node = this.firstNode;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }


    private void checkIndex(int index) {
        if (index < 0 || index >= size) {
            throw new RuntimeException("索引越界错误");
        }
    }

    @Override
    public String toString() {
        Node<E> node = this.firstNode;
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < size; i++) {
            if (i == 0) {
                stringBuilder.append("[");
            } else {
                stringBuilder.append(",");
            }
            stringBuilder.append(node);
            node = node.next;
        }
        stringBuilder.append("]").append(" ==> 链表长度为:").append(size);
        return stringBuilder.toString();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值