一、ArrayList与顺序表
1.1线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
链表
1.2 顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
1.3 ArrayList
在集合框架中,ArrayList是一个普通的类,实现了List接口
注意:
- ArrayList是以泛型方式实现的,使用时必须要先实例化
- ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList。
- ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
扩容
检测是否真正需要扩容,如果是调用grow准备扩容
预估需要库容的大小
初步预估按照1.5倍大小扩容
如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容
真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
使用copyOf进行扩容
1.4 ArrayList的缺陷
由于 ArrayList底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。
1.5 顺序表遍历操作
参考代码
/**
* 遍历顺序表中的元素
*/
@Override
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i]+" ");
}
System.out.println();
}
1.6 顺序表增加操作
参考代码:
/**
* 新增数组,默认在数组最后新增
* @param data
*/
@Override
public void add(int data) {
//判断是否为满
checkCapacity();
this.elem[this.usedSize] = data;
this.usedSize++;
}
//只为当前类服务,用private
private void checkCapacity(){
if(isFull()){
//扩容
elem = Arrays.copyOf(elem,usedSize*2);
}
}
@Override
public boolean isFull() {
/* if (usedSize == elem.length){
return true;
}else {
return false;
}*/
return usedSize == elem.length;
}
1.7 顺序表在指定位置插入操作
参考代码:
/**
* 检查pos的合法性
* @param pos
*/
public void checkPosOnAdd(int pos) throws PosIllegality {
if (pos < 0 || pos > usedSize){
System.out.println("不合法!");
throw new PosIllegality("插入元素下标异常: "+pos);
}
}
@Override
public void add(int pos, int data) {
try {
checkPosOnAdd(pos);
}catch (PosIllegality e){
e.printStackTrace();
return;
}
checkCapacity();
//从最后一个数据开始向后移动, //当i<pos就结束
for (int i = usedSize-1; pos <= i ; i--) {
this.elem[i+1] = this.elem[i];
}
this.elem[pos] = data;
//存放元素到pos位置
//usedSize++
usedSize++;
}
1.8 顺序表是否包含目标值、获取指定下标的数值
参考代码:
@Override
public boolean contains(int toFind) {
if (isEmpty()){
return false;
}
for (int i = 0; i < usedSize; i++) {
//如果查找引用类型,重写equals
if (this.elem[i] == toFind){
return true;
}
}
return false;
}
public boolean isEmpty(){
return usedSize == 0;
}
@Override
public int indexOf(int toFind) {
if (isEmpty()){
return -1;
}
for (int i = 0; i < usedSize; i++) {
//如果查找引用类型,重写equals
if (this.elem[i] == toFind){
return i;
}
}
return -1;
}
@Override
public int get(int pos) throws MyArrayListEmpty {
checkPosOnGetAndSet(pos);
if (isEmpty()){
throw new MyArrayListEmpty("获取指定下标元素时"+"顺序表为空!");
}
return elem[pos];
}
/**
* 检查pos的合法性
* @param pos
*/
public void checkPosOnGetAndSet(int pos) throws PosIllegality {
if (pos < 0 || pos >= usedSize){
System.out.println("不合法!");
throw new PosIllegality("获取元素下标异常: "+pos);
}
}
@Override
public void set(int pos, int value) {
checkPosOnGetAndSet(pos);
this.elem[pos] = value;
}
@Override
public void remove(int toRemove) {
int index = indexOf(toRemove);
if (index == -1){
System.out.println("没有这个数字");
return;
}
for (int i = index; i < usedSize-1 ; i++) {
this.elem[i] = this.elem[i+1];
}
usedSize--;
}
@Override
public int size() {
return this.usedSize;
}
@Override
public void clear() {
this.usedSize =0;
}
}
二、链表
2.1 链表的概念及结构
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。
注意
- 链式结构在逻辑上是连续的,但是在物理上不一定连续
- 现实中的结点一般都是从堆上申请出来的
- 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
2.2 链表的种类
单向 带头 循环
单向 带头 非循环
单向 不带头 循环
单向 不带头 非循环
双向 带头 循环
双向 带头 非循环
双向 不带头 循环
双向 不带头 非循环
2.3 链表的插入(前插法、尾插法)
前插法参考代码
//前插法
@Override
public void addFirst(int data) {
ListNode node = new ListNode(data); //实例化一个新节点
if(this.head == null){
this.head = node;
}else{
node.next = this.head; //改变插入节点的next
this.head = node; //改变head
}
}
尾插法参考代码
/**
* 如果想让cur停在最后一个节点,cur.next != null
* 如果想把整个链表的节点都遍历完 :cur !=null
* 头插法的时间复杂度为O(1)
* 尾插法的时间复杂度为O(n)
* @param data
*/
@Override
public void addLast(int data) {
//实例化一个新节点
ListNode node = new ListNode(data);
ListNode cur = this.head;
if(this.head == null){
this.head = node;
}else {
// 当head为空时,遍历结束,找到最后一个节点
while (cur.next != null) {
cur = cur.next; //cur从当前节点位置,走到下一个节点的位置
}
cur.next = node;
}
}
2.4 链表节点的删除
/**
* 找到关键字的前一个节点的地址
* @param key
* @return
*/
private ListNode findSearch(int key){
ListNode cur = this.head;
while (cur.next != null) {
if (cur.next.val == key) {
return cur;
}
cur = cur.next;
}
return null;
}
@Override
public void remove(int key) {
if (this.head == null){
return;
}
if (this.head.val == key){
this.head = this.head.next;
return;
}
//找到前驱
ListNode cur = findSearch(key);
if (cur == null ){
System.out.println("没有您要删除的数字");
return;
}
ListNode del = cur.next;
cur.next = del.next;
}
2.5 删除所有目标关键字的节点
参考代码
public void removeAllKey(int key) {
if (this.head == null){
return;
}
ListNode prev = this.head; //头节点没有判断
ListNode cur = this.head.next;
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;
}
}
2.6 链表的反转
参考代码
public ListNode reverseList(){
//判断链表是否为空
if(head == null){
return null;
}
//判断是否为单链表
if(head.next == null){
return head;
}
ListNode cur = head.next;
head.next = null;
while(cur != null){
ListNode curNext = cur.next;
cur.next = head;
head = cur;
cur = curNext;
}
return head;
}
三、总结
ArrayList和LinkedList的区别: