Skip to content

链表

链表 Linked List

链表是有序列表

存储结构

头指针head 110

地址 data域 next域
110   a1   130
120   a3   
130   a2   120

单链表带头节点的逻辑结构

head ->[头节点|next]-> [data|next] -> [data|^]

小结:

  • 链表以节点方法链式存储
  • 每个节点包含data域、next域(指向下一个节点)
  • 链表各节点不一定是连续存储的
  • 链表分带头节点的链表和没有头节点的链表

head头节点 不存放数据,表示单链表的头

代码示例

java
package com.demo.linkedlist;

public class SingleLinkedListDemo {
    public static void main(String[] args) {
        SingleLinkedList linkedList = new SingleLinkedList();

        Node node1 = new Node(1, "张飞");
        Node node2 = new Node(2, "刘备");
        Node node3 = new Node(3, "关羽");


        // 无序插入
        // linkedList.add(node1);
        // linkedList.add(node3);
        // linkedList.add(node2);

        // 有序插入
        linkedList.addByOrder(node1);
        linkedList.addByOrder(node3);
        linkedList.addByOrder(node2);

        Node node4 = new Node(2, "赵云");
        linkedList.update(node4);

        linkedList.delete(node1);

        linkedList.list();

    }
}


class SingleLinkedList {
    // 头节点不存实际数据
    private Node head = new Node(0, null);

    /**
     * 链表最后添加节点
     */
    public void add(Node node) {
        Node temp = head;

        // 查找链表尾部
        while (temp.next != null) {
            // 指针后移
            temp = temp.next;
        }

        // 最后一个节点
        temp.next = node;
    }

    /**
     * 按照大小排序
     */
    public boolean addByOrder(Node node) {
        Node temp = head;

        boolean flag = false; // 是否存在该节点

        while (temp.next != null) {
            // 判断数据是否存在
            if (temp.next.id == node.id) {
                flag = true;
                break;
            }
            // 找到数据插入位置
            else if (temp.next.id > node.id) {
                break;
            }
            temp = temp.next;
        }

        if (!flag) {
            // 修改指针指向
            node.next = temp.next;
            temp.next = node;
        }
        return !flag;
    }

    /**
     * 查找到指向节点的前一个节点
     * BeforeNode.next -> Node
     */
    public Node findBeforeNode(Node node) {
        Node temp = head;

        while (temp.next != null) {
            if (node.id == temp.next.id) {
                return temp;
            }
            temp = temp.next;
        }

        return null;
    }

    /**
     * 查找节点
     */
    public Node findNode(Node node){
        Node beforeNode = findBeforeNode(node);

        if(beforeNode != null){
            return beforeNode.next;
        } else{
            return null;
        }
    }
    /**
     * 修改节点
     */
    public boolean update(Node node) {
        Node beforeNode = findBeforeNode(node);

        if (beforeNode != null) {
            beforeNode.next.data = node.data;
            return true;
        } else {
            return false;
        }

    }

    /**
     * 删除节点 GC会自动清除没有被引用的对象
     */
    public boolean delete(Node node) {
        Node beforeNode = findBeforeNode(node);

        if (beforeNode != null) {
            beforeNode.next = beforeNode.next.next;
            return true;
        } else {
            return false;
        }
    }

    /**
     * 显示链表
     */
    public void list() {

        Node temp = head;

        // 遍历
        while (temp.next != null) {
            // 指针后移
            temp = temp.next;

            System.out.println(temp);
        }
    }
}


class Node {
    public int id;
    public String data;
    public Node next;

    public Node(int id, String data) {
        this.id = id;
        this.data = data;
    }

    @Override
    public String toString() {
        return "Node{" +
                "id=" + id +
                ", data='" + data + '\'' +
                '}';
    }
}

面试题

1、获取单链表的有效节点个数(如果有头节点,不统计)

java
class SingleLinkedList {
    /**
     * 统计节点个数
     */
    public int length(){
        Node temp = head;
        int len = 0;

        while (temp.next != null){
            len ++;
            temp = temp.next;
        }
        return len;
    }
}

2、查找单链表中倒数第k个节点

思路: (1)先遍历单链表,获得链表长度 (2)再遍历链表,得到第 (size-index) 个节点

