IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 数据结构(JAVA)第二篇-----链表 -> 正文阅读

[数据结构与算法]数据结构(JAVA)第二篇-----链表

目录

引言

一、单链表

1.定义

2.自定义一个单链表

创建一个MyLinkedList类

addFirst

addLast

addIndex(int index,int data)

obtain

remove

removeAllKey

clear

二、双链表

1.模拟实现

三、总结

引言

①链表也是一种线性表,那我们既然已经有了顺序表这种数据结构了,又发明链表干嘛呢?

首先,我们知道顺序表根本上讲就是一个数组,那么我们回顾一下顺序表的插入或删除,需要先遍历一遍数组,找到插入或删除的位置,然后进行插删操作,之后必须调整这之后所有元素的位置,这样下来时间复杂度为O(n^{2}),效率并不是很可观。

②那么,我们想想导致这一原因的因素是什么呢?

就是因为它是数组,它的内存空间物理上是连续的,所以我们每次增删数据,都必须考虑该位置后的元素的位置是否要做出相应的改变。所以,如果我们可以构造一个物理空间上不连续,但它们都具有某种特定关系的线性表不就可以解决这个效率问题了吗,于是有了链表。

链表的定义:链表是一种物理存储单元上非连续非顺序的存储结构,数据元素的逻辑顺序是通过链表中的引用(也说指针)链接次序实现的。


由于实际上的链表种类较多,根据带头和不带头,单向和双向,循环与非循环分类,可以分成八种,但本文篇幅有限,就介绍不带头单向非循环,不带头双向循环。

一、单链表

1.定义

顾名思义,就是只有一端指向的链表。

不带头结点的单向非循环链表

RT:

?

其中,每一个对象的next都存放的是逻辑上的下一个对象的地址。?

2.自定义一个单链表

创建一个MyLinkedList类

public class MyLinkedList {
    
    static class ListNode {
        public int val;//值

        public ListNode next;//引用链接

        public ListNode(int val) {
            this.val = val;
        }
    }
    public ListNode head;//头结点
}

addFirst

头插

public void addFirst(int data){
            ListNode cur = new ListNode(data);//new 一个需要插入的结点
            cur.next = head;//将新结点的next指向head
            head = cur;//让cur成为头结点
        }

addLast

尾插

如果你一开始有tail结点,那就十分容易:

public void addLast(int data) {
            tail.next = new ListNode(data);
            tail = tail.next;
        }

