数据结构之链表(java语言实现)

news2024/11/18 8:52:24

链表的底层储存结构:

相对于数组这一需要连续、足够大空间的数据结构,链表只需要利用“指针”将一组零碎的空间(在链表中称之为节点)串联起来,这样就可以避免在创建数组时一次性申请过大的空间二导致有可能创建失败的问题!!!
同时比较两种数据结构我们易发现:

1.数组“擅长”按照下标随机访问的相关操作
2.链表“擅长”插入、删除操作
在这里插入图片描述
在这里插入图片描述

链表的定义和操作:

在链表中为了把每个节点串联起来,每个节点除了存储数据本身外,还需要额外的空间存储下一个节点的地址。我们把记录下一个节点地址的指针称为next指针(后继指针)。
在一个链表中我们称链表的第一个节点为头节点用于存储链表的基地址,称链表的最后一个节点为尾节点,其next指针指向空地址(在Java中空地址用null表示)。
在这里插入图片描述

对于链表的定义Java语言代码实现如下:

public class LinkedList{
	public class Node {
		public int data;	//假设节点储存的数据类型为int
		public Node next;
	}
	private Node head = null;
}

我们易知对于数组的插入和删除都会涉及到数据的搬移所以时间复杂度为O(N),但是由于链表并不是连续的储存空间,所以我们只需要操作操作链表节点的next指针就可以实现插入,删除操作,其相对数组的删除插入操作效率更高为O(1)。其查找,删除,插入的代码实现如下:

//查找
public Node find(int value) {
	Node p = head;
	while (p != null && p.data != value) {
		p = p.next;
	}
	return p;
}

//插入
//其中b节点为前驱节点,x为待插入节点
void insert(Node b, Node x) {
	//待插入的节点在链表头部
	if (b == null) {
		x.next = head;
		head = x;
	} elas {
		x.next = b.next;
		b.next = x;
	}
}

//删除
//a为b的前驱节点
void remove(Node a, Node b) {
	if (a == null) {
		head = head.next;
	} else {
		a.next = a.next.next;	//相当去操作a.next = b.next;
	}
}

但是我们看删除函数remove我们之所以能够达到O(1)的时间复杂度是因为我们已知其前驱节点。而由于链表的空间地址是不连续的所以计算机不能想数组那样通过寻址公式直接在基地址的基础上算出第k个节点的地址,所以对于单链表的删除需要先遍历到其前驱节点在实现删除,时间复杂度为O(N)。

链表的变形结构:

除较简单的单链表之外还有:循环链表、双向链表、循环双向链表。

1.循环链表:即链表的尾节点的next指针指向了链表头构成了一个环;
在这里插入图片描述

2.双向链表:在每个节点都存在一个前驱指针(pre)指向当前节点的上节点。
在这里插入图片描述

3.循环双向链表
在这里插入图片描述

对于双向链表的定义Java代码实现如下:

public class LinkedList{
	public class Node {
		public int data;	//假设节点储存的数据类型为int
		public Node next;
		public Node prev;
	}
	private Node head = null;
}

我们易发现由于多了prev指针双向链表要比单链表占用更多的内存,不过这样就能在O(1)时间复杂度的情况下找到某个节点的前驱节点,也是因为这个特点使得双向链表在某些情况下对节点的删除喝插入比单链表要更加简便!我们对删除做出两种常见的分类:

1.按照指定的值进行删除
2.按照指定的指针指向删除节点

对于第一种情况,不论是单链表还是双向链表我们都需要先从表头遍历比较时间复杂度均为:O(N)

//单链表中删除指定的值
    public void remove(int value) {
        Node q = head;
        Node p = null;  //q的前驱节点
        while (q != null && q.next != null) {
            p = q;
            q = q.next;
        }
        if (q != null) {    //找到值等于val的节点q
            if (p == null) {    //q是头节点
                head = q.next;
            } else {
                p.next = q.next;
            }
        }
    }
    
    //在双向链表中删除值等于value的节点
    public void remove(int value) {
        Node q = head;
        while (q != null && q.data != value) {
            q = q.next;
        }
        if (q != null) {
            if (q.prev == null) {
                head = head.next;
            } else {
                q.prev.next = q.next;
            }
        }
    }

