数据结构(Java版)第五期:ArrayList与顺序表(下)

数据结构(Java版)第五期:ArrayList与顺序表(下)

目录

一、用数组实现顺序表


一、用数组实现顺序表

     我们提到过,顺序表是基于数组的封装,这次我们以int为例,用数组去实现一个顺序表。

public class MyArrayList { private int[] arr; public MyArrayList(int capacity){//指定初始容量 arr = new int[capacity];//这个顺序表依然是空的,还得使用add往里面添加有效元素 } }

      既然存在有效与无效,那我们该如何区分呢?我们可以定义一个size变量。

private int size;//规定前size个元素为有效元素

下面就是进行写出方法,比如增、删、查、改等。

 //获取元素个数 public int size(){ return size; } //新增元素,尾插 public void add(int val){ } //任意位置新增元素 public void add(int index,int val){ } //根据下标获取元素 public void get(int index){ } //根据下标设置元素 public void set(int index,int val){ } //根据数组的值删除元素 public void delete(int val){ } //根据下标删除元素 public int remove(int index,int val){ } //判断元素是否存在 public boolean contains(int val){ } //查找元素所在位置 public int indexOf(int index){ } //删除所有元素 public void clear(){ } //打印操作,把ArrayList转成字符串 @Override public String toString() { }

       这里是一些主要的方法,如果我们想实现其他方法,也可以再新增。我们对方法进行了命名之后,下面就是进行实现。我们先来看新增元素的实现。我们需要把新增的元素放到最后的位置,下标为size。如果说size比arr.length的值要小,那么我们正常添加就可以,可如果我们size的值比arr.length要小,我们要先进行扩容。我们可以再写一个resize方法来扩容。

 //新增元素,尾插 public void add(int val){ if(size == arr.length){ resize(); } arr[size] = val; size++; } private void resize(){//这个方法只有程序员才能调用 //创建一个更长的数组,长度扩大到原来的1.5倍 //这样就能保证我们每添加一个元素,长度够用 int[] newArr = new int[(int)(arr.length * 1.5)]; for (int i = 0; i < size; i++) { //原来数组的元素复制到新数组中 newArr[i] = arr[i]; //新数组代替旧数组,旧的数组会被垃圾回收给释放掉 arr = newArr; } } 

      我们还要实现toString方法,方便我们后期打印这个顺序表。