java
class SingleLinkedList {
    /**
     * 从链表尾部开始获取节点, index从0开始
     * 0 <= index < size
     */
    public Node findLastIndexNode(int index) {
        int size = this.length();

        // 如果链表长度为0
        if (size == 0) {
            return null;
        }

        // index校验
        if(index < 0 || index >= size){
            return null;
        }

        Node temp = head;

        //  head -> 1 -> 2 -> 3
        //  index   2    1    0

        // size index size-index
        //  3     0      3
        //  3     1      2
        //  3     2      1
        //  3     3      0  (null)

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

3、单链表反转

思路: (1) 定义一个头节点 (2) 从头到尾遍历原来的链表,每遍历一个节点就将其取出,并放在新链表最前端 (3) 原来的链表的 head.next = reverseHead.next

java
class SingleLinkedList {
    /**
     * 反转链表
     * head->1->2->3
     *
     * reverseHead
     * reverseHead->1
     * reverseHead->2->1
     * reverseHead->3->2->1
     */
    public void reverse(){

        // 如果链表长度为0或1则直接返回
        if(head.next == null || head.next.next == null){
            return;
        }

        // 定义临时节点
        Node reverseHead = new Node(0, null);
        Node current = head.next;
        Node next;

        while (current != null){
            // 保存下一个节点
            next = current.next;

            // 修改指向
            current.next  = reverseHead.next;
            reverseHead.next = current;

            // 后移
            current = next;
        }

        // 改变头接点指向
        head = reverseHead;
    }
}

4、从尾到头打印单链表

思路 (1)方式一:反向遍历,先将单链表进行反转,然后再遍历,不过会破坏数据结构,不建议 (2)方式二:利用栈的数据结构,将各个节点压入栈中,利用栈的【先入后出】特点

栈的基本使用

java
package com.demo.stack;

import java.util.Stack;

public class StackDemo {
    public static void main(String[] args) {
        // 栈的基本使用
        Stack<String> stack = new Stack<>();
        stack.add("Tom");
        stack.add("Jack");
        stack.add("Steve");

        while (stack.size() > 0) {
            System.out.println(stack.pop());
            //    Steve Jack Tom
        }
    }
}

单链表逆序实现

java
class SingleLinkedList {
    /**
     * 逆序打印
     */
    public void reverseList() {
        // 如果是空链表就直接返回
        if (head.next == null) {
            return;
        }

        Node current = head.next;
        Stack<Node> stack = new Stack<>();

        // 将节点压入栈中
        while (current != null){
            stack.push(current);
            current = current.next;
        }

        // 从栈中取出数据,先入后出,实现逆序输出
        while (stack.size() > 0){
            System.out.println(stack.pop());
        }
    }
}

5、合并两个有序链表,合并之后依然有序

java
class SingleLinkedList {
  /**
     * 合并两个有序链表,合并之后依然有序
     * 
     * head->1->2->3
     * otherHead->2->4->6
     * 
     * newHead->1
     */
    public void concat(SingleLinkedList other) {
        if (other == null) {
            return;
        }

        // 新的头节点, 借助辅助链表
        Node newHead = new Node(0, null);
        Node newCurrent = newHead;

        Node current = head.next;
        Node otherCurrent = other.getHead().next;

        while (true) {
            // 4种组合
            // current == null  && otherCurrent == null break
            // current == null  && otherCurrent != null move left
            // current != null  && otherCurrent == null more right
            // current != null  && otherCurrent != null compare

            // 结束循环
            if (current == null && otherCurrent == null) {
                break;
            }
            // 其中一个是null, 另一个就直接后移追加
            else if (current == null) {
                // 保存下一个节点信息,并将当前节点下一个节点指向空
                newCurrent.next = otherCurrent;
                otherCurrent = otherCurrent.next;
            }
            // 另一个是null
            else if (otherCurrent == null) {
                newCurrent.next = current;
                current = current.next;
            }
            // 两个都不是null则比较大小
            else {
                if (current.id < otherCurrent.id) {
                    newCurrent.next = current;
                    current = current.next;
                } else {
                    newCurrent.next = otherCurrent;
                    otherCurrent = otherCurrent.next;
                }
            }
            // 新链表指针后移
            newCurrent = newCurrent.next;
        }

        // 修改指针指向
        head = newHead;
    }
}

双向链表

单向链表,查找方向只能是一个方向 双向链表,可以向前或者向后查找

单向链表,不能自我删除,需要靠辅助节点 双向链表,可以自我删除

增删改查思路:

遍历:可以向前,也可以向后
添加:默认添加到链表最后
temp.next = newNode
newNode.pre = temp
修改:~
删除:双向量表可以自我删除
temp.pre.next = temp.next
temp.next.pre = temp.pre

代码实现

java
package com.demo.linkedlist;

public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        DoubleLinkedList list = new DoubleLinkedList();
        DoubleNode node1 = new DoubleNode(1, "刘备");
        DoubleNode node2 = new DoubleNode(2, "曹操");
        DoubleNode node3 = new DoubleNode(3, "孙权");

        list.add(node1);
        list.add(node2);
        list.add(node3);

        list.delete(node3);

        node2.data = "司马懿";
        list.update(node2);

        list.list();

    }
}