但是针对于第二种情况(删除给定指针指向的节点)对于单链表,尽管我们已经找到要删除的节点,但是单链表中是没有储存前驱节点的所以我们还要从头开始遍历链表直到p.next = q为止,所以其的时间复杂度还是O(N),但是双向链表本身储存了其前驱指针,所以我们就可以省去再一次的遍历找其前驱的操作,所以其时间复杂度为O(1)

//单链表中删除节点q
   void remove() {
        if (q == null) {
            return;
        }
        if (head == q) {
            head = q.next;
            return;
        }
        Node p = head;
        while (p != null && p.next != q) {
            p = p.next;
        }
        if (p.next != null) {
            p.next = q.next;
        }
   }

    //在双向链表中删除值节点q
     void remove(Node q) {
        if (q == null) {
            return;
        }
        if (q.prev == null) {   //q是头节点
            head = q.next;
            return;
        }
        q.prev.next = q.next;
    }

实践操作:

读者可以尝试自己用链表完成如下操作:

1.单链表的翻转(写完可以尝试完成力扣206题)
2.链表中环的检测(写完可以尝试完成力扣141题)
3.两个有序链表的合并(写完可以尝试完成力扣21题)
4.删除链表倒数第k个节点(写完可以尝试完成力扣19题)
5.寻找链表的中间节点(有一个规律)(使用快慢指针同时讨论链表长度为奇数喝偶数时的情况)

部分代码实现:

package DataStructures.linkedlist_;

/**
 * @author: 
 * @data: 
 * @description:
 */
public class SinglyLinkedList {
    /**
     * 1)单链表的插入、删除、查找操作
     * 2)链表中储存的是int类型的数据
     */
    private Node head = null;

    public Node findByValue(int value) {
        Node p = head;
        while (p != null && p.data != value) {
            p = p.next;
        }
        return p;
    }

    public Node findByIndex(int index) {
        Node p = head;
        int pos = 0;
        while (p != null && pos != index) {
            p = p.next;
            pos++;
        }
        return p;
    }

    //无头节点
    //表头部插入
    //这种操作将输入的顺序相反,逆序
    public void insertToHead(int value) {
        Node newNode = new Node(value, null);
        insertToHead(newNode);
    }

    public void insertToHead(Node newNode) {
        if (head == null) {
            head = newNode;
        } else {
            newNode.next = head;
            head = newNode;
        }
    }

    //顺序插入
    //链表的尾部插入
    public void insertTail(int value) {

        Node newNode = new Node(value, null);
        //空链表,可以插入新节点作为head,也可以不操作
        if (head == null) {
            head = newNode;
        } else {
            Node q = head;
            while (q.next != null) {
                q = q.next;
            }
            q.next = newNode;
        }
    }
    public void insertAfter(Node p, int value) {
        Node newNode = new Node(value, null);
        insertAfter(p, value);
    }

    public void insertAfter(Node p, Node newNode) {
        if (p == null) return;

        newNode.next = p.next;
        p.next = newNode;
    }

    public void insertBefore(Node p, int value) {
        Node newNode = new Node(value, null);
        insertAfter(p, newNode);
    }

    public void insertBefore(Node p, Node newNode) {
        if (p == null) return;
        //如果p为头节点。则头插
        if (head == p) {
            insertToHead(newNode);
            return;
        }

        Node q = head;
        while (q != null && q.next != p) {
            q = q.next;
        }

        if (q == null) {
            return;
        }

        newNode.next = p;
        q.next = newNode;
    }

    public void deleteByNode(Node p) {
        if (p == null || head == null) return;

        if (p == head) {//待删除的为头节点
            head = head.next;
            return;
        }
        Node q = head;
        while (q != null && q.next != p) {
            q = q.next;
        }

        if (q == null) {
            return;
        }
        q.next = p.next;
    }

    public void deleteByValue(int value) {
        if (head == null) return;

        Node p = head;
        Node q = null;
        while (p != null && p.data != value) {
            q = p;
            p = p.next;
        }

        if (p == null) {
            return;
        }
        if (q == null) {
            head = head.next;
        } else {
            q.next = q.next.next;
        }
        // 可删除指定重复value的代码
        /*
        if (head != null && head.data == value) {
            head = head.next;
        }

        Node pNode = head;
        while (pNode != null) {
            if (pNode.next.data == data) {
                pNode.next = pNode.next.next;
                continue;
            }
            pNode = pNode.next;
        }
        */
    }

    public void printAll() {
        Node p = head;
        while (p != null) {
            System.out.print(p.data + " ");
            p = p.next;
        }
        System.out.println();
    }

