文章目录

  • 一、链表(LinkedList)简介
  • 二、单链表
  • 2.1 应用实例
  • 2.2 Java代码实现
  • 2.3 面试题(新浪、百度、腾讯)
  • 三、双向链表
  • 3.1 思路分析
  • 3.2 Java代码实现
  • 四、单向环形链表
  • 4.1 应用场景
  • 4.2 约瑟夫(Josephu)问题
  • 4.3 Java代码求解Josephu问题



一、链表(LinkedList)简介

链表是有序的列表,它在内存中的存储如下:

java null 链表 java链表详解_算法

  1. 链表是以节点的方式来存储, 是链式存储
  2. 每个节点包含 data 域, next 域:指向下一个节点.
  3. 如图: 发现链表的各个节点不一定是连续存储.
  4. 链表分带头节点的链表和没有头节点的链表, 根据实际的需求来确定

单链表(带头结点) 逻辑结构示意图如下:

java null 链表 java链表详解_数据结构_02

二、单链表

2.1 应用实例

使用带 head 头的单向链表实现 - 水浒英雄排行榜管理完成对英雄人物的增删改查操作

  1. 第一种方法在添加英雄时, 直接添加到链表的尾部
  2. 第二种方式在添加英雄时, 根据排名将英雄揷入到指定位置 (如果有这个排名, 则添加失败, 并给出提示)
  3. 修改节点功能
    思路(1) 先找到该节点, 通过遍历, (2) temp.name = newHeroNode.name ; temp.nickname java null 链表 java链表详解_java_03
  4. 删除节点

2.2 Java代码实现

public class SingleLinkedList {
    public static void main(String[] args) {
        // 进行测试
        // 创建单链表对象
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        // 增加节点
        System.out.println("增加节点:");
        singleLinkedList.add(new HeroNode("wskh"));
        singleLinkedList.add(new HeroNode("lso"));
        singleLinkedList.add(new HeroNode("wrq"));
        singleLinkedList.add(new HeroNode("sxc"));
        singleLinkedList.list();
        // 删除节点
        System.out.println("删除节点:");
        singleLinkedList.remove("wrq");
        singleLinkedList.remove("sxc");
        singleLinkedList.list();
    }

    // 当前节点的英雄
    HeroNode heroNode;
    // 子节点
    SingleLinkedList next;

    // 添加节点
    public void add(HeroNode heroNode){
        if(this.heroNode == null){
            this.heroNode = heroNode;
            next = new SingleLinkedList();
        }else{
            next.add(heroNode);
        }
    }

    // 根据名字删除节点
    public void remove(String name){
        if(heroNode != null && name.equals(heroNode.name)){
            if(next != null){
                heroNode = next.heroNode;
                next = next.next;
            }else{
                heroNode = null;
            }
        }else if(next != null){
            next.remove(name);
        }
    }

    // 输出链表中所有的英雄节点信息
    public void list() {
        if (this.heroNode != null) {
            System.out.println(heroNode.name);
            if (this.next != null) {
                next.list();
            }
        }
    }

    // 英雄节点类
    static class HeroNode {
        String name;

        public HeroNode(String name) {
            this.name = name;
        }
    }

}

输出:

增加节点:
wskh
lso
wrq
sxc
删除节点:
wskh
lso

2.3 面试题(新浪、百度、腾讯)

  • 求单链表中有效节点的个数
  • 查找单链表中的倒数第k个结点
  • 单链表的反转
  • 从尾到头打印单链表
  • 合并两个有序链表

三、双向链表

3.1 思路分析

使用带 head 头的双向链表实现 - 水汻英雄排行榜

管理单向链表的缺点分析:

  1. 单向链表, 查找的方向只能是一个方向, 而双向链表可以向前或者向后查找。
  2. 单向链表不能自我删除, 需要靠辅助节点, 而双向链表, 则可以自我删除, 所以前面我们单链表删除 时节点, 总是找到 temp,temp 是待删除节点的前一个节点(认真体会).
  3. 分析了双向链表如何完成遍历, 添加, 修改和删除的思路

