【数据结构与算法】2 - 链表

  1. 动态数组有一个明显的缺点:可能造成内存空间的大量浪费

  2. 能否用到多少就申请多少内存? 链表就可以,链表是一种链式存储的线性表,所有元素的内存地址不一定是连续的。

1. 链表的设计

链表的设计
  1. 链表大部分接口和动态数组是一样的,对其进行抽取。将公共的方法抽取到抽象类中实现抽象类在实现公共的接口。

1.1 接口设计

类与接口之间的UML类图
  1. 起名为 ListForObject 是因为其适应所有数据类型的数据。

  2. ListForObject接口定义了所有需要实现的方法。和静态常量 ELEMENT_NOT_FOUND

  public interface ListForObject {

    static final int ELEMENT_NOT_FOUND = -1;

    /**
     * 数组长度
     *
     * @return
     */
    int size();

    /**
     * 数组是否为空
     *
     * @return
     */
    boolean isEmpty();

    /**
     * 是否包含某个元素
     *
     * @param element
     * @return
     */
    boolean contains(E element);

    /**
     * 添加元素到后面
     *
     * @param element
     */
    void add(E element);

    /**
     * 放回index位置对应的元素
     *
     * @param index
     * @return
     */
    E get(int index);

    /**
     * 设置index位置的元素
     *
     * @param index
     * @param element
     * @return
     */
    E set(int index, E element);

    /**
     * 在index位置添加元素
     *
     * @param index
     * @param element
     */
    void add(int index, E element);

    /**
     * 删除index位置的元素
     *
     * @param index
     * @return
     */
    E remove(int index);

    /**
     * 查看元素的位置
     *
     * @param element
     * @return
     */
    int indexOf(E element);

    /**
     * 清除所有元素
     */
    void clear();
}
  1. 定义抽象方法实现ListForObject接口 , 实现公共方法,由于是抽象方法,不需要实现接口的全部方法,将需要实现的方法交给子类实现。
public abstract class AbstractList implements ListForObject {
    protected int size;

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

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

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

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


    /**
     * 检查索引范围
     *
     * @param index
     */
    protected void rangeCheck(int index) {
        if (index < 0 || index >= size) {
            outOfBounds(index);
        }
    }

    /**
     * 数组下标越接将抛出异常
     *
     * @param index
     */
    private void outOfBounds(int index) {
        throw new IndexOutOfBoundsException("index = " + index + " size = " + size);
    }

    /**
     * 添加元素的时候 index 索引检查
     *
     * @param index
     */
    protected void rangeCheckForAdd(int index) {
        if (index < 0 || index > size) {
            outOfBounds(index);
        }
    }
}
  1. 定义实现类 LinkedList 继承抽象类 AbstractList ,实现未实现的方法:
    public class LinkedList extends AbstractList {

    private Node firstNode;

    /**
     * 静态内部类
     *
     * @param 
     */
    private static class Node {
        E element;
        Node next;

        private Node(E element, Node next) {
            this.element = element;
            this.next = next;
        }
    }

    /**
     * 获取 index 位置节点的 element
     * 由于获取节点中进行了 index 范围判断所以不需要在判断
     *
     * @param index
     * @return
     */
    @Override
    public E get(int index) {
        return node(index).element;
    }

    /**
     * 设置 index 位置节点的元素信息为 element
     *
     * @param index
     * @param element
     * @return
     */
    @Override
    public E set(int index, E element) {
        rangeCheck(index);
        Node node = node(index);
        E oldElement = node.element;
        node.element = element;
        return oldElement;
    }

    /**
     * 向链表中指定位置添加一个元素
     *
     * @param index
     * @param element
     */
    @Override
    public void add(int index, E element) {
        // 考虑当 index 等于 0 的情况
        if (index == 0) {
            // firstNode 位置存的是第一个节点的地址
            firstNode = new Node<>(element, firstNode);
        } else {
            // 在 index 位置插入数据需要先找到 index - 1 位置
            Node prev = node(index - 1);
            // 注意前后 prev.next 是不一样的
            prev.next = new Node(element, prev.next);
        }
        size++;
    }

    /**
     * 从链表中移除一个 节点
     *
     * @param index
     * @return
     */
    @Override
    public E remove(int index) {
        Node node = firstNode;
        if (index == 0) {
            firstNode = firstNode.next;
        } else {
            // 需要移除节点的上一个节点
            Node prev = node(index - 1);
            node = prev.next;
            prev.next = node.next;
        }
        size--;
        return node.element;
    }

    /**
     * 查询 某个元素在链表中的位置
     *
     * @param element
     * @return
     */
    @Override
    public int indexOf(E element) {
        Node node = firstNode;
        if (element == null) {
            // 遍历链表
            for (int i = 0; i < size; i++) {
                if (node.element == null) {
                    return i;
                }
                node = node.next;
            }
        } else {
            // 遍历链表
            for (int i = 0; i < size; i++) {
                if (node.element.equals(element)) {
                    return i;
                }
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

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

    /**
     * 返回第 index 个节点
     *
     * @param index
     * @return
     */
    private Node node(int index) {
        rangeCheck(index);
        Node node = firstNode;

        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }


    /**
     * 重写 toString
     *
     * @return
     */
    @Override
    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("size = ").append(size).append(" ; ").append(" [  ");
        // firstNode 指向第一个节点
        Node node = firstNode;
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                string.append(",");
            }
            string.append(node.element);
            node = node.next;
        }
        string.append("  ] ");
        return string.toString();
    }
}

