前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >单链表反转

单链表反转

作者头像
Ryan-Miao
发布于 2021-03-15 13:57:46
发布于 2021-03-15 13:57:46
46000
代码可运行
举报
文章被收录于专栏:Ryan MiaoRyan Miao
运行总次数:0
代码可运行

数据结构第一节就是链表。链表由多个node节点组成,每个node节点包含数据和一个指针。指针指向下一个节点。

组装链表

经常问单链表的算法,那你首先要定下来链表的结构,而不是直接思考算法。为了方便使用,我们固定一个哨兵作为 头节点。数据节点都在头节点之后。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author Ryan Miao
 */
@Data
static class Node {
    //是否是head节点。 true-YES
    private Boolean head;
    private Integer data;
    private Node next;
}

那么,我们创建的一个节点是这样的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Node head = new Node();
head.setData(-1);
head.setHead(true);

Node node = new Node();
node.setData(123);
node.setHead(false);

所以,我们首先要创建一个数组1 2 3 4 5 6 7 8 9

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private Node toNode(Integer[] arr) {
    Node head = new Node();
    head.setData(-1);
    head.setHead(true);
    Node tail = head;
    for (int i = 0; i < arr.length; i++) {
        Node node = new Node();
        node.setData(arr[i]);
        node.setNext(null);
        node.setHead(false);
        // append to tail
        tail.next = node;
        // set tail to next
        tail = node;
    }
    return head;
}

private Node makeNode(Integer... arr) {
    return toNode(arr);
}

makeNode(1,2,3,4,5,6,7,8,9);

为了方便展示,写一个链表遍历的方法,用来打印链表结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static void printNode(Node head) {
    Node p = head.next;
    while (p != null) {
        System.out.print(p.getData());
        p = p.next;
        if (p != null) {
            System.out.print("->");
        }
    }
    System.out.println();
}

链表插入

插入节点tmp. 先找到要插入的位置,然后构造插入节点tmp。让tmp指向后面的节点。前一个节点指向tmp。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
public void testInsert() {
    Node node = makeNode(3, 4, 5, 6, 7, 8, 9);
    System.out.println("--------origin--------");
    printNode(node);
    // insert 10 between 4 and 5
    Node p = node;
    while (p != null) {
        if (p.getData() == 4) {
            Node tmp = new Node();
            tmp.setData(10);
            tmp.next = p.next;
            p.next = tmp;
            break;
        }
        p = p.next;
    }

    System.out.println("--------inserted--------");
    printNode(node);
}

打印结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
--------origin--------
3->4->5->6->7->8->9
--------inserted--------
3->4->10->5->6->7->8->9

链表删除

链表删除首先要找到要删除的节点,将pre指向next。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
public void deleteNode() {
    Node node = makeNode(3, 4, 5, 6, 7, 8, 9);
    System.out.println("--------origin--------");
    printNode(node);

    //delete 5
    Node head = node;
    Node p = node;
    while (p != null) {
        if (p.getData() == 5) {
            //if the first is 5, skip
            head.next = p.next;
            break;
        }
        Node pre = p;
        p = p.next;
        if (p != null && p.getData().equals(5)) {
            pre.next = p.next;
            break;
        }
    }

    System.out.println("---------deleted---------");
    printNode(head);
}

打印结果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
--------origin--------
3->4->5->6->7->8->9
---------deleted---------
3->4->6->7->8->9

链表反转

链表最常问的算法就是反转了。目前有两个常见的方式,一个是头插入法,新建一个head,遍历原来的head,插入新链表。

一个是就地反转。将链表看成两部分,左边是新链表,右边是旧链表。每次从右边取出一个,插入昨天的头部,最终全部插入左边。实现整体反转。

头插法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private Node headInsert(Node head) {
    if (head.next == null) {
        return head;
    }

    // new node head
    final Node newHead = new Node();
    newHead.setHead(true);
    newHead.setData(head.getData());
    // pointer
    Node p = head;
    while (p.next != null) {
        // 暂存取下的节点
        Node tmp = p.next;
        // 原来的链表指针移动到下一个
        p.next = p.next.next;
        // 取下的节点 指向 新链表的头节点之后
        tmp.next = newHead.next;
        // 新链表指向 插入的节点
        newHead.next = tmp;
    }
    return newHead;
}

打印结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
=========origin==========
3->4->5->6->7->8->9
---------head insert--------
9->8->7->6->5->4->3