    //判断true or false
    public boolean TFResult(Node left, Node right) {
        Node l = left;
        Node r = right;

        boolean flag = true;
        System.out.println("left_:" + l.data);
        System.out.println("right_:" + r.data);
        while (l != null && r != null) {
            if (l.data == r.data) {
                l = l.next;
                r = r.next;
                continue;
            } else {
                flag = false;
                break;
            }
        }
        System.out.println("什么结果");
        return flag;
        /*
        if (l == null && r == null) {
            System.out.println("什么结果");
            return true;
        } else {
            return false;
        }
        */
    }

    // 判断是否回文

    public boolean palindrome() {
        if (head == null) {
            return false;
        } else {
            System.out.println("开始执行找到中间节点");
            Node p = head;
            Node q = head;
            if (p.next == null) {
                System.out.println("只有一个元素");
                return true;
            }
            while (q.next != null && q.next.next != null) {
                p = p.next;
                q = q.next.next;
            }

            System.out.println("中间节点" + p.data);
            System.out.println("开始执行奇数节点的回文判断");
            Node leftLink = null;
            Node rightLink = null;
            if (q.next == null) {
                //此时p一定为整个链表的中点,且节点数目为奇数
                rightLink = p.next;
                leftLink = inverseLinkList(p).next;
                System.out.println("左边第一个节点" + leftLink.data);
                System.out.println("右边第一个节点" + rightLink.data);
            } else {
                //p q均为中点
                rightLink = p.next;
                leftLink = inverseLinkList(p);
            }
            return TFResult(leftLink, rightLink);
        }
    }

    //带头节点的链表翻转
    public Node inverseLinkList_head(Node p) {
        // Head 为新建的一个头节点
        Node Head = new Node(9999,null);
        // p 为原来整个链表的头节点,现在Head指向 整个链表
        Head.next = p;
        /*
        带头结点的链表翻转等价于
        从第二个元素开始重新头插法建立链表
         */
        Node Cur = p.next;
        p.next = null;
        Node next = null;

        while (Cur != null) {
            next = Cur.next;
            Cur.next = Head.next;
            Head.next = Cur;
            System.out.println("first " + Head.data);

            Cur = next;
        }

        // 返回左半部分的中点之前的那个节点
        // 从此处开始同步向两边比较
        return Head;
    }

    // 无头节点的链表翻转
    public Node inverseLinkList(Node p) {

        Node pre = null;
        Node r = head;
        System.out.println("z----" + r.data);
        Node next = null;
        while (r != p) {
            next = r.next;

            r.next = pre;
            pre = r;
            r = next;
        }
        r.next = pre;
        // 返回左半部分的中点之前的那个节点
        // 从此处开始同步像两边比较
        return  r;
    }

    public static Node CreateNode(int value) {
        return new Node(value, null);
    }



    //Node
    public static class Node {
        private int data;
        private Node next;

        public Node(int data, Node next) {
            this.data = data;
            this.next = next;
        }

        public int getData() {
            return data;
        }
    }

    public static void main(String[] args) {
        SinglyLinkedList link = new SinglyLinkedList();
        System.out.println("hello");
        //int data[] = {1};
        //int data[] = {1,2};
        //int data[] = {1,2,3,1};
        //int data[] = {1,2,5};
        //int data[] = {1,2,2,1};
        // int data[] = {1,2,5,2,1};
        int data[] = {1,2,5,3,1};

        for (int i = 0; i < data.length; i++) {
            //link.insertToHead(data[i]);
            link.insertTail(data[i]);
        }
//        link.printAll();
//        Node p = link.inverseLinkList_head(link.head);
//        while (p != null) {
//            System.out.println("aa" + p.data);
//            p = p.next;
//        }
        System.out.println("打印元始:");
        link.printAll();
        if (link.palindrome()) {
            System.out.println("回文");
        } else {
            System.out.println("不是回文");
        }
    }
}

力扣刷题中的常用操作(单链表):

package DataStructures.linkedlist_;

import java.util.LinkedList;

/**
 * @author: ln
 * @data:
 * @description:
 */
