【数据结构】 LinkedList的模拟实现与使用

🍀什么是LinkedList

LinkedList 的官方文档
LinkedList的底层是双向链表结构(链表后面介绍),由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
在这里插入图片描述

🌴LinkedList的模拟实现

我们在这里创建一个MyLinkedList的java文件用于模拟实现我们的LinkedList。

🚩创建双链表

我们要模拟实现LinkendList,首先我们要有我们的双链表,这里我们来实现一个双链表

双链表所需要元素

  • 前驱节点:用于存储前一节点的位置,用prev表示
  • 后继节点:用于储存下一节点的位置,用next表示
  • 所需要储存的数据,用val表示
  • 头节点:用head表示
  • 尾节点:用last表示

如下图所示:
在这里插入图片描述
代码实现如下:

    static class ListNode {
        public int val;
        public ListNode prev;//前驱
        public ListNode next;//后继

        public ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode head;//头节点
    public ListNode last;//尾节点

接下来我们将实现它的一些的功能

🚩头插法

在这里插入图片描述

实现思路:

  • 首先判断头节点是否为null
  • 若为null,则该节点就是头节点,也是尾节点
  • 则将原先head的前驱节点指向位置改为新增节点位置
  • 新增节点后驱节点指向位置改为head节点的位置
  • 将新增节点设为新的头节点

在这里插入图片描述

代码实现如下:

    //插法 O(1)
    public void addFirst(int data){
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            last = node;
        }else {
            node.next = head;
            head.prev = node;
            head = node;
        }
    }

🚩尾插法

在这里插入图片描述
实现思路与头插法类似:

  • 首先判断头节点是否为null
  • 若为null,则该节点就是尾节点,也是头节点
  • 将last的后继节点设为node
  • node的前驱节点设为last
  • node设为新的尾节点

在这里插入图片描述
代码实现如下:

    //尾插法 O(1)
    public void addLast(int data){
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            last = node;
        }else {
            last.next = node;
            node.prev = last;
            last = node;
        }
    }

🚩任意位置插入

首先我们需要对插入数据进行判断其合法线性

这里我们自定义一个异常,为ListIndexOutOfException

public class ListIndexOutOfException extends RuntimeException{
    public ListIndexOutOfException() {
    }

    public ListIndexOutOfException(String message) {
        super(message);
    }
}

其次我们可以对插入数据进行判断,若是在头尾进行插入,则可以直接用头插法与尾插法进行搞定

当所插入数据在中间时,我们首先要找到需要插入的节点,并返回,用cur接收

    private ListNode findIndex(int index) {
        ListNode cur = head;
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }

在这里插入图片描述

当我们找到要插入的节点后,我们的步骤为以下几步

  • 我们先将node的next置为cur;
  • 让cur前驱节点所指向的节点的后继节点指向node
  • 再将cur的前驱节点给node的前驱节点
  • 最后将cur的前驱节点置为node;
  • 在这里插入图片描述

代码实现如下:

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        if(index < 0 || index > size()) {
            throw new ListIndexOutOfException("违规数据");
        }
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == size()) {
            addLast(data);
            return;
        }

        ListNode cur = findIndex(index);
        //
        ListNode node = new ListNode(data);
        node.next = cur;
        cur.prev.next = node;
        node.prev = cur.prev;
        cur.prev = node;

    }

🚩查找关键字

直接遍历查找即可

代码实现如下

    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

🚩链表长度

用一个len变量进行记录,遍历链表,最后进行返回即可

代码实现如下:

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

🚩打印链表

遍历链表进行输出即可

代码实现如下:

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

🚩删除第一次出现关键字为key的节点

我们大致的做法就是对链表进行遍历,遍历到相应的元素,删除即可,然后返回即可;

细分可分为三种情况

  • 删除的是头节点
  • 删除的是中间节点
  • 删除的是尾节点

📌删除的是头节点

我们只需要将head向前走一步就好

这时候又分为只一个节点和有多个节点的情况

当只有一个节点时,我们的head只需要向前走一步就好

