应用-单向链表-数据结构和算法

1 单向链表反转

  • 需求:原链表数据1->2->3->4,反转之后链表中的数据4->3->2->1。效果图示,在这里插入图片描述

  • 思路:

    1. 非递归:从第一个节点开始遍历链表,记录前一个节点,当前节点,后一个节点;把当前节点的next指针指向前一个节点,指针后移,直到链表结尾,交换头指针和尾指针,整个链表反转完成。
      • 指针后移指当前节点指针赋值给前一个节点指针,后一个指针节点赋值给当前节点,后一个节点指针.next赋值给后一个节点指针
    2. 递归实现:从第一个节点开始,依次递归调用反转每一个节点直到最后一个节点既原尾节点现在的头结点,整个链表反转完成。
  • 思路2递归实现

    • API:把下面2个方法放入我们前面实现的单向链表中

      • public void reverse():反转整个链表
      • public Node reverse(Noce curr):反转当前节点
    • 代码:

      /**
       * 反转单向链表
       */
      public void reverse() {
          if (size == 0 || size == 1) {
              return;
          }
          // 递归反转
          reverseRecursion(first);
          // 交换头尾指针
          Node<E> n = first;
          first = last;
          last = n;
      }
      
      private Node<E> reverseRecursion(Node<E> curr) {
          if (curr.next == null) {
              return curr;
          }
          Node<E> pre = reverseRecursion(curr.next);
          pre.next = curr;
          curr.next = null;
          return curr;
      }
      
    • 测试:

      ===反转前====
      [1, 2, 3, 4]
      ===反转后====
      [4, 3, 2, 1]
      
  • 非递归实现

    • 代码:

      /**
           * 单向链表反转非递归实现
           */
          public void reverse() {
              if (size == 0 || size == 1) {
                  return;
              }
              //
              Node<E> pre = null;
              Node<E> curr = first;
              Node<E> next = first.next;
              while (curr != null) {
                  curr.next = pre;
                  if (next == null) {
                      break;
                  }
                  pre = curr;
                  curr = next;
                  next = next.next;
              }
              // 交换头尾指针
              Node<E> n = first;
              first = last;
              last = n;
          }
      
    • 测试:

      ===反转前====
      [1, 2, 3, 4]
      ===反转后====
      [4, 3, 2, 1]
      

2 快慢指针

快慢指针指的就是定义两个指针,这两个指针的移动速度一块一慢,快慢指针之间的差值可以让我们找到链表中的相应节点。一般情况下,快指针的移动步长为慢指针的两倍。

2.1 中间值问题

利用快慢指针查找链表中间节点。

原理:快慢指针初始位置为链表头,快指针移动步长2倍于慢指针,当快指针移动到链表末尾的时候,慢指针刚好移动到链表中间位置。

链表有7个节点,分别存储字符串aa,bb,cc,dd,ee,ff,gg,蓝色表示慢指针,每次移动一个节点位置;绿色表示快指针,每次移动2个节点位置,当绿色指针移动到链表结尾时,蓝色节点刚好移动到中间节点位置dd处。如图2.1-1所有:在这里插入图片描述

用代码模拟实现,代码2.1-1如下:

public class FastSlowPointerTest {

    public static void main(String[] args) {
        // 创建链表
        Node<String> seven = new Node<>("gg", null);
        Node<String> six = new Node<>("ff", seven);
        Node<String> five = new Node<>("ee", six);
        Node<String> four = new Node<>("dd", five);
        Node<String> three = new Node<>("cc", four);
        Node<String> two = new Node<>("bb", three);
        Node<String> one = new Node<>("aa", two);
        // 获取中间节点的值
        System.out.println(getMid(one));

    }

    /**
     * 获取中间节点元素
     * @param first     头结点
     * @return          中间节点元素值
     */
    public static String getMid(Node<String> first) {
        // 定义快慢指针
        Node<String> fast = first;
        Node<String> slow = first;

        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            if (fast == null) {
                break;
            }
            slow = slow.next;
        }
        return slow.item;
    }

    /**
     * 节点类
     */
    private static class Node<E> {
        E item;
        Node<E> next;

        public Node(E item, Node<E> next) {
            this.item = item;
            this.next = next;
        }
    }
}

2.2 单向链表是否有环

无环链表,前一节点指向后一节点,而后面节点不指向前面节点,如图2.2-1所示:在这里插入图片描述

有环链表,前一节点指向后一节点,且后面节点指向前面节点,如图2.2-2所示:在这里插入图片描述

快慢指针查找是否有环原理:快指针移动快,如有环路,当慢指针进入环路时,快指针已经在环路中,且快指针移动快,一直移动直至快慢指针相遇;如果没有环路,则快指针和慢指针不会相遇。

快慢指针查找环路图示2.2-3:在这里插入图片描述

用代码模拟实现,代码2.2-1如下:

public class LinkListCircuitTest {

    public static void main(String[] args) {
        // 创建链表
        Node<String> seven = new Node<>("gg", null);
        Node<String> six = new Node<>("ff", seven);
        Node<String> five = new Node<>("ee", six);
        Node<String> four = new Node<>("dd", five);
        Node<String> three = new Node<>("cc", four);
        Node<String> two = new Node<>("bb", three);
        Node<String> one = new Node<>("aa", two);
        // 环路
        seven.next = three;
        // 判断链表是否有环路
        System.out.println(isCircuit(one));

    }

    /**
     * 判断链表是否有环路
     * @param first     头结点
     * @return          是否是环路
     */
    public static boolean isCircuit(Node<String> first) {
        // 定义快慢指针
        Node<String> fast = first;
        Node<String> slow = first;

        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            if (fast == null) {
                break;
            }
            slow = slow.next;
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }

    /**
     * 节点类
     */
    private static class Node<E> {
        E item;
        Node<E> next;

        public Node(E item, Node<E> next) {
            this.item = item;
            this.next = next;
        }
    }
}

2.3 有环链表入口

当检测的链表有环后,怎么找到入口呢?当快慢指针相遇时,这是重新设定一个新的指针指向链表的启点,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口。证明涉及数论的知识,本人还没有学,略过。

代码实现如下:

public static Node<String> getEntrance(Node<String> first) {
    // 定义快慢指针,临时指针
    Node<String> fast = first;
    Node<String> slow = first;
    Node<String> temp = null;
    // 遍历链表,先找的环(快慢指针相遇), 临时指针指向链表的启点,且步长与慢指针一样为1,则慢指针与临时指针相遇的地方就是环的入口
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        if (fast == null) {
            break;
        }
        slow = slow.next;
        if (fast == slow) {
            temp = first;
            continue;
        }
        if (temp != null) {
            temp = temp.next;
            if (temp == slow) {
                return slow;
            }
        }
    }
    return null;
}

测试代码同2.2一致

3 后记

​ 如果小伙伴什么问题或者指教,欢迎交流。

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/algorithm

参考:

[1]黑马程序员.黑马程序员Java数据结构与java算法全套教程,数据结构+算法教程全资料发布,包含154张java数据结构图[CP/OL].p56~p60.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值