一起学算法(链表篇)

news2024/9/20 6:33:57

1.链表的概念

       对于顺序存储的结构最大的缺点就是插入和排序的时候需要移动大量的元素,所以链表的出生由此而来

先上代码

// 链表
public class LinkedList<T extends Comparable> {
    // 结点类
    class Node {
        T ele; // 当前结点上的元素内容
        Node next; // 指向下个结点的索引

        public Node(T ele) {
            this.ele = ele;
        }

        @Override
        public String toString() {
            return ele.toString();
        }
    }

    // 头结点
    private Node head;

    // 链表中实际存放结点的个数
    private int size;

    // 判断链表是否为空
    public boolean isEmpty() {
        return head == null;
    }

    // 获取链表中结点的个数
    public int getSize() {
        return this.size;
    }

    public void addHead(T ele) {
        this.add(0, ele);
    }

    public void addTail(T ele) {
        this.add(this.size, ele);
    }

    // 在指定位置添加结点, 关键点: 找到待插入位置的前一个结点
    public void add(int index, T ele) {
        if (index < 0 || index > this.size) {
            throw new IllegalArgumentException("index is invalid!");
        }
        Node node = new Node(ele);

        // 给链表增加一个虚拟头结点, 解决在链表的头部添加时的特殊处理
        Node dummyHead = new Node(null);
        dummyHead.next = head;
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }
        node.next = prev.next;
        prev.next = node;
        // 更新头结点
        head = dummyHead.next;
        this.size++;
    }
      //根据索引找对应的元素
    public T get(int index){
        if(index<0||index>=this.size){
            return null;
        }
        Node cur=head;
        for (int i = 0; i < index; i++) {
            cur=cur.next;
        }
        return cur.ele;
    }
//获取头节点的元素
    public T getFirst(){
       return get(0);
    }
    //获取尾结点的元素
    public T getLast(){
        return get(this.size-1);
    }
   //判断是否包含该元素
    public boolean contain(T ele){
        Node cur=head;
        for (int i = 0; i < this.size; i++) {
           if(cur.ele.compareTo(ele)==0){
               return true;
           }
             cur=cur.next;
        }
        return false;
    }
    //将链表中的某个元素进行替换
    public void set(T ele,int index) {
        if(index<0||index>=this.size){
        }
        Node cur=head;
        for (int i = 0; i < index; i++) {
            cur=cur.next;
        }
        cur.ele=ele;
    }
    //将链表中的元素进行删除
    public void delete(int index) {
        if (index < 0 || index >= this.size) {

        }
        //无虚拟头结点
//        if (index == 0) {
//            Node delnode = head;
//            head = delnode.next;
//            delnode.next = null;
//            this.size--;
//        } else {
//            Node pre = head;
//            Node cur = pre.next;
//            for (int i = 1; i < index; i++) {
//                pre = pre.next;
//                cur=cur.next;
//            }
//            Node delnode =cur;
//            pre.next = delnode.next;
//            delnode.next = null;
//            this.size--;
//            cur = cur.next;

            //有虚拟头节点
        Node dummyNode=new Node(null);
        dummyNode.next=head;
        Node pre=dummyNode;
        Node cur=pre.next;
        for (int i = 0; i < index; i++) {
            pre=pre.next;
            cur=cur.next;
        }
        Node delnode =cur;
            pre.next = delnode.next;
            delnode.next = null;
            this.size--;
            cur = cur.next;
        }

    @Override
    public String toString() {
        //IO流
        StringBuilder sb = new StringBuilder();
        // 头结点不能动
        Node cur = head;
        while (cur != null) {
            sb.append(cur + "-->");
            cur = cur.next;
        }
        return sb.toString();
    }
}

1.链表的定义

      链表是由一个个结点组成的,每个结点之间通过链接关系串联起来的,每个结点都有一个前驱、一个后继,最后一个结点的后继结点为空节点

       由链接关系A->B组织起来的两个结点,B称为A的后继结点,A被称为B的前驱结点,链表分为单向链表、双向链表、循环链表等