如果有多个节点,我们还需要将新节点的前驱节点置为null;

代码实现如下:

 head = head.next;
 //只有一个节点
 if(head != null) {
 	head.prev = null;
 }

📌删除的是中间节点

在这里插入图片描述

我们分为两步:

  • 将cur的前驱节点的后继节点变为cur的后继节点
  • 将cur的后继节点的前驱节点变为cur的前驱节点
  • 在这里插入图片描述

代码实现如下:

cur.prev.next = cur.next;                   
cur.next.prev = cur.prev;           

📌删除节点为尾节点

在这里插入图片描述

其实我们也分为两步:

  • 将cur的前驱节点的后继节点变为cur的后继节点
  • 将cur的前驱节点设为新的尾节点
  • 在这里插入图片描述

我们发现其实前面一步和删除中间节点一样,所以这里我们将他们组合起来,在删除中间节点里面加入判断即可

代码实现如下:

 //中间  尾巴
 cur.prev.next = cur.next;
//不是尾巴节点
if(cur.next != null) {
	cur.next.prev = cur.prev;
}else {
//是尾巴节点
	last = last.prev;
}

删除完后直接返回就好,如此一来,我们删除第一次出现关键字为key的节点完整代码也就可以出来了

代码如下:

    //删除第一次出现关键字为key的节点
    public void remove(int key){
        ListNode cur = head;
        while (cur != null) {
            //开始删除了
            if(cur.val == key) {
                //1. 删除的是头节点
                if(cur == head) {
                    head = head.next;
                    //只有一个节点
                    if(head != null) {
                        head.prev = null;
                    }
                }else {
                    //中间  尾巴
                    cur.prev.next = cur.next;
                    //不是尾巴节点
                    if(cur.next != null) {
                        cur.next.prev = cur.prev;
                    }else {
                        //是尾巴节点
                        last = last.prev;
                    }
                }
                return;
            }
            cur = cur.next;
        }
    }

🚩删除所有值为key的节点

这个代码实现起来与删除第一次出现关键字为key的节点几乎是一模一样的;

我们只需要return删掉就好

代码实现如下:

    //删除所有值为key的节点
    public void removeAllKey(int key){
        ListNode cur = head;
        while (cur != null) {
            //开始删除了
            if(cur.val == key) {
                //1. 删除的是头节点
                if(cur == head) {
                    head = head.next;
                    //只有一个节点
                    if(head != null) {
                        head.prev = null;
                    }
                }else {
                    //中间  尾巴
                    cur.prev.next = cur.next;
                    //不是尾巴节点
                    if(cur.next != null) {
                        cur.next.prev = cur.prev;
                    }else {
                        //是尾巴节点
                        last = last.prev;
                    }
                }
            }
            cur = cur.next;
        }
    }

🚩清空链表

我们只需要遍历整个链表,将每个节点的前驱与后继节点都置为null就好

代码实现如下:

    public void clear(){
        ListNode cur = head;
        while(cur != null) {
            cur.prev  = null;
            cur = cur.next;
            cur.prev.next = null;
        }
        head = null;
        last = null;
    }

🚩完整代码实现

public class MyLinkedList {
    static class ListNode {
        public int val;
        public ListNode prev;//前驱
        public ListNode next;//后继

        public ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode head;//头节点
    public ListNode last;//尾节点