就地反转

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private Node inverse(Node head) {
    if (head == null) {
        return null;
    }
    // 左边链表的tail节点
    Node leftTail = head.next;
    if (leftTail == null) {
        return head;
    }
    // 左边链表的head节点
    Node leftHead = head.next;

    // 当前的指针右边原始链表的第一个节点
    Node pCur = leftTail.next;
    if (pCur == null) {
        leftTail.next = head;
        head.next = null;
        return leftTail;
    }

    while (pCur != null) {
        // 左边链表tail指向 右边链表的下个节点
        leftTail.next = pCur.next;
        // 右边链表的当前第一个节点指向昨天链表的head
        pCur.next = leftHead;
        // head指向插入的节点
        head.next = pCur;
        // 右边链表指针移动下一个节点
        pCur = leftTail.next;
    }
    return head;
}

打印结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
=========origin==========
3->4->5->6->7->8->9
---------inverse--------
9->8->7->6->5->4->3

完整代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.test.algorithm.link;


import lombok.Data;
import org.junit.Test;

/**
 * @author Ryan Miao
 * @see https://github.com/Ryan-Miao/l4Java/blob/master/src/test/java/com/test/algorithm/link/%E5%8D%95%E9%93%BE%E8%A1%A8%E5%8F%8D%E8%BD%AC.java
 */
public class 单链表反转 {

    /**
     * @author Ryan Miao
     */
    @Data
    static class Node {
        private Boolean head;
        private Integer data;
        private Node next;

    }

    @Test
    public void testInsert() {
        Node node = makeNode(3, 4, 5, 6, 7, 8, 9);
        System.out.println("--------origin--------");
        printNode(node);
        // insert 10 between 4 and 5
        Node p = node;
        while (p != null) {
            if (p.getData() == 4) {
                Node tmp = new Node();
                tmp.setData(10);
                tmp.next = p.next;
                p.next = tmp;
                break;
            }
            p = p.next;
        }

        System.out.println("--------inserted--------");
        printNode(node);
    }

    @Test
    public void deleteNode() {
        Node node = makeNode(3, 4, 5, 6, 7, 8, 9);
        System.out.println("--------origin--------");
        printNode(node);

        //delete 5
        Node head = node;
        Node p = node;
        while (p != null) {
            if (p.getData() == 5) {
                //if the first is 5, skip
                head.next = p.next;
                break;
            }
            Node pre = p;
            p = p.next;
            if (p != null && p.getData().equals(5)) {
                pre.next = p.next;
                break;
            }
        }

        System.out.println("---------deleted---------");
        printNode(head);
    }

    private Node makeNode(Integer... arr) {
        return toNode(arr);
    }

    @Test
    public void testInverse() {
        Integer[] arr = new Integer[]{
                3, 4, 5, 6, 7, 8, 9
        };

        inverse(arr);
        headInsert(arr);
        Integer[] arr2 = new Integer[]{
                1
        };
        headInsert(arr2);
        inverse(arr2);
        Integer[] arr3 = new Integer[]{
                1, 2
        };

        inverse(arr3);
        headInsert(arr3);
    }

    private void headInsert(Integer[] arr) {
        Node head = toNode(arr);

        System.out.println("=========origin==========");
        printNode(head);

        Node inverse = headInsert(head);

        System.out.println("---------head insert--------");
        printNode(inverse);

    }

    private Node headInsert(Node head) {
        if (head.next == null) {
            return head;
        }

        // new node head
        final Node newHead = new Node();
        newHead.setHead(true);
        newHead.setData(head.getData());
        // pointer
        Node p = head;
        while (p.next != null) {
            // 暂存取下的节点
            Node tmp = p.next;
            // 原来的链表指针移动到下一个
            p.next = p.next.next;
            // 取下的节点 指向 新链表的头节点之后
            tmp.next = newHead.next;
            // 新链表指向 插入的节点
            newHead.next = tmp;
        }
        return newHead;
    }

    private void inverse(Integer[] arr) {
        Node head = toNode(arr);

        System.out.println("=========origin==========");
        printNode(head);

        Node inverse = inverse(head);

        System.out.println("---------inverse--------");
        printNode(inverse);
    }

    private Node toNode(Integer[] arr) {
        Node head = new Node();
        head.setData(-1);
        head.setHead(true);
        Node tail = head;
        for (int i = 0; i < arr.length; i++) {
            Node node = new Node();
            node.setData(arr[i]);
            node.setNext(null);
            tail.next = node;
            tail = node;
        }
        return head;
    }

