JAVA-数据结构与算法-单链表和单向环形链表

259 阅读4分钟

写在前面

单链表

  • 有序的链表,一个节点包括数据域(data)包含下一个数组的地址域(next)
  • 各个节点不一定是连续存放/存储的
  • 分为带头节点的列表/没有头节点的链表
  • 单向链表查找的方向只有一个;不能实现自我删除,需要辅助节点

单链表实现

  • 节点,next指向下一个节点
class Node {
    public int id;
    public String name;
    public Node next;
    public Node(int id, String name) {
        this.id = id;
        this.name = name;
    }
}
  • 通过链表对象实现,管理node
class SingleLinkedList {
    //初始化头节点,头节点不能动,不存放数据
    private  Node head = new Node(0,"");
    //增删改查方法....
}
  • 添加,不考虑编号顺序,找到当前链表的最后节点,将最后这个节点的next指向新节点,通过temp.next == null来判断,是否为最后一个节点
public void add(Node node) {
    //头节点不能动,通过辅助节点进行遍历
    Node temp = head;
    while (true) {
        //最后一个节点的next指向空
        if (temp.next == null) {
            break;
        }
        //如果没有找到最后,就将temp后移
        temp = temp.next;
    }
    //当退出while循环,temp指向链表的最后
    temp.next = node;
}
  • 按照顺序添加,断开要插入位置的前一个节点与后一个节点的连接,并将后一个节点连接上要插入的节点;node.next = temp.next;;再连接前一个节点与要插入的节点temp.next = node;
public void addByOrder(Node node) {
    //通过辅助变量找到插入的位置,位于添加位置的前一个节点
    Node temp = head;
    boolean flag = false; //添加的编号是否已经存在,默认为false
    while (true) {
        //说明temp已经在最后了
        if (temp.next == null) {
            break;
        }
        //找到位置,这个位置的后一个id要大于要添加的id
        if (temp.next.id > node.id) {
            break;
        } else if (temp.next.id == node.id) {
            //添加的id已经存在,不能添加
            flag = true;
            break;
        }
        //后移
        temp = temp.next;
    }
    if (flag) {
        System.out.println("准备插入的节点已经存在,修改名字");
        temp.next.name = node.name;
    } else {
        //插入到链表中 例如 1 3,2要插入,1是temp,2的下一个节点指向3,1的下一个节点指向2
        //要插入的节点的下一个节点指向temp的下一个节点
        node.next = temp.next;
        //找到的位置要指向要插入的节点
        temp.next = node;
    }
}
  • 修改,根据id修改,id不能改变
public void update(Node node) {
    if (head.next == null) {
        System.out.println("List is empty");
        return;
    }
    Node temp = head;
    boolean flag = false; //是否找到该节点
    while (true) {
        if (temp == null) {
            break;
        }
        if (temp.id == node.id) {
            //找到节点
            flag = true;
            break;
        }
        temp = temp.next;
    }
    if (flag) {
        temp.name = node.name;
    } else {
        System.out.println("node - " + node.id + " does not exist.");
    }
}
  • 删除,head不能动,通过temp找到要删除的节点的前一个节点;待删除的节点的前一个节点temp的下一个节点,指向待删除节点temp.next的下一个节点temp.next.next
public void del(int id) {
    Node temp = head;
    boolean flag = false; //是否找到待删除的节点
    while (true) {
        //已经到了链表的最后且没有找到
        if (temp.next == null) {
            break;
        }
        if (temp.next.id == id) {
            //找到待删除的节点的前一个节点
            flag = true;
            break;
        }
        temp = temp.next;
    }
    if (flag) {
       
        temp.next = temp.next.next;
    } else {
        System.out.println("没有找到id为" + id + "的节点");
    }
}
  • 显示
public void list() {
    //判断链表是否为空
    if (head.next == null) {
        System.out.println("List is empty");
        return;
    }
    //通过辅助变量,进行遍历,从头节点的后一个开始遍历
    Node temp = head.next;
    while (true) {
        //判断是否到链表最后
        if (temp == null) {
            break;
        }
        System.out.println(temp);
        //后移节点
        temp = temp.next;
    }
}

单链表面试题

求单链表中有效节点的个数

//获取单链表节点个数,如果带头节点的链表,不统计头节点
public static int getLength(Node head) {
    if (head.next == null) {
        //空链表
        return 0;
    }
    int length = 0;
    Node cur = head.next;
    while (cur != null) {
        length ++;
        cur = cur.next;
    }
    return length;
}

查找倒数第K个节点

  • 假设长度为3,查找倒数第2个,也就是正数第二个,cur需要从头head.next移动1次,也就是正数移动size-index
public static Node findLastIndexNode(Node head, int index) {
    if (head.next == null) { //空
        return null;
    }
    //得到链表长度
    int size = getLength(head);
    //第二次遍历获取目标
    if (index <= 0 || index > size) {
        return null;
    }
    Node cur = head.next;
    for (int i = 0; i < size - index; i++) {
        cur = cur.next;
    }
    return cur;
}

单链表反转

  • 当前cur.next指向reverseHead.nextreverseHead.next指向cur,使cur插入reverseHeadreverseHead.next之间;从头到尾遍历原链表,就可以把后面的不断提前;next保存原链表中的下一个,等cur操作完成后进行替换
