这篇主要回顾下,单链表和双链表增删查改的实现,链表操作一个基本点就是先连结再拆除
后文用例都是将一个学生作为节点,包含姓名和分数两项属性,进行相关功能练习,完整代码可访问。https://gitee.com/flying-morning/data-structure/tree/master/src/com/datastructure/list

class StudentNode {
    public String name; //姓名
    public int score;      //分数
    public StudentNode next;   //指向后一个节点
    public StudentNode pre; //指向前一个节点,双链表的时候才有
    public StudentNode(String name,int score) {
        this.name = name;
        this.score = score;
        next = null;
        pre = null;
    }

    @Override
    public String toString() {
        return "name: " + this.name + ", score: " + this.score;
    }
}

1 单链表

head 节点:不存放具体数据,作用就是表示单链表头

1. 1 增加节点

在末尾插入节点其实没什么好说的,从头节点一直开始遍历,直到找到最后一个节点,然后将尾节点 next 指针指向新节点。
尾节点:temp.next == null

    public void add(StudentNode student) {
        StudentNode temp = head;
        //next == null 就是尾节点
        while (temp.next != null) {
            temp = temp.next;
        }
        temp.next = student;
    }

如果是在中间插入节点,比如说按照分数从高到低的顺序。因为单链表只有指向后一个节点的指针,所以我们需要找到插入位置的前一个节点,这样才能进行链表连接操作。
比如我们要插入的节点 a3 分数高于 a2,所以要插到 a2 之前,那么 a1 这个节点就是我们需要找到的,有如下两步操作:

  • a3.next = a1.next,将新节点 a3 的 next 指向 a2,而 a2 = a1.next
  • a1.next = a3,将 a1 的 next 指向 a3.
    这两步顺序不能反,否则会丢失 a2 节点的访问
    回顾总结之数据结构:3 链表-LMLPHP
	public void addByScore(StudentNode studentNode) {
        StudentNode temp = head;
        while (temp.next != null) {
            //目标节点的分数大于后一个节点
            if(temp.next.score < studentNode.score) {
                break;
            }
            temp = temp.next;
        }
        //节点插入,先将目标节点的next指向temp.next
        //再将 temp.next 指向目标节点
        studentNode.next = temp.next;
        temp.next = studentNode;
    }

1. 2 删除节点

删除节点大概三步:

  • delNode = a1.next,暂存 a3,不暂存的话,后面就找不到这个节点了。
  • a1.next = a1.next.next,让 a1 的 next 指向 a2
  • delNode.next = null,将 a3 的 next 置为 null,完全断开

回顾总结之数据结构:3 链表-LMLPHP

	public void del(String name) {
        StudentNode temp = head;
        while (temp.next != null) {
            if (temp.next.name.equals(name)) {
                break;
            }
            temp = temp.next;
        }
        if (temp.next != null) {
            StudentNode delNode = temp.next;
            temp.next = temp.next.next;
            delNode.next = null;
            System.out.println(name + "已被删除");
        }else {
            System.out.println(name + "不在链表中");
        }
    }

1.3 修改和查找

这两个其实没什么好说的,同增加和删除不同,这里因为不需处理跟前后节点的关系,所以只需要当前节点就行,我们在遍历的时候就让 StudentNode temp = head.next;,不再记录前置节点的信息了。

2 双链表

2.1 增加节点

在末尾增加,和单链表类似,只是多了将新增节点的 pre 指向之前的尾节点的操作。

	public void add(StudentNode studentNode) {
        StudentNode temp = head;
        while (temp.next != null) {
            temp = temp.next;
        }
        temp.next = studentNode;
        studentNode.pre = temp;
    }

按分数插在中间,比单链表要简单,步骤如下

  • a3.next = a2; a2.pre = a2.pre,这一步就处理了好了 a3 节点 pre 和 next 的值
  • a2.pre.next = a3,将 a1 的 next 指向 a3;a2.pre = a3,将 a2 的 pre 指向 a3

回顾总结之数据结构:3 链表-LMLPHP

代码中的 temp 就想当于 a2

	public void addByScore(StudentNode studentNode) {
        StudentNode temp = head.next;
        while (temp != null) {
            // 目标节点的分数大于当前节点
            if(temp.score < studentNode.score) {
                break;
            }
            temp = temp.next;
        }
        //节点插入
        studentNode.next = temp;
        studentNode.pre = temp.pre;
        temp.pre.next = studentNode;
        temp.pre = studentNode;
    }

2.2 删除节点

删除 a3 节点,主要步骤如下:

  • 先连结:a3.pre.next = a3.next; a3.next.pre = a3.pre,借助 a3 的信息将 a1、a2关联
  • 断开a3:a3.next = null; a3.pre = null

回顾总结之数据结构:3 链表-LMLPHP

    public void del(String name) {
        StudentNode temp = head.next;
        while (temp != null) {
            if (temp.name.equals(name)) {
                break;
            }
            temp = temp.next;
        }
        if(temp != null) {
            temp.pre.next = temp.next;
            if (temp.next != null) {
                temp.next.pre = temp.pre;
                temp.next = null;
            }
            temp.pre = null;
            System.out.println(name + "已被删除");
        }else {
            System.out.println(name + "不在链表中");
        }
    }

3 单向环形链表

环形链表的一个特点就是,尾节点的 next 指向第一个节点,形成一个环状的结构。我们定义第一个添加的节点为 first ,且只有一个节点时 first.next = first

这里主要围绕约瑟夫问题展开,每个节点只有 num 这一个属性,表示序号,方便问题求解。

class NumNode {
    int num; //节点序号
    NumNode next;
    NumNode(int num) {
        this.num = num;
        this.next = null;
    }
}

节点的操作在前面应该已经很熟悉了,这里主要讲讲约瑟夫问题怎么解。
对于如下的环形链表,first 指向 a1,
回顾总结之数据结构:3 链表-LMLPHP

06-26 10:16