    private Node inverse(Node head) {
        if (head == null) {
            return null;
        }
        // 左边链表的tail节点
        Node leftTail = head.next;
        if (leftTail == null) {
            return head;
        }
        // 左边链表的head节     
        Node leftHead = head.next;

        // 当前的指针右边原始链表的第一个节点
        Node pCur = leftTail.next;
        if (pCur == null) {
            leftTail.next = head;
            head.next = null;
            return leftTail;
        }

        while (pCur != null) {
            // 左边链表tail指向 右边链表的下个节点
            leftTail.next = pCur.next;
            // 右边链表的当前第一个节点指向昨天链表的head
            pCur.next = leftHead;
            // head指向插入的节点
            head.next = pCur;
            // 右边链表指针移动下一个节点
            pCur = leftTail.next;
        }
        return head;
    }

    private static void printNode(Node head) {
        Node p = head.next;
        while (p != null) {
            System.out.print(p.getData());
            p = p.next;
            if (p != null) {
                System.out.print("->");
            }
        }
        System.out.println();
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-03-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Java50个关键字总结
abstract修饰类,这个类就是抽象类,抽象类中可以有非抽象变量和成员变量,也可以有普通方法、构造方法。但是不能实例化,只能被子类继承。 如果子类不是抽象类,则必须重写父类的抽象方法。
用户7886150
2020/12/13
6700
java最全关键字
访问控制类 关键字 说明 private 私有的 ,只有当前类中的成员能访问到 protected 受保护的,只有当前类的成员与继承该类的类才能访问 public 公共的,所有用户都可以直接进行调用 default 默认 类、方法和变量修饰符 关键字 说明 abstract 声明抽象 class 类 extends 继承,扩充 final 最终值,一旦定义了就不可改变 implements 实现接口的关键字 interface 接口名 native 本地,原生方法 new 新创建的对象 static 定义
小雨的分享社区
2022/10/26
1900
Java关键字和相关疑问总结
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
李玺
2021/11/22
5051
Java之Java关键字及其作用
private 关键字是访问控制修饰符,可以应用于类、方法或字段(在类中声明的变量)。 只能在声明 private(内部)类、方法或字段的类中引用这些类、方法或字段。在类的外部或者对于子类而言,它们是不可见的。 所有类成员的默认访问范围都是 package 访问,也就是说,除非存在特定的访问控制修饰符,否则,可以从同一个包中的任何类访问类成员。
全栈程序员站长
2022/06/30
1K0
【java基础】java关键字总结及详解
Java关键字是电脑语言里事先定义的,有特别意义的标识符,有时又叫保留字,还有特别意义的变量。Java的关键字对Java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等,关键字不能用作变量名、方法名、类名、包名和参数。
全栈程序员站长
2022/09/08
4850
Java 中的关键字有哪些及其分类
Java 关键字 下面列出了 Java 关键字。这些保留字不能用于常量、变量、和任何标识符的名称。 类别关键字说明访问控制private私有的protected受保护的public公共的default 默认类、方法和变量修饰符abstract声明抽象class类extends扩充,继承final最终值,不可改变的implements实现(接口)interface接口native本地,原生方法(非 Java 实现)new新,创建static静态strictfp严格,精准synchronized线程,同步tra
海拥
2021/08/23
3960
【收藏篇】Java关键字 及其 更详细介绍
private 关键字是访问控制修饰符,可以应用于类、方法或字段(在类中声明的变量)。 只能在声明 private(内部)类、方法或字段的类中引用这些类、方法或字段。在类的外部或者对于子类而言,它们是不可见的。 所有类成员的默认访问范围都是 package 访问,也就是说,除非存在特定的访问控制修饰符,否则,可以从同一个包中的任何类访问类成员。
框架师
2019/09/19
7440
【收藏篇】Java关键字 及其 更详细介绍
【Java学习笔记之一】java关键字及作用
Java关键字及其作用 一、 总览: 1 访问控制 2 private protected public 3 4 类,方法和变量修饰符 5 abstract class extends final implements interface native new 6 static strictfp synchronized transient volatile 7 8 程序控制 9 break c
Angel_Kitty
2018/04/09
1.1K0
盘点历届 Java 语言的关键字,一定有你不认识的
在 Java 编程语言中,关键字是具有特殊含义的保留字,它们用于表示语言中的特定功能和操作。
Java极客技术
2024/06/25
2340
盘点历届 Java 语言的关键字,一定有你不认识的
Java基础入门篇(二)——Java注释、关键字和标识符
前面几篇文章用Java带大家一起了解了几个游戏小项目,感兴趣的小伙伴可以点击文章观摩下,手把手教你用Java打造一款简单故事书(上篇)、手把手教你用Java打造一款简单故事书(下篇)、手把手教你用Java打造一款简单考试系统(上篇)、手把手教你用Java打造一款简单考试系统(下篇)接下来的几篇文章是关于Java基础的,希望对大家的学习有帮助,欢迎大家在讨论区留言。
Java进阶者
2021/01/22
5530
【愚公系列】2021年12月 Java教学课程 05-关键字
关键字是电脑语言里事先定义的,有特别意义的标识符,有时又叫保留字,还有特别意义的变量。Java的关键字对Java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等,关键字不能用作变量名、方法名、类名、包名和参数。
愚公搬代码
2021/12/28
2740
java编程基础(入门级)(超级完整版)「建议收藏」
【1】进制 A.十进制转化二进制 除以2,求余数,商继续除以2,一直到0为止,从底下往上得到结果。 B.二进制转化十进制 1 | 1 | 0 | 0 2 3 ∣ 2 2 ∣ 2 1 ∣ 2 0 2^3 | 2^2 | 2^1 | 2^0 23∣22∣21∣20 8 + 4 + 0 + 0 = 12 8+4+0+0=12 8+4+0+0=12 【2】 计算机的储存方式 位(bit):0或1 字节(byte):8位1字节,数据储存的最小单位 1 KB=1024 Byte 1 MB=1024 KB 1 GB=1024 MB 1 TB= 1024 GB 1 PB= 1024 TB 1 EB= 1024 PB 1 ZB= 1024 EB 【3】命令提示符 进入文件夹:cd 文件夹1/文件夹2/文件夹3 返回上一级:cd … 回根目录: ls 查看当前目录下文件 清屏:command+k 退出:exit
全栈程序员站长
2022/06/29
1.1K0
java编程基础(入门级)(超级完整版)「建议收藏」
java标识符与关键字_4、Java标识符和关键字
标识符:Java对各种变量,方法和类等要素命名时使用的字符序列称为标识符。(凡是自己可以起名的地方都叫标识符,都遵循标识符的规则)
全栈程序员站长
2022/09/08
3260
什么是java的关键字_java中常见的关键字
在java中常见的关键字有很多,千万不能死记硬背,用一个记一个就行了,下面我举出一些常见的关键字。
全栈程序员站长
2022/07/18
6130
什么是java的关键字_java中常见的关键字
java中“53”个关键字(含2个保留字)
2).定义类、接口、抽象类和实现接口、继承类的关键字、实例化对象(共6个)
全栈程序员站长
2022/09/08
4960
Java 基础语法(1)- 注释、标识符、关键字
背景 要开始磕 Java 了,虽然以前学过用过,但是差不多忘光光了... 现在直接搬狂神的视频素材,不再自己总结,要学的东西太多了... 注释 单行注释 // 多行注释 /* */ 文档
小菠萝测试笔记
2021/07/08
4380
测试人员学Java入门指南
本指南特别适合有Python基础的同学学习Java入门,对于没有任何编程经验的同学可能会存在困难。
dongfanger
2022/05/09
7970
测试人员学Java入门指南
最新Java面试题 每一题都是经典
    1.整型:byte(1个字节)、short(2个字节)、int(4个字节) 、long(8个字节)
陶然同学
2023/02/24
9730
Java中所有的关键字及用法
int 基本数据类型 ,内存空间占8位 取值范围-128~127 int i=10;
全栈程序员站长
2022/09/08
3260
Java中所有的关键字及用法
【JAVA-Day04】Java关键字和示例:深入了解常用关键字的用法
本文深入探讨了Java编程语言中的常用关键字及其示例用法。我们从基本的数据类型声明开始,逐步介绍了控制流、异常处理、多线程、类继承等多个关键字的实际应用。通过详细的示例代码,读者将能够更好地理解这些关键字的功能和用法,为Java编程提供了坚实的基础。无论是新手还是有经验的开发人员,都可以从本文中获得有关Java关键字的重要知识和实用技巧。
默 语
2024/11/20
2100
推荐阅读
相关推荐
Java50个关键字总结
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档