    //头插法 O(1)
    public void addFirst(int data){
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            last = node;
        }else {
            node.next = head;
            head.prev = node;
            head = node;
        }
    }
    //尾插法 O(1)
    public void addLast(int data){
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            last = node;
        }else {
            last.next = node;
            node.prev = last;
            last = node;
        }
    }
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        if(index < 0 || index > size()) {
            throw new ListIndexOutOfException("违规数据");
        }
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == size()) {
            addLast(data);
            return;
        }

        ListNode cur = findIndex(index);
        //
        ListNode node = new ListNode(data);
        node.next = cur;
        cur.prev.next = node;
        node.prev = cur.prev;
        cur.prev = node;

    }
    private ListNode findIndex(int index) {
        ListNode cur = head;
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        ListNode cur = head;
        while (cur != null) {
            if(cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
    //删除第一次出现关键字为key的节点
    public void remove(int key){
        ListNode cur = head;
        while (cur != null) {
            //开始删除了
            if(cur.val == key) {
                //1. 删除的是头节点
                if(cur == head) {
                    head = head.next;
                    //只有一个节点
                    if(head != null) {
                        head.prev = null;
                    }
                }else {
                    //中间  尾巴
                    cur.prev.next = cur.next;
                    //不是尾巴节点
                    if(cur.next != null) {
                        cur.next.prev = cur.prev;
                    }else {
                        //是尾巴节点
                        last = last.prev;
                    }
                }
                return;
            }
            cur = cur.next;
        }
    }
    //删除所有值为key的节点
    public void removeAllKey(int key){
        ListNode cur = head;
        while (cur != null) {
            //开始删除了
            if(cur.val == key) {
                //1. 删除的是头节点
                if(cur == head) {
                    head = head.next;
                    //只有一个节点
                    if(head != null) {
                        head.prev = null;
                    }
                }else {
                    //中间  尾巴
                    cur.prev.next = cur.next;
                    //不是尾巴节点
                    if(cur.next != null) {
                        cur.next.prev = cur.prev;
                    }else {
                        //是尾巴节点
                        last = last.prev;
                    }
                }
            }
            cur = cur.next;
        }
    }

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

    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;
        while(cur != null) {
            cur.prev  = null;
            cur = cur.next;
            cur.prev.next = null;
        }
        head = null;
        last = null;
    }
}

🎍LinkedList的使用

在集合框架中,LinkedList也实现了List接口,具体如下:
在这里插入图片描述

【说明】

  1. LinkedList实现了List接口

  2. LinkedList的底层使用了双向链表

  3. LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问

  4. LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)

  5. LinkedList比较适合任意位置插入的场景

🚩LinkedList的构造

在这里插入图片描述
构造代码如下:

import java.util.LinkedList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
// 构造一个空的LinkedList
        List<Integer> list1 = new LinkedList<>();
        List<String> list2 = new java.util.ArrayList<>();
        list2.add("JavaSE");
        list2.add("JavaWeb");
        list2.add("JavaEE");
// 使用ArrayList构造LinkedList
        List<String> list3 = new LinkedList<>(list2);
    }
}

🚩LinkedList的其他常用方法介绍

在这里插入图片描述
方法使用代码如下:

import java.util.LinkedList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1); // add(elem): 表示尾插
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        list.add(7);
        System.out.println(list.size());
        System.out.println(list);
// 在起始位置插入0
        list.add(0, 0); // add(index, elem): 在index位置插入元素elem
        System.out.println(list);
        list.remove(); // remove(): 删除第一个元素,内部调用的是removeFirst()
        list.removeFirst(); // removeFirst(): 删除第一个元素
        list.removeLast(); // removeLast(): 删除最后元素
        list.remove(1); // remove(index): 删除index位置的元素
        System.out.println(list);
// contains(elem): 检测elem元素是否存在,如果存在返回true,否则返回false
        if(!list.contains(1)){
            list.add(0, 1);
        }list.add(1);
        System.out.println(list);
        System.out.println(list.indexOf(1)); // indexOf(elem): 从前往后找到第一个elem的位置
        System.out.println(list.lastIndexOf(1)); // lastIndexOf(elem): 从后往前找第一个1的位置
        int elem = list.get(0); // get(index): 获取指定位置元素
        list.set(0, 100); // set(index, elem): 将index位置的元素设置为elem
        System.out.println(list);
// subList(from, to): 用list中[from, to)之间的元素构造一个新的LinkedList返回
        List<Integer> copy = list.subList(0, 3);
        System.out.println(list);
        System.out.println(copy);
        list.clear(); // 将list中元素清空
        System.out.println(list.size());
    }
}

🚩LinkedList的遍历

    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1); // add(elem): 表示尾插
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        list.add(7);
        System.out.println(list.size());