 @Override public String toString() { // 打印的格式形如: [1, 2, 3, 4] StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("["); for (int i = 0; i < size; i++) { stringBuilder.append(arr[i]); if (i < size - 1) { stringBuilder.append(", "); } } stringBuilder.append("]"); return stringBuilder.toString(); }

下面我们来进行一下测试,

 public static void test(){ MyArrayList list = new MyArrayList(6); list.add(1); list.add(2); list.add(3); list.add(4); System.out.println(list.size); System.out.println(list); } public static void main(String[] args) { test(); }

 下面是调试结果

运行结果 

       下面我们进行对新增元素的实现,当我们插入一个新元素时,这个新元素之后的所有元素都要后移一个位置。那么此时我们的时间复杂度就是

O(N)

 public void add(int index, int val) { if (index < 0 || index > size) { throw new IndexOutOfBoundsException("Index out of bounds"); } // 如果数组已经满了, 继续添加元素, 也是要先扩容 if (size == arr.length) { resize(); } // 搬运元素的操作. 从后往前, 依次把每个元素都往后搬运一个位置. // 如果元素本身的下标是 i, 就把这个元素赋值到 i+1 的位置上. // index 位置的元素也需要往后搬运一下, i >= index for (int i = size - 1; i >= index; i--) { arr[i + 1] = arr[i]; } // 此时就相当于把 index 位置已经腾出来了. 把新元素放到 index 位置上就好了. arr[index] = val; // 不要忘记更新 size size++; }

        有的老铁可能觉得这个方法有点麻烦,如果说我们直接插入的新元素,这个位置的旧元素直接后移到我们顺序表中的最后一个位置行不行,并且此时的时间复杂度就是

O(N)

。其实呢,对于我们的顺序表和链表这样的结构来说,它们都是“有序的”,如果我们把原有的顺序改变了,就不是原来的结构了。

//根据下标获取元素 public int get(int index){ if(index<0 || index>arr.length){ throw new IndexOutOfBoundsException("下标越界"+index); } return arr[index]; } 

       数组通过[]访问下标,时间复杂度是

O(1)

。Java的数组呢,本质是来源于C/C++,C/C++中数组访问下标时间复杂度同样也是

O(1)

。我们知道数组是一块连续存放的内存空间,通过过数组首元素和下标快速算出来对应元素的地址,根据这个地址来访问这个内存空间。内存的硬件设备具备一个特性,随机访问能力。

       对于删除元素,也是要保证顺序表在有序的前提下,对于尾删的情况,直接进行size--就可以了,不必进行搬运。 但如果说,我们删除中间的某一个元素,当index==size时,尾插是可以的,但index>size的时候,就会出现异常。

 if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("下标越界: " + index); }
 //根据下标删除元素 public int remove(int index,int val){ if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("下标越界: " + index); } //要删除的元素先保存起来,就不怕后期搬运的时候被覆盖 int result = arr[index]; if (index == size-1){ //尾删 size--; return result; } //一定要注意边界值,可以代入具体数字进行验证 for (int i = index; i < size-1; i++) { arr[i] = arr[i+1];//进行向后搬运 } }

       仔细分析上面的代码时就会发现,当index为尾删的时候,for循环是进不去的,接下来进行的就是size--的操作。

 public static void test2(){ MyArrayList list = new MyArrayList(10); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); list.add(6); list.add(7); list.remove(0); System.out.println(list); list.remove(2); System.out.println(list); list.remove(4); System.out.println(list); list.remove(100); System.out.println(list); }

对于delete方法,我们需要先找到这个值所在的位置,找到之后,调用remove方法执行。