public class Test {
    public static void main(String[] args) {
        // Create a LinkedList
        LinkedList<Integer> list = new LinkedList<>();
        // Add element
        // Time Complexity: O(1)
        list.add(1);
        list.add(2);
        list.add(3);
        // [1,2,3]
        System.out.println(list.toString());

        // Insert element
        // Time Complexity: O(N)
        list.add(2,99);
        // [1,2,99,3]
        System.out.println(list.toString());

        // Access element
        // Time Complexity: O(N)
        int element = list.get(2);
        // 99
        System.out.println(element);

        // Search element
        // Time complexity: O(N)
        int index = list.indexOf(99);
        // 2
        System.out.println(index);

        // Update element
        // Time Complexity: O(N)
        list.set(2,88);
        // [1,2,88,3]
        System.out.println(list.toString());

        // Length
        // Time Complexity: O(1)
        int length = list.size();
        // 4
        System.out.println(length);
    }
}

力扣题目推荐:

对于初学的小伙伴们可以先尝试完成如下两题:(建议初学者可以跟着b站up主–"爱学习的饲养员"学习数据结构的基础和刷题常用操作)

1.力扣203题移除链表元素
2.力扣206题反转链表(重点)

注:本笔记主要来自于对《数据结构与算法之美》–王争,一书的整理笔记,若有侵权问题,请第一时间联系作者!!!

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

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

相关文章

20221228英语学习

今日短文 Words and Phrases to Avoid in a Difficult Conversation Difficult conversations are difficult for a reason, and when you’re anxious or stressed out, it’s easy to say the wrong thing.And it doesn’t matter how prepared you are.Your best laid plan…

UDP协议与TCP协议详解

UDP协议详解 UDP&#xff0c;即User Datagram Protocol&#xff0c;用户数据报协议 UDP协议的特点&#xff1a;无连接&#xff0c;不可靠传输&#xff0c;面向数据报&#xff0c;全双工 无连接&#xff1a;知道对端的IP和端口号就直接进行传输&#xff0c;不需要建立连接&am…

深入讲解Linux中断子系统--Workqueue

说明&#xff1a; Kernel版本&#xff1a;4.14ARM64处理器&#xff0c;Contex-A53&#xff0c;双核使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 概述 Workqueue工作队列是利用内核线程来异步执行工作任务的通用机制&#xff1b;Workqueue工作队列可以用作中…

以前的互联网时代,其实就是一个以互联网技术为主导的年代

事实上&#xff0c;以往&#xff0c;我们所经历的那个互联网玩家频出的年代&#xff0c;其实就是一个以互联网技术为主导的年代。在那样一个年代里&#xff0c;互联网技术几乎是解决一切痛点和难题的万能解药&#xff0c;几乎是破解一切行业痛点和难题的杀手锏。任何一个行业&a…

使用Python读取网易邮箱大师客户端的所有邮件

文章目录1. 前言2. 效果3. 探究过程3.1. 找到本地存储的数据库3.2. 使用Python读取数据库3.2.1. 代码4. 探究结果4.1. 函数4.1.1. 找到特定邮~箱的最新一条邮件4.1.2. 找到特定邮箱的最新一次验证码4.1.3. 通过命令行调用Python代码找到特定邮箱的最新的验证码1. 前言 现在绝大…

中科大FPGAOL使用方法

1.中科大的FPGA在线平台提供了一个非常好用的功能&#xff0c;将bit文件上传到远程FPGA开发板上加以功能验证&#xff0c;而且可以游客的身份访问。 Login - FPGA Onlinehttp://fpgaol.ustc.edu.cn/ 2.系统采用的硬件平台是赛灵思的Nexys4 DDR开发板(xc7a100t-csg324)&#x…

RocketMQ学习(五):分布式事务

一、分布式事务 事务&#xff08;Transaction&#xff09;&#xff0c;一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言&#xff08;如SQL&#xff0c;C或Java&#xff09;…

『Java课设』JavaSwing+MySQL实现医院智慧点餐系统

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位喜欢写作&#xff0c;计科专业大三菜鸟 &#x1f3e1;个人主页&#xff1a;starry陆离 如果文章有帮到你的话记得点赞&#x1f44d;收藏&#x1f497;支持一下哦 『Java课设』JavaSwingMySQL实现医院智慧点餐系统1.功能介…

设计模式:责任链模式的应用场景及源码应用

一、概述 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是将链中每一个节点看作是一个对象&#xff0c;每个节点处理的请求均不同&#xff0c;且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时&#xff0c;会沿着链的路径依次传递给每一个…

AI代码实时生成工具teleportHQ

