数据结构之数组
1、数据结构研究的是数据如何在计算机中进行组织和存储,使得我们可以高效的获取数据或者修改数据。
数据结构包含三种结构,线性结构,树结构,图结构。其中,线性结构包含数组,栈,队列,链表,哈希表等等。树结构包含二叉树,二分搜索树,AVL树,红黑树,Treap,Splay,堆,Tril,K-D树,并查集,哈夫曼树等等。图结构包含邻接矩阵,邻接表。
2、时间复杂度O(1), O(n), O(logn), O(nlogn),O(n^2),其中,大O描述的是算法的运行时间和输入数据之间的关系,n是指数据元素的个数。O后面的括号中有一个函数,指明某个算法的耗时/耗空间与数据增长量之间的关系。其中的n代表输入数据的量。
3、数据结构之数组。数据的查询,修改,删除,增加,动态扩容以及数组的缩容。
3.1、添加元素的时间复杂度分析。
1)、addLast(e),时间复杂度O(1),直接在数组最后赋值即可,和数组的容量无关。如果是进行了扩容reszie操作,那么时间复杂度是O(n)。
2)、addFirst(e),时间复杂度O(n),在数组头部添加元素,需要将所有元素后移一位。
3)、add(index,e),按照概率论,时间复杂度近似O(n),在该索引index位置插入一个元素e,和index的值有关的,比如index的值等于0或者index等于size。
3.2、删除元素的时间复杂度分析。
1)、removeFirst(e),时间复杂度是O(1),如果是进行了缩容reszie操作,那么时间复杂度是O(n)。
2)、removeLast(e),时间复杂度是O(n)。
3)、remove(index,e),时间复杂度是O(n)。
3.3、修改元素的时间复杂度分析。
1)、set(index,e),时间复杂度是O(1),数组支持随机访问哦。
3.4、查询元素的时间复杂度分析。
1)、get(index),时间复杂度是O(1)。
2)、contains(e),时间复杂度是O(n)。
3)、find(e),时间复杂度是O(n)。
3.5、综上所述,增加的时间复杂度是O(n)。删除的时间复杂度是O(n)。修改的时间复杂度是,如果已知索引O(1),未知索引O(n)。查找的时间复杂度是,如果已知索引O(1),未知索引O(n)。
1 package com.company; 2 3 4 /** 5 * 6 */ 7 public class Array<E> { 8 9 private E[] data;//定义数组 10 private int size;//数组实际的长度 11 12 /** 13 * 构造函数,传入数组的容量capacity构造Array 14 * 15 * @param capacity 初始化数据的容量长度 16 */ 17 public Array(int capacity) { 18 // 使用泛型,可以创建任意类型的数组类型 19 data = (E[]) new Object[capacity]; 20 // 初始化数组的实际内容的长度为0 21 size = 0; 22 } 23 24 /** 25 * 无参数的构造函数,默认数组的容量capacity=10 26 */ 27 public Array() { 28 // 无参构造函数,指定初始化数组容量长度为0 29 this(10); 30 } 31 32 /** 33 * 获取数组中的元素个数 34 * 35 * @return 36 */ 37 public int getSize() { 38 // 获取到数组中的元素个数 39 return size; 40 } 41 42 /** 43 * 获取数组的容量 44 * 45 * @return 46 */ 47 public int getCapacity() { 48 // 获取到数组的容量 49 return data.length; 50 } 51 52 /** 53 * 返回数组是否为空 54 * 55 * @return 56 */ 57 public boolean isEmpty() { 58 // 判断数组的长度是否为空 59 return size == 0; 60 } 61 62 63 /** 64 * 向所有元素后添加一个新元素 65 * 66 * @param e 67 */ 68 public void addLast(E e) { 69 // if (size == data.length) { 70 // throw new IllegalArgumentException("AddLast failed,Array is full......"); 71 // } else { 72 // 在数组的末尾添加一个元素 73 // data[size] = e; 74 // 添加元素以后数组长度加一 75 // size++; 76 // } 77 78 // 调用公共的方法,其实就是在数组的实际长度后面添加指定的元素的。 79 80 // 调用添加数组的方法,参数一,传入数组实际的长度,参数二,添加的元素 81 add(size, e); 82 } 83 84 /** 85 * 在第一个位置添加元素,在所有元素前添加一个新元素 86 * 87 * @param e 88 */ 89 public void addFirst(E e) { 90 // 在数组的第一个位置添加元素 91 add(0, e); 92 } 93 94 95 /** 96 * 在第index个位置插入一个新元素e 97 * 98 * @param index 在第index个位置 99 * @param e 添加的元素内容 100 */ 101 public void add(int index, E e) { 102 // 判断位置index是否小于0或者索引index是否大于数组的实际长度size 103 if (index < 0 || index > size) { 104 throw new IllegalArgumentException("add failed,Require index >= 0 and index <= size......."); 105 } 106 107 // 判断实际长度size是否等于数组的长度 108 if (size == data.length) { 109 // 可以直接抛出异常 110 // throw new IllegalArgumentException("add failed,Array is full......"); 111 // 调用数组扩容的方法,扩容的长度为元数组长度的2倍 112 resize(2 * data.length); 113 } 114 115 // 添加元素,就是在最后一个位置,将元素添加进去,即size的位置 116 // 从后向前移动,从后面的元素向后移动 117 // 如果传入的index是size,则初始化的位置是size-1,那么i的值大于等于传入的index(即size的值),i递减 118 119 // int i = size - 1是数组的实际长度的,即最后一个位置的元素 120 // i >= index,是在指定索引位置添加索引 121 // i--是为了将前面的元素向后面移动的。 122 for (int i = size - 1; i >= index; i--) { 123 // 后一个索引位置赋予前一个索引位置的值 124 data[i + 1] = data[i]; 125 } 126 // 将元素的内容插入到数组的index索引位置 127 data[index] = e; 128 // 数组的size递增 129 size++; 130 } 131 132 /** 133 * 私用扩容,扩容数组的长度 134 * 135 * @param newCapacity 136 */ 137 private void resize(int newCapacity) { 138 // 使用泛型创建对象,使用new Object的方法创建泛型的对象 139 E[] newData = (E[]) new Object[newCapacity]; 140 // 循环,将原数组里面的内容放入到新数组里面,新数组的长度为元素组的2倍 141 for (int i = 0; i < size; i++) { 142 // 将原数组的值赋值给新数组的值 143 newData[i] = data[i]; 144 } 145 // 将原数组的值指向新数组,此操作,不影响原数组的正常使用 146 data = newData; 147 } 148 149 /** 150 * 获取Index索引位置的元素 151 * 152 * @param index 153 * @return 154 */ 155 public E get(int index) { 156 // 如果索引index小于0或者索引的长度大于等于实际长度size,则抛出异常 157 if (index < 0 || index >= size) { 158 throw new IllegalArgumentException("add failed,Index is illegal......"); 159 } 160 // 返回指定索引位置的数组元素 161 return data[index]; 162 } 163 164 /** 165 * 修改index索引位置的元素e 166 * 167 * @param index 168 */ 169 public void set(int index, E e) { 170 // 如果索引index小于0或者索引的长度大于等于实际长度size,则抛出异常 171 if (index < 0 || index >= size) { 172 throw new IllegalArgumentException("add failed,Index is illegal......"); 173 } 174 // 则将元素放入到数组的索引index位置 175 data[index] = e; 176 } 177 178 179 /** 180 * @return 181 */ 182 @Override 183 public String toString() { 184 // 封装数组遍历的内容 185 StringBuilder sb = new StringBuilder(); 186 sb.append(String.format("Array : size = %d,capacity = %d\n", size, data.length)); 187 sb.append('['); 188 for (int i = 0; i < size; i++) { 189 sb.append(data[i]); 190 if (i != size - 1) { 191 sb.append(", "); 192 } 193 } 194 sb.append(']'); 195 return sb.toString(); 196 } 197 198 /** 199 * 查找数据中是否包含元素e 200 * 201 * @param e 202 * @return 203 */ 204 public boolean contains(E e) { 205 // 查看是否包含元素,进行遍历 206 for (int i = 0; i < size; i++) { 207 // 如果数组元素等于传入的元素e 208 if (data[i].equals(e)) { 209 // 返回true 210 return true; 211 } 212 } 213 return false; 214 } 215 216 /** 217 * 查找数组中元素e所在的索引,如果不存在元素e,则返回-1 218 * 219 * @param e 220 * @return 221 */ 222 public int find(E e) { 223 // 查看是否包含元素,进行遍历 224 for (int i = 0; i < size; i++) { 225 // 如果数组元素等于传入的元素e 226 if (data[i].equals(e)) { 227 // 返回该索引位置的索引 228 return i; 229 } 230 } 231 return -1; 232 } 233 234 /** 235 * 从数组中删除index位置的元素,返回删除的元素 236 * 237 * @param index 此参数是索引参数 238 * @return 239 */ 240 public E remove(int index) { 241 // 如果索引位置小于等于0或者索引位置大于等于数组的实际长度size 242 if (index < 0 || index >= size) { 243 throw new IllegalArgumentException("remove failed,Index is illegal......"); 244 } 245 246 // 返回删除的元素,先获取到要删除的元素 247 E result = data[index]; 248 System.out.println(result); 249 250 // 初始化值是要删除索引的位置加1,且i的值小于实际size的大小,i递减 251 252 // int i = index + 1,传入进去的删除索引位置的元素 253 // i < size,i小于数组的实际长度 254 // i++,i递增 255 for (int i = index + 1; i < size; i++) { 256 // 将数组元素的值赋值被删除元素的位置上面 257 // 即将数组元素向前移动,即第i位置的索引的数据移动到第i-1位置索引的数据 258 data[i - 1] = data[i]; 259 } 260 // 删除元素以后,数组长度size递减1 261 size--; 262 // 将不可访问的位置置空 263 data[size] = null; 264 265 // 避免出现复杂度震荡 266 // 删除数组长度,缩小容量 267 // data.length / 2 != 0,避免出现创建数组长度为0的数组 268 if (size == data.length / 4 && data.length / 2 != 0) { 269 // 缩容数组的长度 270 resize(data.length / 2); 271 } 272 return result; 273 } 274 275 /** 276 * 删除数组的第一个元素的值 277 * <p> 278 * 从删除中删除第一个元素,返回删除的元素 279 * 280 * @return 281 */ 282 public E removeFirst() { 283 // 删除数组的第一个位置的元素 284 return remove(0); 285 } 286 287 /** 288 * 从数组中删除最后一个元素,返回删除的元素。 289 * 290 * @return 291 */ 292 public E removeLast() { 293 // 删除数组最后一个位置的元素 294 return remove(size - 1); 295 } 296 297 /** 298 * 从数组中删除元素e 299 * 300 * @param e 301 */ 302 public void removeElement(E e) { 303 // 查看数组里面是否有该元素 304 int index = find(e); 305 // 如果查找到存在该元素 306 if (index != -1) { 307 // 调用删除数组元素的方法 308 remove(index); 309 } 310 } 311 312 public static void main(String[] args) { 313 // 数组添加元素 314 Array<Integer> array = new Array<>(20); 315 for (int i = 0; i < 10; i++) { 316 array.addLast(i); 317 } 318 319 System.out.println(array.toString()); 320 321 // 在指定位置添加元素 322 array.add(1, 110); 323 System.out.println(array.toString()); 324 325 // 在第一个位置添加元素 326 array.addFirst(-1); 327 System.out.println(array.toString()); 328 329 // 修改index索引位置的元素e 330 array.set(1, 120); 331 System.out.println(array.toString()); 332 333 // 是否包含某个元素 334 boolean contains = array.contains(9); 335 System.out.println(contains); 336 337 // 删除指定索引位置的元素 338 array.remove(2); 339 System.out.println(array); 340 341 // 删除第一个索引位置的元素 342 array.removeFirst(); 343 System.out.println(array); 344 345 // 删除最后一个位置的元素 346 array.removeLast(); 347 System.out.println(array); 348 349 // 从数组中删除元素e 350 array.removeElement(110); 351 System.out.println(array); 352 } 353 354 }
4、数组,就是把数据码成一排进行存放的。数组的最大优点,就是可以快速查询,如果知道了索引,可以根据索引直接获取到元素的值。
5、二次封装属于我们自己的数组,制作属于我们自己的数组类Array,对Java数组的二次开发。
5.1、数组元素的添加,向数组添加元素,最简单的是向数组的末尾添加元素。
将元素一放入到data[0]以后,维护size的大小,size自增一,此时size为1。维护的size的大小,同时也是指数组中有多少个元素。
此时,如果再添加一个元素,将元素二添加到数组中。同理,此时size为1,1这个索引的位置就是数组中第一个没有元素的位置。我们向数组的末尾添加一个元素,就是让data[1]等于我们要添加的元素。维护size的大小,size自增一,此时size为2。
5.2、向数组中的指定位置添加元素。如何将元素77插入到指定的索引为1的位置。
将当前索引为1的这个位置的元素以及索引为1之后的所有元素向后移动一个位置。此时,将索引为1这个位置腾给77这个元素,但是索引为1的位置的元素此时还是88。
具体移动的时候,需要从后向前移动,先把最后一个元素向后移动一个位置,然后依次移动前一个位置的元素。具体移动的时候,是将size-1这个位置的元素,移动到size这个位置上。
然后依次移动,从后向前,依次将元素向后移动一个位置,直到索引为1的时候。
此时将元素88向后移动一个位置,但是此刻需要注意,原来88元素所在的位置上面,元素88还依旧存在,比如这里元素88还依旧存在在索引为1的位置上面。只不过此时可以将你需要插入的元素覆盖掉之前的元素了,因为你已经将元素放到了正确的位置上。
注意:从指定索引的位置以及指定索引的位置的后面,每一个元素都向后移动一个位置,也就是后一个索引位置赋予前一个索引位置的元素,直到移动到索引index这个位置的元素,移动到索引index这个位置以后,相应的就把index这个位置腾出来了,这里需要注意的是,我们说腾出来这个位置,不是说data[index]这个位置没有元素了,是空的,此时data[index]这个位置还是存放的原来的那个元素,只不过现在我可以放心的将这个位置上原来的那个元素给覆盖掉了,因为相应的副本已经正确的放到了这个索引的下一个位置。
最后将元素77放入到索引为1的位置上面,完成元素在指定位置的插入。
最后维护size的大小,size++。
5.3、删除指定位置的元素。删除索引为1的元素,删除掉元素77。
要删除索引为1的元素,那么就要从索引为2的元素开始,将索引为2这个元素移动到索引为1的这个元素位置中。
让索引2位置的这个元素等于索引3位置的这个元素。
让索引3位置的这个元素等于索引4位置的这个元素。依次循环,直到最后一个元素。
此时,已经将元素77从数组中删除掉了,删除任务已经结束了,然后维护size的大小,size--就行了。
注意,数组中的size既表示数组中有多少元素,也表示指向了第一个没有元素的位置。如果此时我们向数组的末尾添加一个元素的话,需要向数组的data[size]这个索引的位置添加元素的。但是,此时将元素77删除以后,data[size]的位置指向了元素100,此时会存在问题吗,其实是不会存在问题的,用户访问数组来说,最多只能访问到data[size -1]这个位置的索引的,如果想使用某一个索引拿到某一个元素,会判断该索引的合法性,size必须大于等于零且小于size的,用户永远看不到data[size]的值是多少的。
创建数组的时候会开辟空间,数组所有的位置都有一个默认值的,具体默认值看数组类型而定的,默认值对用户来说也是不可见的。
如果可以的话,可以将data[size]这个位置置空的。
5.4、数组的动态扩容。动态数组,是区别于静态数组的,静态数组的容量是有限的。
此时,可以新创建一个2倍于原来数组长度的数组。将旧数组的元素依次赋值到新数组的元素。
将旧数组的元素依次赋值到新数组的元素。实际上,是需要进行循环的,循环遍历原数组的所有元素,把这些元素依次赋值到新数组中。
此时,对于整个数组来说,我们是希望新数组newData取代旧数组data的,换句话说,对于现在这个数组newData来说,容量capacity增大了一倍,与此同时,size的值是不变的,只不过新数组newData的这个size后面有了新的空间,可以容纳更多的元素了, 而对于整个旧数组data来说,它本身是一个引用,之前是指向四个容量的数组,现在让它指向了8个容量的数组,就可以了。
此时,类的成员变量data,和新创建的newData这个变量都指向了同样的空间,由于整个过程是封装在一个方法中的,所以对于newData来说这个变量在我们这个方法执行完成以后就失效了,而我们的这个data是整个类的成员变量,和整个类的生命周期是一致的,只要类还在使用,这个data就是有效的,它指向了新的含有二倍于元素组容量大小的新数组。对于原来的只有四个空间的数组,由于没有变量指向它了,Java的垃圾回收机制就将它回收了。此时就完成了扩容操作。
对于原来的只有四个空间的数组,由于没有变量指向它了,Java的垃圾回收机制就将它回收了。此时就完成了扩容操作。
此时就完成了扩容操作。
数组的动态缩容,避免出现数组空间浪费,和数组的动态扩容反过来,自己可以理解一下的。
作者:别先生
博客园:https://www.cnblogs.com/biehongli/
如果您想及时得到个人撰写文章以及著作的消息推送,可以扫描上方二维码,关注个人公众号哦。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
2017-02-26 简易图书管理系统(主要是jsp的练习)
2017-02-26 jsp统计页面访问量和刷访问量的简单使用
2017-02-26 Map集合遍历的四种方式理解和简单使用