class DoubleLinkedList {
    // 头节点
    private DoubleNode head = new DoubleNode(0, null);

    //添加节点
    public void add(DoubleNode node) {
        DoubleNode temp = head;

        while (temp.next != null) {
            temp = temp.next;
        }

        temp.next = node;
        node.pre = temp;
    }

    // 删除节点
    public void delete(DoubleNode node) {
        DoubleNode temp = head.next;

        while (temp != null) {
            if (temp.id == node.id) {
                temp.pre.next = temp.next;
                if (temp.next != null) {
                    temp.next.pre = temp.pre;
                }
                break;
            }
            temp = temp.next;
        }

    }

    // 查看链表
    public void list() {
        DoubleNode temp = head.next;
        while (temp != null){
            System.out.println(temp);
            temp = temp.next;
        }
    }

    // 更新节点
    public void update(DoubleNode node) {
        DoubleNode temp = head.next;

        while (temp != null) {
            if (temp.id == node.id) {
                temp.data = node.data;
            }
            temp = temp.next;
        }
    }
}

class DoubleNode {
    public int id;
    public String data;
    public DoubleNode pre;
    public DoubleNode next;

    public DoubleNode(int id, String data) {
        this.id = id;
        this.data = data;
    }

    @Override
    public String toString() {
        return "DoubleNode{" +
                "id=" + id +
                ", data='" + data + '\'' +
                '}';
    }
}

单向环形链表

1、约瑟夫Josepfu问题 设编号为1、2、...n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数, 数到m的那个人出列,他的下一位又从1开始报数,数到m的那个人又出列,以此类推, 直到所有人出列为止,由此产生一个出队编号的序列。

2、构建单向环形链表思路 先创建第一个节点,first指向该节点 创建新节点加入到环形链表

3、遍历环形链表 先让辅助指针指向first节点 然后while循环,结束标志current.next == first

java
package com.demo.linkedlist;

public class JosepfuLinkedListDemo {
    public static void main(String[] args) {
        JosepfuLinkedList list = new JosepfuLinkedList();
        list.addBoys(5);
        list.showList();
        // 5个小孩,从第1个人开始,数2个数
        System.out.println("===出圈===");
        list.boyOut(1, 2);
        // 2->4->1->5->3

    }
}

/**
 * 约瑟夫问题环形链表
 */
class JosepfuLinkedList {
    private BoyNode first; // 头节点,指向第一个节点

    // 新建环形链表,编号从1开始
    public void addBoys(int num) {
        // 入参校验
        if(num < 1){
            System.out.println("require: num>0");
            return;
        }

        BoyNode last = null;  // 尾节点,指向最后一个节点,辅助指针

        BoyNode node;  // 新建的节点

        for (int i = 1; i < num + 1; i++) {
            node = new BoyNode(i);

            if (i == 1) {
                first = node;
                last = node;
            } else {
                last.next = node;
                last = node;
            }

            last.next = first;
        }
    }

    // 显示链表
    public void showList() {

        // 必要的入参校验
        if (first == null) {
            return;
        }

        BoyNode temp = first;

        while (true) {
            System.out.println(temp);

            if (temp.next == first) {
                break;
            }

            temp = temp.next;
        }
    }

    /**
     * 出圈顺序
     *
     * @param startNo  开始编号
     * @param countNum 报数
     */
    public void boyOut(int startNo, int countNum) {
        // 入参校验
        if (first == null || startNo < 1 || countNum < 1) {
            return;
        }

        BoyNode last = first; // 辅助节点

        // 将last.next 指向first
        while (last.next != first) {
            last = last.next;
        }

        // 移动到指定的开始位置
        for (int i = 0; i < startNo - 1; i++) {
            first = first.next;
            last = last.next;
        }

        // 报数出圈
        while (true) {
             // 只有一个节点退出
            if(first == last){
                break;
            }

            for (int i = 0; i < countNum - 1; i++) {
                first = first.next;
                last = last.next;
            }

            // 出圈
            System.out.println(first);

            first = first.next;
            last.next = first;
        }

        //  最后一个节点出圈
        System.out.println(first);


    }
}

class BoyNode {
    public int id;
    public BoyNode next;

    public BoyNode(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "BoyNode{" +
                "id=" + id +
                '}';
    }
}