2.链表结点的定义

       由于该类继承了Comparable,所以该类中的元素具有了比较性,ele代表的是数据域,可以是任意的类型,由编码的人自行指定,next代表指针域,指向后继结点的地址

 

3.虚拟头结点

       为了方便对链表的头结点执行操作,往往会建立一个虚拟头结点,这个结点上也不存储数据,也就是ele字段永远为空,或者是为一个特殊的标识

ListNode dummyNode=new ListNode(head,-1);//后继结点为头结点,默认值为-1

2.链表的遍历

1.遍历的含义

遍历就是从链表头结点开始,对所有的结点一次访问的过程

2.动画演示

       虚拟头节点head用-1进行标识,tmp代表当前遍历到的结点,其中tmp后的数字代表链表结点的索引

 3.链表结点的索引

1.索引的含义

链表结点的索引就是给定一个链表头和一个下标index,通过下标获取到链表第index个元素

2.动画演示

3.代码演示

      //根据索引找对应的元素
    public T get(int index){
        if(index<0||index>=this.size){
            return null;
        }
        Node cur=head;
        for (int i = 0; i < index; i++) {
            cur=cur.next;
        }
        return cur.ele;
    }

4.链表结点的插入

1.插入的含义

       给定一个链表头和一个位置index(index>=0)和一个值ele生成一个值为ele的结点,并且将它插入到链表第index之后的位置

2.动画演示

 3.演示代码

    // 在指定位置添加结点, 关键点: 找到待插入位置的前一个结点
    public void add(int index, T ele) {
        if (index < 0 || index > this.size) {
            throw new IllegalArgumentException("index is invalid!");
        }
        Node node = new Node(ele);

        // 给链表增加一个虚拟头结点, 解决在链表的头部添加时的特殊处理
        Node dummyHead = new Node(null);
        dummyHead.next = head;
        Node prev = dummyHead;
        for (int i = 0; i < index; i++) {
            prev = prev.next;
        }
        node.next = prev.next;
        prev.next = node;
        // 更新头结点
        head = dummyHead.next;
        this.size++;
    }

5.链表结点的删除

1.删除的含义

        给定一个链表头和一个位置index(index>=1)将位置index的结点删除,并且返回被删除的结点,由于第一个结点是虚拟头结点,所以我们要从索引为1的位置开始

2.动画演示

 3.代码演示

 //将链表中的元素进行删除
    public void delete(int index) {
        if (index < 0 || index >= this.size) {

        }
        //无虚拟头结点
//        if (index == 0) {
//            Node delnode = head;
//            head = delnode.next;
//            delnode.next = null;
//            this.size--;
//        } else {
//            Node pre = head;
//            Node cur = pre.next;
//            for (int i = 1; i < index; i++) {
//                pre = pre.next;
//                cur=cur.next;
//            }
//            Node delnode =cur;
//            pre.next = delnode.next;
//            delnode.next = null;
//            this.size--;
//            cur = cur.next;

            //有虚拟头节点
        Node dummyNode=new Node(null);
        dummyNode.next=head;
        Node pre=dummyNode;
        Node cur=pre.next;
        for (int i = 0; i < index; i++) {
            pre=pre.next;
            cur=cur.next;
        }
        Node delnode =cur;
            pre.next = delnode.next;
            delnode.next = null;
            this.size--;
            cur = cur.next;
        }

6.链表结点的查找

1.查找的含义

查找的含义就是给定一个值通过遍历链表找到链表值和给定相等的那个结点

2.动画演示

 3.代码演示

      //根据索引找对应的元素
    public T get(int index){
        if(index<0||index>=this.size){
            return null;
        }
        Node cur=head;
        for (int i = 0; i < index; i++) {
            cur=cur.next;
        }
        return cur.ele;
    }

7.链表结点的修改

1.修改的含义

       给定一个链表头一个位置index(index>=1)和一个值ele,将位置index的结点值修改为ele