public static void reverseLinkedList(Node head) {
    if (head.next == null || head.next.next == null) {
        return;
    }
    Node cur = head.next;
    //指向当前节点的下一个节点
    Node next = null; //cur的下一个节点
    Node reverseHead = new Node(0,"");
    //遍历原来的列表
    while (cur != null) {
        next = cur.next; //保存当前节点的下一个节点
        cur.next = reverseHead.next; //将cur下一个节点指向新的列表的头部
        reverseHead.next = cur;
        cur = next;
    }
    //将head.next指向reverseHead.next;
    head.next = reverseHead.next;
}

从尾到头打印单链表

  • 利用栈(先进后出)实现逆序打印
public static void reversePrint(Node head) {
    if (head.next == null) {
        return;
    }
    //创建栈
    Stack<Node> nodes = new Stack<>();
    Node cur = head.next;
    while (cur != null) {
        nodes.push(cur);
        cur = cur.next;
    }
    while (nodes.size() > 0) {
        System.out.println(nodes.pop());
    }
}

将两个有序单链表合并

  • 当存在curOne.id > curTwo.id,则将curTwo及其以后序的链表全部移动到cur后,cur移动到curwocurTwo向后移;当存在curOne.id < curTwo.id,同理移动curOne及后面的所有链表。假定curTwo为空,curTwo被循环完毕,则表示curOne后续的元素都比curTwo大,直接接到cur后面加就好
public static Node merge(Node headOne , Node headTwo) {
    if (headOne.next == null) {
        return headTwo;
    }
    if (headTwo.next == null) {
        return headOne;
    }
    Node newHead = new Node(0,"");
    Node curOne = headOne.next;
    Node curTwo = headTwo.next;
    Node cur = newHead;
    while (curOne != null && curTwo != null) {
        if (curOne.id > curTwo.id) {
            cur.next = curTwo;
            cur = curTwo;
            curTwo = curTwo.next;
        } else {
            cur.next = curOne;
            cur = curOne;
            curOne = curOne.next;
        }
    }
    if (curOne == null) {
        cur.next = curTwo;
    } else {
        cur.next = curOne;
    }
    return newHead;
}

单向环形链表(Josephu约瑟夫问题)

  • 问题,设编号为1、2、3....的n个人围坐一圈,约定编号k(1<=k<=n)的人从1开始报数,数到m的那个人出列,m的下一位又开始报数,数到m的人再次出列,以此类推,直到所有人出列为止,由此产生一个出列的序列
  • 例如 n=5,k=1,m=2 从1开始数2下出来一个人,顺序为2 4 1 5 3

构建

  • 节点
class Node {
    private int id;
    private Node next;
    //getter setter 构造 toString...
}
  • 环形链表
class CircleSingleLinkedList {
    //创建first节点,永远指向第一个位置
    private Node first;
    //其他方法
}
  • 添加,第一个特殊处理,要将fistcur赋值,且要自己连自己;最后一个节点的next要指向first
public void addNode(int nums) {
    if (nums < 1) {
        System.out.println("nums is erroe");
        return;
    }
    //辅助变量
    Node cur = null;
    //创建环形链表
    for (int i = 1; i <= nums; i++) {
        //根据编号创建
        Node node = new Node(i);
        if (i == 1) {
            first = node;
            first.setNext(first);
            cur = first;
        } else {
            //让cur连接新node
            cur.setNext(node);
            //让node连回first
            node.setNext(first);
            //cur后移
            cur = node;
        }
    }
}
  • 遍历
public void show() {
    if (first == null) {
        System.out.println("List is empty.");
        return;
    }
    //辅助变量
    Node cur = first;
    while (true) {
        System.out.println(cur);
        //说明遍历完毕
        if (cur.getNext() == first) {
            break;
        }
        //后移
        cur = cur.getNext();
    }

约瑟夫问题

  • 特例分析,当只有两个节点的时候,假如此时只用两个是1,3 first指向1end指向3,数2下,3准备出圈,数好后,first先指向3end先指向1,输出3后,first重新指向1,而此时end只负责绑定first,所以end == first
/**
 * @param startId 第几个开始
 * @param countNum 数几下出圈
 * @param nums 圈里的node个数
 */
public void countNode(int startId, int countNum, int nums) {
    this.addNode(nums);
    if (first == null || startId < 1 || startId > nums) {
        System.out.println("error");
        return;
    }

    //辅助指针,记录最后一个节点,用于出圈后的重新连接
    Node end = first;
    while (true) {
        if (end.getNext() == first) {
            break;
        }
        end = end.getNext();
    }
    //移动first和end到开始的位置,first到开始报数的node,end到前一个
    //例如 startId = 3 需要移动 startId - 1 = 2次
    for (int i = 0; i < startId - 1; i++) {
        first = first.getNext();
        end = end.getNext();
    }
    //报数
    while (true) {
        //圈中只有一个node
        if (end == first) {
            break;
        }
        //自己要报一次
        for (int i = 0; i < countNum - 1; i++) {
            first = first.getNext();
            end = end.getNext();
        }
        //first指向要出圈的node
        System.out.println(first.getId());
        //将end.next连上first.next
        first = first.getNext();
        end.setNext(first);
    }
    System.out.println(first.getId());
}