 for (int i = 0; i < size; i++) { if(arr[i] == val){ //找到了 break; } }

       这个for循环结束的条件有两个,i==size或者是arr[i]==val时,但由于我们这个i是for循环里面的局部变量,我们可以把i改成index,并作用再for循环之外。

 //根据数组的值删除元素 public void delete(int val){ int index = 0; for (; index < size; index++) { if(arr[index] == val){ //找到了 break; } } for (int i = index; i < size-1; i++) { arr[i] = arr[i+1];//进行向后搬运 } }

    对于contains方法和IndexOf两个方法的代码逻辑非常相似,也比较简单,就不作过多讲解。

 //判断元素是否存在 public boolean contains(int val){ for (int i = 0; i < size; i++) { if (arr[i] == val) { return true; } } return false; } //查找元素所在位置 public int indexOf(int val){ for (int i = 0; i < size; i++) { if (arr[i] == val) { return i; } } return -1; }

Read more

保姆级教程:Windows/Mac/Linux三平台OpenClaw部署,90%的坑我帮你踩完了

保姆级教程:Windows/Mac/Linux三平台OpenClaw部署,90%的坑我帮你踩完了

做OpenClaw企业落地的这大半年,我被问得最多的问题就是: 为什么我跟着网上的教程部署,要么Docker启动失败,要么面板访问不了? Windows部署WSL2报错怎么解决?Mac M芯片跑不起来是什么原因?Linux服务器部署完了外网访问不了? 毫不夸张地说,OpenClaw的部署本身极简,但90%的问题都不是OpenClaw本身的问题,而是环境配置、权限、端口、依赖兼容这些基础坑。我自己在三平台都反复部署过几十次,踩过的坑能凑成一本手册,小到中文路径导致的启动失败,大到企业内网环境的镜像拉取失败,几乎都遇见过。 这篇文章,我就把Windows/Mac/Linux三平台的部署流程,拆成保姆级的一步步操作,每一步都标注踩坑点,新手跟着走,99%能一次部署成功。同时把90%的人会遇到的问题,整理成「踩坑合集」,直接给原因+现成的解决方案,不用你再到处搜教程。 部署前必看:先搞懂这3点,少走90%的弯路 1. 硬件最低要求 很多人上来就部署,结果自己的电脑/服务器根本带不动,先看清楚硬件门槛: 配置类型最低配置推荐配置说明CPU2核4线程4核8线程纯指令执行用最低配

By Ne0inhk
打工人摸鱼新姿势!轻量斗地主服务器,内网穿透让同事远程联机不翻车

打工人摸鱼新姿势!轻量斗地主服务器,内网穿透让同事远程联机不翻车

Ratel 斗地主服务器是一款基于 Netty 和 Protobuf 开发的轻量级服务端软件,核心功能是搭建斗地主游戏服务,适配 Windows、Linux、macOS 多系统,适合职场上班族、学生群体这类想利用碎片时间休闲的人群,它的核心优点是资源占用极低,CPU 仅占 3%,内存消耗也少,还支持 AI 对手和隐藏进程,日常使用不会给设备带来负担。 使用这款软件时也有一些小细节需要注意,比如在办公场景下启动服务要注意隐藏会话,避免被察觉;和 AI 对战时不同难度模式的出牌节奏有差异,新手可以先从简单模式上手,而且软件启动后需要保持终端窗口运行,不小心关闭就会中断游戏。 不过这款软件仅靠局域网使用时,会遇到不少实际问题:比如上班族想和异地的同事联机,却因为不在同一局域网无法连接;学生在宿舍搭建好服务器,放假回家后就没法和室友继续玩,只能局限在小范围的网络环境里,大大降低了使用的灵活性。 而将 Ratel 斗地主服务器和 cpolar 内网穿透结合后,这些问题就能迎刃而解。cpolar 无需公网 IP 就能把本地的游戏服务映射到公网,

By Ne0inhk

Ubuntu新手必看:如何快速更换国内源(阿里/清华/中科大源对比)

Ubuntu 新手的第一道“加速”关:国内镜像源深度解析与实战指南 刚装好 Ubuntu,那种清爽的桌面和开箱即用的感觉确实不错。但当你兴冲冲地打开终端,准备用 apt install 装点东西时,进度条那慢如蜗牛的爬行速度,是不是瞬间浇灭了一半的热情?别急着怀疑自己的网络,这几乎是每个国内 Ubuntu 用户都会遇到的“新手墙”。问题的核心,往往不在于你的宽带,而在于系统默认连接的软件仓库服务器远在海外,网络延迟和带宽限制成了最大的瓶颈。 解决这个问题的方法,就是“换源”——将系统的软件源地址,更换为位于国内的镜像服务器。这听起来像是个简单的操作,但背后其实有不少门道:国内有哪些可靠的镜像站?阿里云、清华大学、中国科学技术大学(USTC)的源有什么区别?为什么别人的源换上去飞快,你的却报了一堆错?今天,我们就来彻底拆解这个问题。这不仅仅是复制粘贴几行命令,而是帮你理解原理、掌握选择、并能在遇到问题时自己动手排查。无论你是刚接触 Linux 的开发新手,还是希望优化工作流效率的资深用户,一个配置得当的软件源,

By Ne0inhk
时序数据库选型指南:在大数据浪潮中把握未来,为何Apache IoTDB值得关注?

时序数据库选型指南:在大数据浪潮中把握未来,为何Apache IoTDB值得关注?

文章目录 * 1 -> 引言 * 2 -> 时序数据的挑战与选型的重要性 * 3 -> 核心选型维度:超越性能参数的综合考量 * 4 -> 深入聚焦:Apache IoTDB的差异化优势 * 5 -> 选型建议与总结 1 -> 引言 在当今这个万物互联、数据驱动的时代,从工业传感器到智能电网,从车联网到金融交易,每一秒都在产生海量带有时间戳的数据——时序数据。这类数据不仅是企业运营的“脉搏”,更是驱动智能决策、优化效率、预测未来的核心燃料。面对汹涌而至的时序数据洪流,如何选择一款合适的时序数据库(Time-Series Database, TSDB),已成为大数据架构师、物联网(IoT)平台开发者和数据分析师面临的关键决策。本文将站在大数据技术演进和国产基础软件发展的视角,为您梳理时序数据库的选型要点,

By Ne0inhk