2.代码演示

    //将链表中的某个元素进行替换
    public void set(T ele,int index) {
        if(index<0||index>=this.size){
        }
        Node cur=head;
        for (int i = 0; i < index; i++) {
            cur=cur.next;
        }
        cur.ele=ele;
    }

leetcode题单:

返回倒数第k个节点

     // 双指针
    public int kthToLast(ListNode head, int k) {
        ListNode fast = head;
        ListNode slow = head;
        // 让快指针先走k步
        for (int i = 0 ; i < k ; i++) {
            fast = fast.next;
        }
        // 再让快指针和慢指针同时移动
        // 当快指针走到链表结尾时
        // 慢指针所指向的节点就是目标节点
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow.val;
    }

删除链表的倒数第N个节点

  //利用双指针
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(head==null||n<0){
           return head;
       }
       ListNode dummyNode=new ListNode(0,head);
       ListNode slow=dummyNode;
       ListNode fast=dummyNode;
        for (int i = 0; i <n+1; i++) {
            fast=fast.next;
        }
        while(fast!=null){
            slow=slow.next;
            fast=fast.next;
        }
        slow.next=slow.next.next;
        return dummyNode.next;
    }

反转链表

   public ListNode reverseList(ListNode head) {
       if(head==null||head.next==null){
           return head;
       }
       ListNode resNode=reverseList(head.next);
       head.next.next=head;
       head.next=null;
       return resNode;
    }


     public ListNode reverseList(ListNode head) {
        //如果头结点为空或者是头结点的后继节点为空的话,直接返回头结点
             if(head==null ||head.next==null){
                 return head;
             }
        //定义指针
        ListNode cur=head;
        ListNode temp=cur.next;
        ListNode pre=null;
        while(cur!=null){
            temp=cur.next;
            cur.next=pre;
            pre=cur;
            cur=temp;
        }
        return pre;
    }

删除链表中的节点

 public void deleteNode(ListNode node) {
        node.val=node.next.val;
        node.next=node.next.next;
    }

