【DS】3.顺序表链表万字全总结!
文章目录
- 一、关于List
- 为什么要提List
- 什么是List
- List怎么用
- 二、关于线性表
- 三、关于顺序表
- 什么是顺序表
- 顺序表我们常用的功能:
- ArrayList的简化模拟实现:
- 四、ArrayList介绍及常用方法使用
- 什么是ArrayList
- 关于ArrayList源码:
- 常用方法使用注意事项:
- ArrayList的遍历:
- 关于迭代器
- 五、ArrayList扩容机制详解
- 六、关于链表
- 七、LinkedList介绍以及常用方法使用
- 八、ArrayList与LinkedList对比
Java的集合类一般都在util包下
一、关于List
为什么要提List
有人可能会问,你不是要总结顺序表和链表吗?提List干什么?这是因为顺序表和链表背后的集合ArrayList和LinkedList都是实现了List的,我们要想学明白ArrayList和LinkedList,必须对List有一定的了解。
什么是List
站在数据结构的角度来看,List就是一个线性表,是n个具有相同类型元素的有限序列,在该序列上进行增删查改以及变量等操作。
从Java内置的集合框架角度看,List是一个接口,List继承于Collection,Collection继承于Iterable。
他规定了后边容器的一些常见的方法,具体有【了解即可】
List怎么用
由于List是一个接口,所以不能直接用来实例化。
我们一般是创建一个实现它的类的对象(比如说ArrayList或者LinkedList的)然后用List类型的引用接收。
二、关于线性表
定义:线性表就是n个具有相同特性的数据元素的有限序列。
两种存储结构:线性存储、链式存储
要点:逻辑一定相邻,物理不一定相邻。
原因:顺序表是用数组实现的、物理上一定连续,链表是用内部类实现的,物理上不一定连续。
常见的线性表:顺序表、链表、栈、队列
三、关于顺序表
什么是顺序表
定义:顺序表是使用一段物理地址连续的存储单元存储数据元素的线性结构。
关于顺序表和数组关系的理解:顺序表实现方式就是数组,但是可以将顺序表理解成一种功能性的数据结构,是带增删查改等常用操作的,而数组只是一种单纯的数据类型。
**关于顺序表和ArrayList的理解:**顺序表是一个逻辑概念(功能性的数据结构),而ArrayList是一个集合类,是具体实现它逻辑的代码集合。
顺序表我们常用的功能:
- 打印顺序表【show/display】
- 新增元素,默认在末尾【增】
- 在pos位置新增元素【增】
- 判断是不是包含某个元素【查】
- 查找某个元素对应的位置【查】
- 获取pos位置的元素【查】
- 给pos位置的元素设置成为val【改】
- 删除第一次出现的关键字key【删】
- 获取顺序表的长度【查】
- 清空顺序表【删】
ArrayList的简化模拟实现:
我们已经知道,顺序表背后的集合是ArrayList,所以我们在这里自己模拟实现顺序表的功能名字,可以写成MyArrayList。
public class MyArrayList {//首先我们的顺序表必须要有一个数组//其次需要有usedSize,记录有效的数据个数//再然后我们需要给一个构造方法,一般传数组大小,所以我们的构造方法里弄大小//这些变量我们最好设置成为privateprivate int[] elem;private int usedSize;public MyArrayList(int size){elem= new int[size];}//打印函数public void display() {for (int i = 0; i < usedSize; i++) {System.out.print(this.elem[i]+" ");}System.out.println();}//尾插元素public void add(int data) {//1.满了if(isFull()){this.elem=Arrays.copyOf(this.elem,2*elem.length);}//2.没满this.elem[usedSize++]=data;}//判满public boolean isFull() {return this.elem.length<=usedSize;}//指定位置插入public void add(int pos, int data) {//插入数据需要挪动数据//1.坐标不合法【负数/间隔/超过数组长度】if(pos<0||pos>usedSize){throw new PosWrongfulException("坐标位置不合法,插入失败!");//自定义类不再展示}//2.如果满了,扩容if(isFull()){this.elem=Arrays.copyOf(this.elem,2*elem.length);}//3.位置合法for (int i = usedSize; i >pos ; i--) {elem[i]=elem[i-1];}elem[pos]=data;usedSize++;}// 判定是否包含某个元素public boolean contains(int toFind) {//遍历看等不等就可以了,判不判都无所谓for (int i = 0; i < usedSize; i++) {if(toFind==this.elem[i]){return true;}}//如果是引用类型,比较内容用equalsreturn false;}// 查找某个元素对应的位置public int indexOf(int toFind) {//直接遍历for (int i = 0; i < usedSize; i++) {if(this.elem[i]==toFind){return i;}}return -1;}// 获取 pos 位置的元素public int get(int pos) {//没有判空//1.位置不合法if(pos<0||pos>=usedSize){throw new PosWrongfulException("坐标位置不合法");}//2.判断是不是空的if(isEmpty()){throw new EmptyException("当前顺序表为空!");}//3.合法,返回对应的值return this.elem[pos];}//判空public boolean isEmpty() {return usedSize==0;}// 给 pos 位置的元素 更新 为 valuepublic void set(int pos, int value) {//1.位置不合法if(pos<0||pos>=usedSize){throw new PosWrongfulException("坐标位置不合法,插入失败!");}//2.空了,没有更新这一说法if(isEmpty()){throw new EmptyException("当前顺序表为空!");}//3.正常更新this.elem[pos]=value;}//删除第一次出现的关键字keypublic void remove(int key) {//1.原来为空需要考虑,因为第二个循环可能会执行if(isEmpty()){//最好直接抛异常throw new EmptyException("当前顺序表为空!");//自定义的异常类,此处没有贴代码(很简单)}//2.正常找到,并且删除int i=indexOf(key);if(i==-1){System.out.println("没有这个数字!");return ;}for (int j = i; j <usedSize-1 ; j++) {this.elem[j]=this.elem[j+1];;}usedSize--;}// 获取顺序表长度public int size() {//不需要再进行遍历了,直接返回usedSize即可return usedSize;}// 清空顺序表public void clear() {this.usedSize=0;//如果是引用类型需要一个一个进行置空}
}
四、ArrayList介绍及常用方法使用
什么是ArrayList
**定义:**是一个实现了List接口的普通类。
相关的继承关系和实现的接口:继承于AbstractList类,实现了List接口、Cloneable接口、Serializable接口、RandomAcess接口(自然也实现了List,但是这里主要是为了介绍后边的功能铺垫)。
**底层存储:**底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表。
一些拔高点的理解:支持随机访问是因为实现了RandomAcess接口;支持clone是因为实现了Cloneable接口;支持序列化是因为Serializable接口;不是线程安全的,在单线程下可以使用,但在多线程中一般使用Vector或者CopyOnWriteArrayList。
关于ArrayList源码:
源码中的函数这么多,我们并不是每个都需要学习和实现的,只需要熟练掌握以下几个就可以了,其他的再复习的时候或者感兴趣的时候可以自行阅读。
常用方法使用注意事项:
1.构造方法&增加函数
构造方法有三种:无参构造【较多】、利用其他接口构建ArrayList、指定顺序表初始化容量——传进去一个长度【较多】
无参构造源码:**当我们调用不带参数的构造方法时,只有第一个add的时候,我们才会分配默认大小为10的内存。**采用的1.5倍扩容(add)。
toString方法可以直接打印,因为Comparable接口重写了相应的方法。
注意:泛型实参不要省略,不然什么样的元素都可以存,那么取得时候非常不方便
public class TestArrayList {public static void main(String[] args) {// ArrayList创建,推荐写法 // 方法一:构造一个空的列表 List<Integer> list1 = new ArrayList<>();// 方法二:构造一个具有10个容量的列表List<Integer> list2 = new ArrayList<>(10); list2.add(1); list2.add(2);list2.add(3);//list2.add("hello"); // 编译失败,List<Integer>已经限定了,list2中只能存储整形元素//方法三: list3构造好之后,与list中的元素一致 ArrayList<Integer> list3 = new ArrayList<>(list2);//list2存的类型必须是list3存元素的子类 // 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难 // 禁止!!!! List list4 = new ArrayList(); list4.add("111"); list4.add(100);list4.addAll(list3);}
}
2.删除组remove/removeAll
remove方法中,参数只要是int类型都被当成了索引,所以如果需要删除指定key值的数字,就需要new对象/装箱。
但是很少这样用,因为我们操作对象一般是引用类型,也就不存在这样的问题。
3.子表subList
本质上并不是拷贝出来了,而是拿到了对应范围的地址。所以对sublist更改也会改变原来的值
左闭右开的形式,返回值是List类型的。
4.add方法
通过源码我们可以发现,其实有一个默认变量存储的是10,但其实一开始创建的数组大小是0,那么这时又怎么能够向里边元素呢?
简而言之,就是首次add时,进行了扩容。
【其他的暂时没有发现,以后可能会进行补充。】
ArrayList的遍历:
4种方法:
- foreach
- fori
- Iterator 【迭代器方法】
- sout【Comparable重写了toString方法,List继承了它,ArrayList实现了这个类,println可以直接打印】【简单,没有演示】
public static void main(String[] args) {ArrayList<Integer>arrayList=new ArrayList<>();arrayList.add(1);arrayList.add(12);arrayList.add(13);arrayList.add(14);arrayList.add(15);arrayList.add(16);int size=arrayList.size();for (int x:arrayList) {System.out.print (x+" ");}System.out.println();
}
public static void main1(String[] args) {ArrayList<Integer>arrayList=new ArrayList<>();arrayList.add(1);arrayList.add(12);arrayList.add(13);arrayList.add(14);arrayList.add(15);arrayList.add(16);int size=arrayList.size();for (int i = 0; i < size; i++) {System.out.print (arrayList.get(i)+" ");}System.out.println();
}
public static void main2(String[] args) {ArrayList<Integer>arrayList=new ArrayList<>();arrayList.add(1);arrayList.add(12);arrayList.add(13);arrayList.add(14);arrayList.add(15);arrayList.add(16);int size=arrayList.size();Iterator<Integer> it=arrayList.iterator();while(it.hasNext()){//一定是实现了Iteralbe接口才可以System.out.print(it.next()+" ");//打印它的同时,it往后走一步}System.out.println();
}
关于迭代器
Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList 和 HashSet 等集合。
迭代器 it 的基本操作是 next 、hasNext 和 remove。
调用 it.next() 会返回迭代器的下一个元素,并且更新迭代器的状态。
调用 it.hasNext() 用于检测集合中是否还有元素。
调用 it.remove() 将迭代器返回的元素删除。
具体介绍:Java迭代器介绍
五、ArrayList扩容机制详解
扩容的步骤
检查是不是真正需要扩容,如果是准备用grow函数扩容
预估需要库容的大小
正常1.5倍,如果超过1.5倍那么按照用户所需大小扩容
真正扩容前进行检测是不是能扩容成功,防止太大导致扩容失败
使用copyOf开始工作了
通过看扩容的源码,我们可以发现,它是会默认扩容为1.5倍大小的,优点是可以完成功能,缺点是在我们只再需要一个的时候,它反而扩了1.5倍大小,这时其实是有点浪费空间的。
六、关于链表
**定义:链表是一种物理存储结构上非连续存储结构,**数据元素的逻辑顺序是通过链表的引用链接起来的。
**要点:**链式结构在逻辑上是连续的,但是物理结构不一定连续。
为什么是不一定,而不是不?
现实中的节点一般都是从堆上申请出来的,从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间有可能是连续的,也可能不随时连续的。
链表的分类:
按照方向的个数:单向、双向
按照循不循环分:循环、非循环
按照带不带头分:带头节点、不带头节点
他们进行排列组合,也就是2^3=8种
关于带头不带头:
带不带头只是方便了进行增删查改的工作,所以我们在画图的时候就不再单独画出这种情况,所以这时我们就看4种情况下的图示即可。
为什么带头结点的比较简单呢?这是因为,带头的话,我们不需要再单独考虑头结点增删改的情况,特殊情况的分析又少了一种,会更加方便。
关于链表重点:
- 无头的单向的非循环链表:笔试高频,但是一般不会单独用来存储数据,实际上更多的是作为其他数据结构的子结构。
- 无头双向链表:java的集合框架库LinkedList底层实现就是无头双向链表。
关于LinkedList的三种使用方法:
- 1.作为双向链表使用
- 2.作为队列的对象
- 3.作为双端队列使用
接下来,我们也简单实现一下单链表的功能
//无头单向非循环链表实现
public class MySingleLinkedList {//节点和构造方法class ListNode{int val;ListNode next;public ListNode(int val){this.val=val;}public ListNode(){;}}public ListNode head;//一个引用,具有实际意义//头插public void addFirst(int data){ListNode node=new ListNode(data);node.next=this.head;head=node;}//尾插法public void addLast(int data){ListNode node=new ListNode(data);if(head==null){head=node;return ;//一定记得结束这个过程}ListNode cur=this.head;while(cur.next!=null){cur=cur.next;}cur.next=node;}//任意位置插入,第一个数据节点为0号下标public boolean addIndex(int index,int data){if(index<0||index>size()){System.out.println("位置坐标不合法!");return false;}if(index==0){addFirst(data);return true;}if(index==size()){addLast(data);return true;}ListNode node=new ListNode(data);if(head==null){head=node;}ListNode prev=this.head;ListNode cur=head.next;while(index-1!=0){prev=cur;//先从前向后绑定cur=cur.next;index--;}node.next=cur;prev.next=node;return true;}//查找是否包含关键字key是否在单链表当中public boolean contains(int key){ListNode cur=this.head;while(cur!=null){if(cur.val==key){return true;}cur=cur.next;}return false;}//删除第一次出现关键字为key的节点public void remove(int key){if(head==null){return ;}if(head.val==key){head=head.next;return ;}ListNode cur=this.head;while(cur.next!=null) {if (cur.next.val == key) {cur.next = cur.next.next;return;} else {cur = cur.next;}}}//删除所有值为key的节点public void removeAllKey(int key){if(head==null){return ;}if(head.next==null){if(head.next.val==key){head.next=null;}return ;}ListNode cur=this.head.next;ListNode prev=head;while(cur!=null){if(cur.val==key){prev.next=cur.next;cur=cur.next;}else{prev=cur;cur=cur.next;}}if(head.val==key){head=head.next;}}//得到单链表的长度public int size(){int count=0;ListNode cur=this.head;while(cur!=null){count++;cur=cur.next;}return count;}public void display(){ListNode cur=this.head;while(cur!=null){System.out.print(cur.val+" ");cur=cur.next;}System.out.println();}public void clear(){//引用类型需要单独置空ListNode cur=this.head;while(cur!=null){ListNode curNext=cur.next;cur=null;cur=curNext;}}
}
七、LinkedList介绍以及常用方法使用
同ArrayList,LinkedList也是一个实现了List接口的类.顶层是一个无头双向链表
Java中LinkedList的使用方法
不需要刻意的去记忆,可以在碰到题目的时候去查。
八、ArrayList与LinkedList对比
这也算是一个高频的面试题。
一般有以及几种问法
- 数组和链表的区别是什么?
- 顺序表和链表的区别是什么?
- ArrayList和LinkedList区别是什么?
不同点 | ArrayList | LinkedLst |
---|---|---|
内存上 | 逻辑物理存储连续 | 逻辑连续,空间不一定 |
随机存储 | 支持,通过下标索引 | 不支持 |
头增头删 | 需要移动元素,时间复杂度为O(N) | 只需要修改有限指向,时间复杂度为O(1) |
扩容/容量问题 | 容量不够时需要扩容 | 没有容量概念 |
应用场景 | 元素需要频繁随机访问 | 元素频繁进行插入删除操作 |
上边是我们的逻辑框架,我们在组织语言的时候,可以从共性出发。
例如:
首先,在增删查改方面,查找元素或者改变某个元素的值的时候,顺序表支持随机访问,时间复杂度为O(1),而链表必须遍历整个链表,时间复杂度为O(N);在头插入元素和头删除元素时,顺序表必须移动元素,时间复杂度为O(N),链表只需要改变有限个指向,时间复杂度为O(1);其次,在存储方面,顺序表使用数组实现的,逻辑和物理上都是连续的,而链表是散落的节点,通过引用链接到一起的,逻辑上连续,但是物理不一定连续。
所以,我们一般顺序表应用于需要频繁随机访问和高效存储的场景下,而链表应用于元素需要频繁进行插入删除操作的场景下。
【DS】3.顺序表链表万字全总结!相关推荐
- 线性表→顺序表→链表 逐个击破
一. 线性表 1. 前言 线性表,全名为线性存储结构.使用线性表存储数据的方式可以这样理解,即 " 把所有(一对一逻辑关系的)数据用一根线儿串起来,再存储到物理空间中 ".这根线有 ...
- 线性表---顺序表链表
一.线性表 1.线性表中的元素是一对一的关系,除了第一个与最后一个元素之外其他数据元素都是首尾相连的. 如果是一对多就用树来表示,如果是多对多就用网状来表示. 2.线性表的两种存储结构 顺序表:用顺序 ...
- 约瑟夫环问题2(顺序表+链表求解)
1.背景知识 * 古代判官要判决number个罪犯的死刑,制定了一条荒谬的法律 * 将犯人站成一个圈,从start开始数起,每数到第distance * 个就处决他,依照此规律直到最后剩下一个人赦免. ...
- 数据结构顺序表的查找_数据结构1|顺序表+链表
数据结构学习笔记1 进度:静态分配顺序表+单链表 参考资料:b站 王道考研+小甲鱼 < 判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注最高项目.的阶数. 推导大O阶方法 ...
- 《数据结构》实验报告二:顺序表 链表
一.实验目的 1.掌握线性表中元素的前驱.后续的概念. 2.掌握顺序表与链表的建立.插入元素.删除表中某元素的算法. 3.对线性表相应算法的时间复杂度进行分析. 4.理解顺序表.链表数据结构的特点(优 ...
- 2.1顺序表(链表)
2-4 ChainList.h 1 #include <stdlib.h> 2 typedef struct Node 3 { 4 DATA data; 5 struct Node *ne ...
- 复习:顺序表——链表
链表概念 链表:链式存储的线性表,简称链表.链表由多个链表元素组成,这些元素称为节点.结点之间通过逻辑连接,形成链式存储结构.存储结点的内存单元,可以是连续的也可以是不连续的.逻辑连接与物理存储次序没 ...
- 顺序表链表 LeetCode专项练习 [19]\[24]
题目列表: 19.删除链表的倒数第N个节点 24.两两交换链表中的节点 19. 删除链表的倒数第N个节点 删除链表的倒数第N个节点 给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点. ...
- Java数据结构(1.1):数据结构入门+线性表、算法时间复杂度与空间复杂度、线性表、顺序表、单双链表实现、Java线性表、栈、队列、Java栈与队列。
数据结构与算法入门 问题1:为什么要学习数据结构 如果说学习语文的最终目的是写小说的话,那么能不能在识字.组词.造句后就直接写小说了,肯定是不行的, 中间还有一个必经的阶段:就是写作 ...
最新文章
- websocket 代理tcp_netty实现websocket请求实战
- [zz]HDFS文件操作
- 【PAT甲级 删除字符串中重复字母】1084 Broken Keyboard (20 分) Java 全部AC
- mysql 查看端口_新手连接MySQL数据库,再也不怕连不上了
- linux的mount(挂载)命令详解(转)
- C#值类型以及默认值记录下
- Android下图片处理的的一些方法
- Android 系统自带的图标
- phabricator安装配置和使用(docker安装和独立部署)
- 文件操作Python
- Kubernetes详解(十六)——Pod容器探测
- 《纽约邮报》手机客户端推送系统被黑 黑客发送抒情诗
- 多项目公共代码库该如何管理
- linux php adodb,【原创】Linux下php使用adodb对sql Server访问配置
- nssa和stub_实验4 OSPF的特殊区域STUB和NSSA
- Java核心技术大会|Java应用开发专场
- 使用OneDNS完美实现Chorme自动同步书签和插件
- Postek博思得打印机
- android 加花工具下载,Android 代码混淆并加花
- QQ三国华容道拼图脚本(半成品,成功率不高)
热门文章
- 搜狗王小川说输入法的未来是自动问答,那么这个技术究竟发展得怎么样了?...
- 人人都是产品经理2.0-03章摘要总结
- win10打不开设置,弹出来找不到应用程序
- 【矩阵论】Hermite二次型(2)
- c++ 二维矩阵 转vector_C++ vector 实现二维数组
- 女生学UI设计的一些优势,转行者必看!
- linux shell xcopy file by date,Xcopy命令参数使用介绍
- MRI:T1 T2 T1WI T2WI笔记
- opengl对三种光源(方向光,点光源,聚光灯)进行特写并分屏渲染
- 建筑工程竣工验收流程、程序、必备条件及备案,太全了