// foreach遍历
        for (int e:list) {
            System.out.print(e + " ");
        }
        System.out.println();
// 使用迭代器遍历---正向遍历
        ListIterator<Integer> it = list.listIterator();
        while(it.hasNext()){
            System.out.print(it.next()+ " ");
        }
        System.out.println();
// 使用反向迭代器---反向遍历
        ListIterator<Integer> rit = list.listIterator(list.size());
        while (rit.hasPrevious()){
            System.out.print(rit.previous() +" ");
        }
        System.out.println();
    }

🎄ArrayList和LinkedList的区别

在这里插入图片描述

⭕总结

关于《【数据结构】 LinkedList的模拟实现与使用》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

  • 52
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 57
    评论
好的,我可以为您提供一个简单的Java代码模拟进程调度算法。这里我给出的是基于时间片轮转算法的模拟代码。 首先,定义一个进程类,包含进程ID、到达时间、服务时间、剩余服务时间、优先级等属性: ```java class Process { public int pid; // 进程ID public int arriveTime; // 到达时间 public int serviceTime; // 服务时间 public int remainTime; // 剩余服务时间 public int priority; // 优先级 // 构造函数 public Process(int pid, int arriveTime, int serviceTime, int priority) { this.pid = pid; this.arriveTime = arriveTime; this.serviceTime = serviceTime; this.remainTime = serviceTime; this.priority = priority; } } ``` 接下来,定义一个时间片轮转调度类,包含一个进程队列和时间片大小: ```java import java.util.*; class RoundRobinScheduler { public int timeSlice; // 时间片大小 public Queue<Process> queue; // 进程队列 // 构造函数 public RoundRobinScheduler(int timeSlice) { this.timeSlice = timeSlice; this.queue = new LinkedList<Process>(); } // 添加进程到队列 public void addProcess(Process p) { queue.offer(p); } // 执行调度算法 public void run() { int currentTime = 0; // 当前时间 int totalWaitTime = 0; // 总等待时间 int totalTurnaroundTime = 0; // 总周转时间 int totalProcess = queue.size(); // 进程总数 while (!queue.isEmpty()) { Process p = queue.poll(); // 计算等待时间 int waitTime = currentTime - p.arriveTime; if (waitTime < 0) { currentTime = p.arriveTime; waitTime = 0; } // 计算服务时间 int serviceTime = Math.min(timeSlice, p.remainTime); // 更新进程剩余服务时间 p.remainTime -= serviceTime; // 更新当前时间 currentTime += serviceTime; // 如果进程未执行完,则重新加入队列 if (p.remainTime > 0) { queue.offer(p); } // 计算周转时间 int turnaroundTime = waitTime + p.serviceTime; // 统计总等待时间和总周转时间 totalWaitTime += waitTime; totalTurnaroundTime += turnaroundTime; } // 打印平均等待时间和平均周转时间 System.out.printf("Average waiting time: %.2f\n", (double)totalWaitTime/totalProcess); System.out.printf("Average turnaround time: %.2f\n", (double)totalTurnaroundTime/totalProcess); } } ``` 最后,我们可以使用一个简单的main函数来模拟进程调度过程: ```java public static void main(String[] args) { // 创建进程对象 Process p1 = new Process(1, 0, 5, 1); Process p2 = new Process(2, 1, 3, 2); Process p3 = new Process(3, 2, 4, 3); Process p4 = new Process(4, 3, 2, 4); Process p5 = new Process(5, 4, 4, 5); // 创建调度器对象 RoundRobinScheduler scheduler = new RoundRobinScheduler(2); // 添加进程到调度器队列 scheduler.addProcess(p1); scheduler.addProcess(p2); scheduler.addProcess(p3); scheduler.addProcess(p4); scheduler.addProcess(p5); // 执行调度算法 scheduler.run(); } ``` 以上代码模拟了一个包含5个进程的进程调度过程,使用了时间片大小为2的时间片轮转算法。您可以尝试修改进程的属性和调度算法的参数来进行不同的测试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 57
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

遇事问春风乄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值