环形链表问题

环形链表检测与入环点计算

1.链表中是否存在环形结构

首先这种题目我们要想到两个快慢指针

struct ListNode *fast 和 struct ListNode *slow

两个指针都指向头节点开始往后走

fast一次走两步

slow一次走一步

如果链表中有环那么这两个指针将会相遇

代码如下

bool hasCycle(struct ListNode *head) {
    struct ListNode*quick=head;
    struct ListNode*slow=head;
    while(quick&&quick->next)
    {
        quick=quick->next->next;
        slow=slow->next;
        if(slow==quick)
        {
            return true;
        }
    }
    return false;
}

2.链表中是否存在环形结构并且返回开始入环时第一个节点

首先!!!!!这是一个需要计算的题目

只有通过计算我们才能得出思路!!!!!

让我们看上面那个图像

假设非环形链表长L

环形链表长C

快慢指针fast和slow相遇点是meet

入环节点和meet相距N

我们知道快指针fast走的路程是slow的两倍 假设fast比slow多走了n圈

所以可以得出等式 L+N+n*C=2*(L+N) =>> L+N+(n-1)*C+C=2*(L+N)

解等式两边得 C=L+N 

转换得C-N=L

所以我们可以在fast和slow相遇位置设置一个节点meet

meet和head同时从当前位置移动每次走一步

当meet和head相遇时所在节点为进入环的节点

代码如下

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *fast=head;
    struct ListNode *slow=head;
    while(fast && fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(slow==fast)
        {
            struct ListNode * meet=slow;
            while(meet!=head)
            {
                meet=meet->next;
                head=head->next;
            }
            return head;
        }
    }
    return NULL;
}

### 关于 Java HashMap 环形链表问题及其解决方案 #### JDK7 中 HashMap 链表成环的原因 在 JDK7 的实现中,`HashMap` 使用的是 **头插法** 将新节点插入到链表头部。当多个线程同时操作同一个 `HashMap` 时,可能会发生竞态条件(race condition),从而导致链表形成环状结构[^1]。 具体来说,在多线程环境下,如果一个线程正在执行扩容操作,而另一个线程也在尝试插入新的键值对,则可能导致以下情况: - 头插法使得每次插入的新节点都会成为链表的第一个节点。 - 当多个线程并发修改同一链表时,由于缺乏同步机制,某些节点的 `next` 指针会被错误地设置,最终形成循环引用,即链表成环[^2]。 这种情况下,程序会陷入死循环,尤其是在调用 `size()` 或迭代器遍历 `HashMap` 时。 --- #### JDK8 中的改进与解决方案 JDK8 对 `HashMap` 进行了重要优化,解决了链表成环的问题。以下是其主要改进措施: 1. **尾插法替代头插法** - 在 JDK8 中,`HashMap` 改用了 **尾插法** 插入新节点,即将新节点追加到链表的末尾而不是头部。这样可以有效防止因头插法引发的环形链表问题[^3]。 2. **局部变量保护链表状态** - 扩容过程中,JDK8 引入了两个局部变量 `loHead` 和 `hiHead` 来分别存储旧链表和新链表的部分节点。这些局部变量仅存在于当前方法的作用域内,因此不会被其他线程干扰,进一步提高了线程安全性[^4]。 3. **引入红黑树结构** - 当链表长度超过一定阈值(默认为 8)时,JDK8 会将链表转换为红黑树以提高查找效率。这一改动虽然不直接影响环形链表问题,但在一定程度上减少了链表过长带来的性能开销。 4. **增强的并发控制** - 虽然 `HashMap` 并不是线程安全的数据结构,但 JDK8 的设计更加注重减少竞争条件的影响。例如,通过更精细的操作划分以及合理的锁粒度调整,降低了多线程环境下的冲突概率。 --- #### 示例代码对比 ##### JDK7 实现(存在隐患) ```java // 假设这是 JDK7 下的 put 方法部分逻辑 if (bucket[k] != null) { Node<K, V> newNode = new Node<>(hash, key, value, bucket[k]); bucket[k] = newNode; // 头插法直接覆盖原有节点 } ``` 上述代码展示了头插法的特点:新节点总是替换掉原链表的第一个节点,容易造成指针混乱。 --- ##### JDK8 实现(改进后的尾插法) ```java // 假设这是 JDK8 下的 putVal 方法部分逻辑 Node<K,V> p = tab[i]; if (p == null) tab[i] = newNode; else { binCount = 1; for (Node<K,V> e = p; ; ) { K ek; if (e.hash == hash && ((ek = e.key) == key || (key != null && key.equals(ek)))) { oldVal = e.value; if (!onlyIfAbsent) e.value = value; break; } Node<K,V> pred = e; if ((e = e.next) == null) { // 到达链表末端 pred.next = newNode; // 新节点追加至链表末尾 break; } } } ``` 可以看到,JDK8 的实现确保了新节点始终位于链表的最后一位,避免了头插法可能引发的环形链表风险。 --- ### 结论 综上所述,JDK7 存在链表成环的主要原因是采用了头插法并缺乏足够的线程安全保障;而在 JDK8 中,通过改用尾插法、引入局部变量以及优化扩容流程等方式成功消除了该问题。对于需要高并发支持的应用场景,建议优先考虑使用线程安全的集合类(如 `ConcurrentHashMap`),或者采取外部同步手段来保障数据一致性。 --- ####
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值