两两交换链表中的节点

 //方法一
 public ListNode swapPairs(ListNode head) {
        if(head==null||head.next==null){
            return head;
        }
        ListNode left=head;
        ListNode right=left.next;
        ListNode newNode=swapPairs(right.next);
        left.next=newNode;
        right.next=left;
        return right;
    }
    //方法二
    public ListNode swapPairs(ListNode head) {
       if(head==null||head.next==null){
          return head;
      }
      ListNode r=head;
        for (int i = 0; i <2; i++) {
            if(r==null){
                return head;
            }
            r=r.next;
        }
        ListNode node=reverse(head,r);
        head.next=swapPairs(r);
        return node;
    }
   
    public ListNode reverse(ListNode head,ListNode right){
        ListNode pre=null,curNode=head,next=null;
        while(curNode!=right){
            next=curNode.next;
            curNode.next=pre;
            pre=curNode;
            curNode=next;
        }
        return pre;
}

   //方法三
   public ListNode swapPairs(ListNode head) {
        //对入参进行判断
        if(head==null||head.next==null){
             return head;
           }
           ListNode dummyHead=new ListNode(Integer.MIN_VALUE);
           dummyHead.next=head;
           ListNode pre=dummyHead;
           ListNode cur=pre.next;
           ListNode next=cur.next;
          
      while(cur!=null&&next!=null){
          cur.next=next.next;
          next.next=cur;
          pre.next=next;
          pre=cur;
          cur=pre.next;
          next=cur==null?null:cur.next;
      }
            return dummyHead.next ;
    }
    //方法四
     public ListNode swapPairs(ListNode head) {
        if(head==null||head.next==null){
             return head;
           }
           
        //将头结点的后继点设置为res
        ListNode res = head.next;
        //将res.next和res.next.next进行交换
        head.next = swapPairs(res.next);
        //原先的res与头结点相连接
        res.next = head;
        //返回对应的res
        return res;     
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/822123.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java学习路程之篇四、进阶知识、石头迷阵游戏、绘制界面、打乱石头方块、移动业务、游戏判定胜利、统计步数、重新游戏

文章目录 1、绘制界面2、打乱石头方块3、移动业务4、游戏判定胜利5、统计步数6、重新游戏7、完整代码 1、绘制界面 2、打乱石头方块 3、移动业务 4、游戏判定胜利 5、统计步数 6、重新游戏 7、完整代码 java之石头迷阵单击游戏、继承、接口、窗体、事件、组件、按钮、图片

【Spring】Spring 中事务的实现

目录 1.编程式事务&#xff08;手动编写代码&#xff09;2.声明式事务&#xff08;利用注解&#xff09;2.1 Transactional作用范围2.2 Transactional参数说明2.3 Transactional工作原理 3.Spring 中设置事务隔离级别3.1 事务四大特性ACID3.2 事务的隔离级别3.2 Spring中设置事…

(13) Qt事件系统(two)

目录 事件分发函数 无边框窗口拖动 自定义事件 发送事件的函数 自定义事件 系统定义的事件号 自定义事件号 自定义事件类 发送和处理事件 sendEvent与postEvent的区别 栈区对象 堆区对象 事件传播机制 事件传播的过程 事件传播到父组件 鼠标单击事件与按钮单击信…

【STM32零基础入门教程03】GPIO输入输出之GPIO框图分析

本章节主要讲解点亮LED的基本原理&#xff0c;以及GPIO框图的讲解。 如何点亮LED&#xff08;输出&#xff09; 首先我们查看原理图&#xff0c;观察电路图中LED的连接情况&#xff0c;如下图可以看出我们的板子中LED一端通过限流电阻连接的PB0另一端连接的是高电平VCC&#xf…

30. 利用linprog 解决 生产决策问题(matlab程序)

1.简述 线线规划的几个基本性质&#xff1a;【文献[1]第46页】 (1)线性规划问题的可行域如果非空&#xff0c;则是一个凸集-凸多面体&#xff1b; (2)如果线性规划问题有最优解&#xff0c;那么最优解可在可行域的顶点中确定&#xff1b; (3)如果可行域有界&#xff0c;且可行域…

【数据中台】DataX源码进行二开插件

参考官方 使用的离线数据同步工具/平台&#xff0c;实现不同数据库等各种异构数据源之间高效的数据同步功能 工具部署 https://github.com/alibaba/DataX/blob/master/userGuid.md 拉取下来的代码&#xff0c;pom.xml里面注释 <!--<module>tsdbreader</module&g…

大整数截取解决方法(java代码)

大整数截取解决方法&#xff08;java代码&#xff09; 描述输入描述输出描述输入示例输出示例前置知识&#xff1a;代码 解题思路来自这个博客&#xff1a;简单^不简单 https://blog.csdn.net/younger_china/article/details/126376374 描述 花花有一个很珍贵的数字串&#xf…

P4053 [JSOI2007] 建筑抢修(贪心)(内附封面)

[JSOI2007] 建筑抢修 题目描述 小刚在玩 JSOI 提供的一个称之为“建筑抢修”的电脑游戏&#xff1a;经过了一场激烈的战斗&#xff0c;T 部落消灭了所有 Z 部落的入侵者。但是 T 部落的基地里已经有 N N N 个建筑设施受到了严重的损伤&#xff0c;如果不尽快修复的话&#x…

python项目开发案例集锦,python开发程序流程

大家好&#xff0c;给大家分享一下python项目开发案例集锦 源码&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 今天任务 1.创建Python项目为pythontest1以及test1.py文件 2.修改字号 3.输入九九乘法表程序&#xff0c;编译调试执行 4.配置…

Python selenium对应的浏览器chromedriver版本不一致

1、chrome和chromedriver版本不一致导致的&#xff0c;我们只需要升级下chromedriver的版本即可 浏览器版本查看 //打开google浏览器直接访问&#xff0c;查看浏览器版本 chrome://version/ 查看chromedriver的版本 //查看驱动版本 chromedriver chromedriver下载 可看到浏…

基于 Debian GNU/Linux 12 “书虫 “的Neptune 8.0 “Juna “来了

导读Neptune Linux 发行版背后的团队发布了 Neptune 8.0&#xff0c;作为这个基于 Debian 的 GNU/Linux 发行版的重大更新&#xff0c;它围绕最新的 KDE Plasma 桌面环境构建。 Neptune 8.0 被命名为 “Juna”&#xff0c;是在Neptune 7.5 发布 11 个月后发布的&#xff0c;也是…

2.1 密码学基础

数据参考&#xff1a;CISP官方 目录 密码学基本概念对称密码算法非对称密码算法哈希函数与数字签名公钥基础设施 一、密码学基本概念 1、密码学形成与发展 发展历程 古典密码学 (1949年之前) 主要特点&#xff1a;数据的安全基于算法的保密 近代密码学 (1949~1975年…

第4章 案例研究:JavaScript图片库

案例 html部分 <h1 id"title">图片1</h1> <ul><li><!-- onclick绑定点击事件&#xff0c;this为触发dom&#xff0c;return false阻止默认行为 --><a onclick"show_img(this); return false" title"图片1" h…

数字信号处理中的基本运算——乘法运算

一、二进制乘法原理 二进制乘法可分为&#xff1a;无符号乘法和有符号乘法 整个相乘过程可分解为一系列的移位、相加操作。 有符号数乘法可分为&#xff1a;&#xff08;1&#xff09;正数*正数&#xff1b;&#xff08;2&#xff09;正数*负数&#xff1b;&#xff08;3&…

申请软件著作权都有什么好处?

随着社会的发展&#xff0c;知识产权保护意识对于公司而言尤为重要&#xff0c;对自己的权利进行最大限度的保护&#xff0c;以防止被别有用心的人侵权。那么&#xff0c;申请软著的好处到底是什么?软著有什么用呢? 无形资产软著是一种无形的知识产权&#xff0c;是开发者智慧…

(常压)室温超导体:The First Room-Temperature Ambient-Pressure Superconductor

2023年7月23日&#xff0c;一支韩国的研究团队声称他们已经成功研制出了一种在室温和常压下的超导体&#xff0c;名为LK-99。这一发现在科学界引起了广泛的关注和讨论。 然而&#xff0c;这项研究的结果也引起了一些科学家的怀疑。有些人对数据的真实性表示了疑虑&#xff0c;认…

【UEC++学习】UE网络 - Replication、RPC

1. UE网络架构 &#xff08;1&#xff09;UE的网络架构是SC&#xff08;Server - Client&#xff09;的模式&#xff0c;这种模式的优势&#xff1a;这种模式让所有客户端都在服务器端进行安全验证&#xff0c;这样可以有效的防止客户端上的作弊问题。 &#xff08;2&#xff…

【编程范式】聊聊什么是数据类型和范式的本质

什么是编程范式 范式其实就是做事的方式&#xff0c;编程范式可以理解为如何编程&#xff0c;按照什么样的模式或者风格进行编程。 编程范式包含哪些 泛型编程函数式编程面向对象编程编程本质和逻辑编程 虽然有不同的编程范式&#xff0c;但是对于目的来说都是为了解决同一…

关于vs下多态虚表中存储的地址和实际成员函数地址不一样的原因

以如下代码为例&#xff1a; class Base1 { public: virtual void func1() { cout << "Base1::func1" << endl; } virtual void func2() { cout << "Base1::func2" << endl; } private: int b1; }; class Base2 { public: virtual…

BES 平台 SDK之LED的配置

本文章是基于BES2700 芯片&#xff0c;其他BESxxx 芯片可做参考&#xff0c;如有不当之处&#xff0c;欢迎评论区留言指出。仅供参考学习用&#xff01; BES 平台 SDK之代码架构讲解二_谢文浩的博客-CSDN博客 关于SDK 系统框架简介可参考上一篇文章。链接如上所示&#xff01…