1.2 实现细节

  1. 获取指定 index 位置的节点 :Node node(int index)
/**
     * 返回第index 个节点
     *
     * @param index
     * @return
     */
    private Node node(int index) {
        rangeCheck(index);
        Node node = firstNode;

        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }
  1. 实现链表的添加操作: void add(int index, E element) 首先,需要获取到需要添加节点位置的前一个节点 (node(index-1)), newNode.next = prev.next;prev.next = newNode
  /**
     * 向链表中指定位置添加一个元素
     *
     * @param index
     * @param element
     */
    @Override
    public void add(int index, E element) {
        // 考虑当 index 等于 0 的情况
        if (index == 0) {
            // 在 index 位置插入数据需要先找到 index - 1 位置
            Node prev = node(index - 1);
            // firstNode 位置存的是第一个节点的地址
            firstNode = new Node<>(element, firstNode);
        } else {
            // 在 index 位置插入数据需要先找到 index - 1 位置
            Node prev = node(index - 1);
            // 注意前后 prev.next 是不一样的
            prev.next = new Node(element, prev.next);
        }
        size++;
    }

2. 【LeetCode】删除链表中的节点

  1. 题目 : 题目链接
删除链表中的节点
  1. 材料 :
    /**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     public void deleteNode(ListNode node) {
       
    }
}
  1. 分析 :删除传递过来的节点
  • 使用该节点的下一个节点的值覆盖该节点的值。
  • 将该节点的 next 指向下一个节点的next;
删除链表中的节点
  1. 代码实现:
  public class ListNode {
    int val;
    ListNode next;

    public ListNode(int x) {
        val = x;
    }
}
     public void deleteNode(ListNode node) {
        // 使用下一个节点的值将自己当前的值覆盖
        node.val = node.next.val;
        // 将下一个节点的 next 赋值给自己
        node.next = node.next.next;
    }

3. 【LeetCode】反转链表(非递归)

  1. 题目: 题目链接
反转链表
  1. 材料:
  /**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {

    public ListNode reverseList(ListNode head) {
   }
}
  1. 解题思路分析(迭代方式) :
  • 需要定义一个新的头节点 ListNode newNode = null
  • 需要定义一个临时变量 ListNode temp = header.next 用于存放变换之后的头节点。
  • 将旧链表的头节点插入到新链表的头部,head.next = newNode; 将新链表的头指针指向链表的头部。
  • 旧链表的头指针重新指向新的头节点。head = temp;
解题思路
  1. 代码实现 :
  public class ListNode {
    int val;
    ListNode next;

    public ListNode(int x) {
        val = x;
    }
}
    /**
     * 使用迭代的方式实现 链表反转
     *
     * @param head
     * @return
     */
    private static ListNode iterationList(ListNode head) {

        if (head == null || head.next == null) {
            return null;
        }
        ListNode newHead = null;
        while (head != null) {
            ListNode temp = head.next;
            head.next = newHead;
            newHead.next = head;
            head = temp;
        }
        return newHead;
    }

4. 【LeetCode】判断一个链表是否环形链表

  1. 题目:环形链表
判断一个链表是否是环形链表
  1. 解题分析 :
解题分析
  • 使用快慢指针进行求解 , slow 作为慢指针每次步进 1。 slow=slow.next 。fast作为快指针每次步进 2 。 fast = fast.next.next; 由于一快一慢,如果链表有环则快慢指针就会相遇。 fast = slow。
  • 由于快指针步进比较大,如果链表没环则优先到达链表末端。 fast == null 或者 fast.next == null;
  1. 代码实现 :
     public boolean hasCycle(ListNode head) {
       // 1. 如果头结点为空或者只有一个节点表示没有环
       if (head == null || head.next == null) {
           return false;
       }

       ListNode slowNode = head;
       ListNode fastNode = head.next;

       // 由于 fast 走的快, 如果 fast 遇到节点为 null 表示不是环形链表
       while (fastNode != null && fastNode.next != null) {
           if (slowNode == fastNode) {
               return true;
           }
           // 走一步
           slowNode = slowNode.next;
           // 走两步
           fastNode = fastNode.next.next;
       }
       // 如果开始条件都不满足则返回false
       return false;
   }

你可能感兴趣的:(【数据结构与算法】2 - 链表)