分析 双向链表的遍历, 添加, 修改, 删除的操作思路 java null 链表 java链表详解_数据结构_04

  • 遍历 方和 单链表一样, 只是可以向前, 也可以向后查找
  • 添加(默认添加到双向链表的最后)
    (1) 先找到双向链表的最后这个节点
    (2) temp.next java null 链表 java链表详解_java_03 newHeroNode
    (3) newHeroNode.pre = temp;
  • 修改 思路和 原来的单向链表一样.
  • 删除
    (1) 因为是双向链表, 因此, 我们可以实现自我删除某个节点
    (2) 直接找到要删除的这个节点, 比如 temp
    (3) temp.pre.next = temp.next
    (4) temp.next.pre = temp.pre;

3.2 Java代码实现

public class SingleCircularLinkedList {
    public static void main(String[] args) {
        // 进行测试
        // 创建单链表对象
        SingleCircularLinkedList singleCircularLinkedList = new SingleCircularLinkedList();
        // 增加节点
        System.out.println("增加节点:");
        singleCircularLinkedList.add(singleCircularLinkedList, new HeroNode("wskh"));
        singleCircularLinkedList.add(singleCircularLinkedList, new HeroNode("lso"));
        singleCircularLinkedList.add(singleCircularLinkedList, new HeroNode("wrq"));
        singleCircularLinkedList.add(singleCircularLinkedList, new HeroNode("sxc"));
        singleCircularLinkedList.list();
        // 删除节点
        System.out.println("删除节点:");
        singleCircularLinkedList.remove("wrq");
        singleCircularLinkedList.remove("sxc");
        singleCircularLinkedList.list();
        // 继续删除节点
        System.out.println("继续删除节点:");
        singleCircularLinkedList.remove("wskh");
        singleCircularLinkedList.remove("lso");
        singleCircularLinkedList.list();
    }

    // 当前节点的英雄
    HeroNode heroNode;
    // 子节点
    SingleCircularLinkedList next;

    // 添加节点
    public void add(SingleCircularLinkedList head, HeroNode heroNode) {
        if (this.next == null) {
            this.heroNode = heroNode;
            this.next = head;
        } else if (this.next.heroNode.name.equals(head.heroNode.name)) {
            SingleCircularLinkedList singleCircularLinkedList = new SingleCircularLinkedList();
            singleCircularLinkedList.heroNode = heroNode;
            singleCircularLinkedList.next = head;
            next = singleCircularLinkedList;
        } else {
            next.add(head, heroNode);
        }
    }

    // 根据名字删除节点
    public void remove(String name) {
        if (heroNode != null) {
            if (name.equals(heroNode.name)) {
                if (heroNode.name.equals(next.heroNode.name)) {
                    // 最后一个节点
                    heroNode = null;
                    next = null;
                    return;
                } else if (heroNode.name.equals(next.next.heroNode.name)) {
                    // 还剩两个点
                    heroNode = next.heroNode;
                } else {
                    heroNode = next.heroNode;
                    next = next.next;
                }
            } else {
                next.remove(name);
            }
        }
    }

    // 输出链表中所有的英雄节点信息
    public void list() {
        if (this.heroNode != null) {
            Set<String> nameSet = new HashSet<>();
            Queue<SingleCircularLinkedList> queue = new LinkedList<>();
            queue.add(this);
            while (!queue.isEmpty()) {
                SingleCircularLinkedList poll = queue.poll();
                if (!nameSet.contains(poll.heroNode.name)) {
                    nameSet.add(poll.heroNode.name);
                    System.out.println(poll.heroNode.name);
                    if (poll.next != null) {
                        queue.add(poll.next);
                    }
                }
            }
        }
    }

    // 英雄节点类
    static class HeroNode {
        String name;

        public HeroNode(String name) {
            this.name = name;
        }
    }

}

输出:

增加节点:
wskh
lso
wrq
sxc
删除节点:
wskh
lso
继续删除节点:

四、单向环形链表

4.1 应用场景