但没有tail结点也没关系,我们遍历一边即可,只是时间复杂度变为O(n),但也仍然高于顺序表的O(n^{2}

RT:

public void addLast(int data) {
            if(head == null){
                head = new ListNode(data);
                return;
            }
            ListNode cur = head;
            while(cur.next!=null){
                cur = cur.next;
            }
            cur.next = new ListNode(data);
        }

addIndex(int index,int data)

在index下标前插入data

第一时间,依旧是判断index是否合法,那必须要用到size()函数,求出当前链表的长度。

public int size() {
        ListNode cur = this.head;
        int count = 0;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }

RT:

public boolean addIndex(int index, int data) {
        if (index == 0) {
            ListNode insert = new ListNode(data);
            insert.next = head;
            head = insert;
            return true;
        }
        if(index < 0 && index > size()){
            return false;
        }
        ListNode cur = head;
        while(index - 1 > 0){
            cur = cur.next;//找到要插入的前一个结点
            index--;
        }
        ListNode insert = new ListNode(data);
        insert.next = cur.next;
        cur.next = insert;
        return true;
    }

obtain

是否包含

RT:

public boolean contains(int key) {     
        ListNode cur = head;
        while(cur!=null){
            if(cur.val == key){
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

remove

删除

RT:

public void remove(int key) {
        if(head == null){
            return;
        }
        if(head.val == key){
            head = head.next;
        }
        ListNode pre = head;//找到前结点
        while(pre.next != null){
            if(pre.next.val == key){
                break;
            }
            pre = pre.next;
        }
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key){
                pre.next = cur.next;
            }
            cur = cur.next;
        }
    }

removeAllKey

删除所有值为key的结点

由于与leetcode的203题一样,那我们直接做leetcode。

https://leetcode.cn/problems/remove-linked-list-elements/

具体思路:

1.定义两个结点,pre 和 cur,pre作为cur的前一个结点一直跟随cur,当遇到目标结点了,就直接pre.next = cur.next。

2.如此一来,我们把pre初始化为head,cur为head.next。然后将cur!=null作为判断条件,遍历一遍链表即可。

3.值得注意的是,如果把cur初始化为head.next,那么首结点就无法判断了,所以我们在末尾的时候,判断一次首结点是否是目标结点,那么就完成了。

public ListNode removeElements(ListNode head, int val) {
        if(head == null){
            return head;
        }
        ListNode pre = head;
        ListNode cur = head.next;
        while(cur!=null){
            if(cur.val == val){
                pre.next = cur.next;
                cur = cur.next;
            }else {
                pre = cur;
                cur = cur.next;
            }
        }
        if(head.val == val){
            head = head.next;
        }
        return head;
    }
}

clear

清空

public void clear() {
        this.head = null;
    }

解释:这里将head置为空,那么head所引用的对象head.next将因为无人引用而被回收,同样head.next的回收,又导致了head.next.next被回收,这样依次回收,链表就被清空了。

二、双链表

1.模拟实现

双链表的实现与单链表差距不大,主要的变化就是多了一个pre结点。直接上代码:

public class Linkedlist2 {
    static class ListNode {
        int val;
        ListNode pre;
        ListNode next;
        public ListNode(int val) {
            this.val = val;
        }
    }
    public ListNode head;
    public ListNode tail;
    //头插法
    public void addFirst(int data) {
        ListNode cur = new ListNode(data);
        if (this.head == null) {
            this.head = cur;
            this.tail = cur;
        } else {
            this.head.pre = cur;
            cur.next = this.head;
            head = cur;
        }
    }
    //尾插法
    public void addLast(int data) {
        if (head == null) {
            head = new ListNode(data);
            tail = head;
        } else {
            ListNode cur = new ListNode(data);
            tail.next = cur;
            cur.pre = tail;
            tail = cur;
        }
    }
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) {
        //1、判断Index位置的合法性

        //2、判断特殊位置,头插 和 尾插

        //3、找到index位置节点的地址
        if (index < 0 || index > size()) {
            throw new PosOutInedx("INDEX IS ILLEGAL");
        }
        if (index == 0) {
            addFirst(data);
            return;
        }
        if (index == size()) {
            addLast(data);
            return;
        }
        ListNode find = this.head;
        ListNode cur = new ListNode(data);
        while (index > 0) {
            find = find.next;
            index--;
        }
        cur.pre = find.pre;
        cur.next = find;
        find.pre.next = cur;
        find.pre = cur;
    }
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
        }
        return false;
    }
    //删除第一次出现关键字为key的节点(注意链表的tail结点置空情况)
    public void remove(int key) {
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key){
                if(cur == head){
                    head = head.next;
                    if(head==null){
                        tail = null;

                    }else {
                        head.pre = null;
                    }
                }else if(cur == tail){
                    tail = tail.pre;
                    tail.next = null;
                }else{
                    cur.pre.next = cur.next;
                    cur.next.pre = cur.pre;
                }
                return;
            }
            cur = cur.next;
        }
    }
    //删除所有值为key的节点
    public void removeAllKey(int key){
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key){
                if(cur == head){
                    head = head.next;
                    if(head==null){
                        tail = null;
                    }else {
                        head.pre = null;
                    }
                }else if(cur == tail){
                    tail = tail.pre;
                    tail.next = null;
                }else{
                    cur.pre.next = cur.next;
                    cur.next.pre = cur.pre;
                }
            }
            cur = cur.next;
        }
    }
    //得到单链表的长度
    public int size() {
        ListNode cur = head;
        int count = 0;
        while (cur != null) {
            cur = cur.next;
            count++;
        }
        return count;
    }

    public void display() {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }
    public void clear() {
        ListNode cur = head;
        head = null;
        tail = null;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.pre = null;
            cur.next = null;
            cur = curNext;
        }
    }

小结

????????正所谓 -- 基础不牢,地动山摇。虽然JAVA自带了自己的数据结构,但我们仍然需要在模拟实现上多下功夫,了解其底层的实现原理,多看源码,不仅可以加强自己的代码能力,也可以加深对数据结构这门学科的理解。


三、总结

? ? ? ? 我们通过链表的模拟实现,不难发现,链表的头插尾插效率很高,可以做到O(1),即使是随机插入,最坏的情况也是O(N),所以,当我们的实际需求是频繁的插入删除时,同时如果要使用线性结构,请毫不犹豫使用链表。

????????但是,我们同样也发现了,如果我们要知道链表的第K个元素,我们必须去遍历它,而顺序表便没有这个烦恼,我们可以直接访问数组下标,在O(1)的时间复杂度内便可完成,所以,当我们实际需求是要查找,只要进行很少的插入删除操作时,最好使用顺序表。

以上便是本篇文章的全部内容。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-10-08 21:07:03  更:2022-10-08 21:09:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 19:46:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码