来源&#xff1a;投稿 作者&#xff1a;ΔU 编辑&#xff1a;学姐 今天给大家分享一款AI代码实时生成工具teleportHQ&#xff0c;teleportHQ本质上是一个低代码开发平台&#xff0c;但是首次将计算机视觉应用到低代码开发上&#xff0c;teleportHQ允许用户通过熟悉的设计工具界…

JSX的基本使用

JSX的基本使用1.JSX简介1.1 JSX是react的核心内容1.2 createElement的问题1.3 createElement的问题1.4 JSX注意点2 使用prettier插件格式化react代码3 JSX中嵌入JavaScript表达式4 条件渲染5 列表渲染6 样式处理1.JSX简介 JSX是JavaScript XML的简写&#xff0c;表示了在Javas…

Linux 下 rpm管理包

一、 .rpm的文件格式 以.rpm格式发布的软件里面封装的都是经过编译过的二进制形式的软件&#xff0c;可以直接安装。.rpm格式的文件又称为rpm软件包&#xff0c;简称rpm包。 二、 rpm文件名的格式 三、 rpm命令的使用与软件的安装 Linux中安装rpm软件包有3种方法&#xff1…

新手小白入门之泛型

一、背景 JAVA推出泛型以前&#xff0c;程序员可以构建一个元素类型为Object的集合&#xff0c;该集合能够存储任意的数据类型对象&#xff0c;而在使用该集合的过程中&#xff0c;需要程序员明确知道存储每个元素的数据类型&#xff0c;否则很容易引发ClassCastException异常…

嵌入式书籍推荐

现在嵌入式软件工程师的数量需求方面是越来越旺盛&#xff0c;但是在人才供给方面却出现了缺口&#xff0c;个大公司对于嵌入式开发工程师职位出现供不应求的局面&#xff0c;正是有很多人看到这了大好的环境&#xff0c;纷纷选择开始学习嵌入式开发&#xff0c;学习的方式也是…

第十六讲:神州交换机访问控制列表的配置

访问控制列表ACL&#xff08;Access Control Lists&#xff09;数据定义工具&#xff0c;基于用户自行定义的数据的参数区分不同的数据流&#xff0c;是在交换机和路由器上经常采用的一种防火墙技术&#xff0c;它可以对经过网络设备的数据包根据一定规则进行过滤。它有以下一些…

CloudFlare 的路由拦截

因为腾讯需要对网站进行校验。 校验的方法是使用一个 tencent18250331897192314951.txt 文件&#xff0c;在这个文件中放入腾讯指定的内容。 我们使用的是 Discourse 这个社区系统&#xff0c;这个社区系统对这种问题的响应比较头痛。 解决方案 解决方案就是从域名服务商哪…

NX 系统环境 python3.6 部署 PPOCR 报错记录

NX 系统环境 python3.6 部署 PPOCR 报错记录 前言&#xff08;这环境&#xff0c;就硬配&#xff09; 问&#xff1a;为什么要用系统环境&#xff0c;不用 conda&#xff1f;答&#xff1a;因为 conda 的 ARM 端 python 最低只支持 3.7&#xff0c;而 paddlepaddle 提供的 Je…

c#入门-系统特性

特性 特性可以给成员添加元数据。这有两个作用&#xff1a; 这是一个元数据&#xff0c;可以利用反射获取到如果编译器认识这个特性&#xff0c;那么可以与特性进行交互。 第一点涉及到反射的内容&#xff0c;先略过。 而第二点要求的编译器认实这个特性&#xff0c;就仅限于…

在Linux上安装和使用ZFS

真正的文件系统终极者 ZFS 文件系统的英文名称为 ZettabyteFileSystem&#xff0c;也叫动态文件系统&#xff0c;是第一个 128 位文件系统。最初是由 Sun 公司为 Solaris10 操作系统开发的文件系统。作为 OpenSolaris 开源计划的一部分&#xff0c;ZFS 于 2005 年 11 月发布&a…

《MySQL 8从零开始学(视频教学版)》简介

#好书推荐##好书奇遇季#《MySQL 8从零开始学&#xff08;视频教学版&#xff09;》&#xff0c;定价89元&#xff0c;京东当当天猫都有发售。本书面向MySQL 8数据库初学者&#xff0c;是MySQL数据库畅销入门书。 配套资源 本书配套400个实例和14个综合案例的源码、PPT课件、近2…