Josephu 问题为: 设编号为 java null 链表 java链表详解_算法_06java null 链表 java链表详解_链表_07 个人围坐一圈, 约定编号为 java null 链表 java链表详解_数据结构_08 的人从 1 开始报数, 数 到 java null 链表 java链表详解_算法_09 的那个人出列, 它的下一位又从 1 开始报数, 数到 java null 链表 java链表详解_算法_09 的那个人又出列, 依次类推, 直到所有人出列为止, 由 此产生一个出队编号的序列。
提示: 用一个不带头结点的循环链表来处理 Josephu 问题: 先构成一个有 java null 链表 java链表详解_链表_07 个结点的单循环链表, 然后由 java null 链表 java链表详解_java_12 结 点起从 1 开始计数, 计到 java null 链表 java链表详解_算法_09

java null 链表 java链表详解_链表_14

4.2 约瑟夫(Josephu)问题

java null 链表 java链表详解_算法_15

提示

用一个不带头结点的循环链表来处理 Josephu 问题: 先构成一个有 java null 链表 java链表详解_链表_07 个结点的单循环链表, 然后由 java null 链表 java链表详解_java_12 结点起从 1 开 始计数, 计到 java null 链表 java链表详解_算法_09

java null 链表 java链表详解_数据结构_19

4.3 Java代码求解Josephu问题

public class Josephu {

    public static void main(String[] args) {
        solveJosephu(5, 1, 2);
    }

    /**
     * @param n 人数
     * @param k 从第k个人开始报数
     * @param m 数几下删除一次
     * @Description
     */
    public static void solveJosephu(int n, int k, int m) {
        // 根据人数n初始化单向环形链表
        SingleCircularLinkedList singleCircularLinkedList = new SingleCircularLinkedList();
        for (int i = 1; i <= n; i++) {
            singleCircularLinkedList.add(singleCircularLinkedList, new People(i));
        }
        // 开始约瑟夫问题的求解
        singleCircularLinkedList.josephuRemove(1, k, m);
    }

    // 单向环形链表
    static class SingleCircularLinkedList {

        // 当前节点的英雄
        People people;
        // 子节点
        SingleCircularLinkedList next;

        // 添加节点
        public void add(SingleCircularLinkedList head, People people) {
            if (this.next == null) {
                this.people = people;
                this.next = this;
            } else if (this.next.people.id == head.people.id) {
                SingleCircularLinkedList singleCircularLinkedList = new SingleCircularLinkedList();
                singleCircularLinkedList.people = people;
                singleCircularLinkedList.next = head;
                next = singleCircularLinkedList;
            } else {
                next.add(head, people);
            }
        }

        // 根据名字删除节点
        public void josephuRemove(int curCnt, int k, int m) {
            if (people != null) {
                if ((k > 0 && curCnt == k) || (k < 0 && curCnt == m)) {
                    if (k > 0) {
                        k = -1;
                        this.josephuRemove(1, k, m);
                    } else {
                        System.out.print(people.id);
                        if (people.id == next.people.id) {
                            // 最后一个节点,结束算法
                            System.out.print("\n");
                            people = null;
                            next = null;
                            return;
                        } else if (people.id == next.next.people.id) {
                            // 还剩两个点
                            System.out.print("->");
                            people = next.people;
                        } else {
                            System.out.print("->");
                            people = next.people;
                            next = next.next;
                        }
                        // 非最后一个节点,继续往后进行约瑟夫过程
                        this.josephuRemove(1, k, m);
                    }
                } else {
                    next.josephuRemove(curCnt + 1, k, m);
                }
            }
        }

        // 输出链表中所有的英雄节点信息
        public void list() {
            if (this.people != null) {
                Set<Integer> idSet = new HashSet<>();
                Queue<SingleCircularLinkedList> queue = new LinkedList<>();
                queue.add(this);
                while (!queue.isEmpty()) {
                    SingleCircularLinkedList poll = queue.poll();
                    if (!idSet.contains(poll.people.id)) {
                        idSet.add(poll.people.id);
                        System.out.println(poll.people.id);
                        if (poll.next != null) {
                            queue.add(poll.next);
                        }
                    }
                }
            }
        }
    }

    // 人类
    static class People {
        int id;

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

}

输出:

2->4->1->5->3