《隔离十四天》系列 -第十天 -数据结构与算法

前言

好了,解放了就是爽不用天天测体温了,虽然自己健康的不得了,还能随意出去high,解放的日子真幸福。
今天呢看了一些数据结构的视频,来加深自己对一些数据结构的理解,因为对于一些算法都是依赖与数据结构来实现的,所以重温下数据结构,也为以后自己刷leecode题的时候能事半功倍吧,好了废话不多说。

数组:物理上是连续的顺序的存储结构。

ArrayList底层是数组,对数组进行了封装,成为了一个数组容器。默认大小为10.查询速度快,插入和删除需要移动下标,
所以速度慢。扩容时会扩大0.5倍,然后开辟一个新的数据,将老数组的数据copy到新数组中。

创建一个ArrayList,会执行无参方法,新增元素时会先判断该数组当前的长度,当前的长度不大于10那么会分配一个为10的默认空间,然后进行元素的插入。
若大于10,则需要进行一个扩容,扩容的机制是当前数组大小的0.5倍,扩容之后需要判断数组的长度是否大于了整型包装类的大小,若大于则停止扩容。否则执行元素的新增。

链表:链表物理存储单元上非连续、非顺序的存储结构,l链表由一系列结点组成。链表由两部分组成,一部分是数据元素,一部分是指向下一部分的指针。它是通过指针将一组零散的内存块串联起来使用

单链表:第一个结点为头结点,最后一个为尾结点,头结点记录链表的基地址,尾结点指向一个空地址null。新增结点以及删除结点时直接断掉指针就ok。性能高。但是查询链表中的某个元素时需要遍历链表才能获得指定的元素。
循环链表:在循环链表中尾结点指向的是头结点。
双向链表:双向链表存在前驱以及后驱,双向链表中可以向前找到下一个指向的结点。向钱可以找到前驱结点 存储同样的数据双向链表要比单链表占用更多的空间。 Java中的LinkedHashMap底层是双向链表。
双向循环链表:双向循环链表中尾结点的后驱指向头节点的前驱。

链表和数组的比较:从结构上进行分析两者的区别,
数组在存储结构上是顺序的,数组创建时要设定大小,当大小不够用时需要重新申请更大的空间,然后将数组copy进去。因为数据是顺序的,因此查询的性能更快,但是插入和删除需要移动元素的位置,性能差。基于数组的容器时ArrayList
链表在存储结构上是非顺序的,链表中没有大小的限制,支持动态扩容。链表中的数据存储是零散的,因此在进行插入和删除时是不需要移动元素的,因此查询和删除性能更好,在查询元素时需要遍历到指定的元素才可以,查询性能慢。基于链表的容器时LinkedList.

从源码进行分析链表的数据存储:在静态内部类中使用Node来定义结点,该内部类中存在前驱结点和后继结点,因此在LinkedList中是双向链表,当添加新节点时,前一个结点的后继作为新节点的前驱,新节点的后继结点为null。
先定义一个LinkedList,然后执行add方法,在源码中通过linkLast,在这个方法中去新增结点,前结点的后继是新节点的前驱,新结点的后继是null。头结点的前驱也指向null。

获取LinkedList分析:通过checkElementIndex,查看索引是否在【0,size】的范围内,size是容器元素的个数。同时判断index和size的关系,如果索引小于集合长度的一半,则从头结点开始向后查找,若索引大于集合长度的一般,则从薇姐带你开始向前查找。类似于折半查找。

双向链表中可以从前向后查找也可以从后向前查找,这样简短了查询的时间,通过这般查找的思想来确定从哪边开始进行查找。

ArrayList和LinkedList比较:

1、ArrayList的实现基于数组,LinkedList的实现基于双向链表。
2、对于随机访问来说,ArrayList由于LinkedList,原因是因为数组采用的是顺序的存储,通过索引可以快速找到元素,
LinkedList是随机访问的。
3、对于插入和删除的操作,LinkedList由于ArrayList,因为当前元素添加到LinkedList任意位置的时候不需要移动所以
以及计算大小。
4、LinkedList比ArrayList更占用内存,这时因为LinkedList的结点除了存储元素还会存储前驱和后继两个元素。

单链表的反转:如果两个结点 的链表进行反转,则思想为,将2的next结点暂存到temp中,然后将2的next设置为1,1的next设置为temp。
定义前指针结点
当前指针结点

ListNode prev = null;
ListNode curr = head;
while(curr!=null){
    //开辟一个临时结点,暂存当前结点的下一个结点。用于后移。
    ListNode nextTemp = curr.next;
    //当前结点的next指向前面的结点
    curr.next = prev;
    prev = curr;
    curr = nextTemp;
}
return prev;
    

栈:栈典型的特点是先进后出,栈存在栈底和栈顶,只允许在栈的一端进行数据的插入和删除,这两种操作叫做入栈(push)和出栈(poll )。
栈的实现:有两种形式分别是数组的顺序栈、基于链表的链式栈。

栈的应用:分析Stack的源码,向Stack中添加元素,Stack的底层是数组的顺序栈,当我们存的数据大于栈的容量是,需要进行扩容,扩容的容量是原来容量的两倍,需要进行数据的搬移。
Stack的入栈操作是线程安全的,Stack的元素是存放在数组中的,Stack的底层数据还用到父类Vector,所以数据的初始大小为10.

队列:队列的特点是先进先出,有队尾和 对头,从队尾进,从对头出,所以只能从一端进行入队和出队。循环队列,阻塞队列,并发队列。

队列的实现:
顺序队列,基于数组实现的,定义一个head指针和tail指针

/**
  • @author Administrator
    */
    public class ArrayQueue {

private Object[] elements;
//定义队列的大小
private int size;
//定义队列的初始容量
private int default_size = 10;
//定义队列的头指针
private int head;
//定义一个尾指针

private int tail;
//定义一个容量最大值
private int MAX_ARRAY_SIZE = Integer.MAX_VALUE-8;
//定义一个默认初始化长度为10的队列
public ArrayQueue(){
elements = new Object[default_size];
//初始化首尾指针
this.initPoint(0,0);
}


private void initPoint(int head,int tail){
this.head = head;
this.tail = tail;
}
public ArrayQueue(int capcity){
if(capcity<=0){
throw new RuntimeException("队列创建有问题");
}
this.initPoint(0,0);
}


public boolean enqueue(Object element){
//校验我们的队列容量是否够用
ensureSize();
elements[tail++] = element;
size++;
return true;
}
public Object dequeue(){
if (head==tail){
return null;
}
Object object = elements[head++];
size--;
return object;

}
//判断队列容量是否够用
public void ensureSize(){
//尾指针已经越过尾部
if (tail==elements.length){
if(size<elements.length){
if (head==0){
//扩容操作
}else {
//进行数据的搬移
for (int i = head; i < tail; i++) {
elements[i-head] = elements[i];
}
initPoint(0,tail-head);
}
}
}
}


//扩容
public void grow(int oldSize){
int newSize = oldSize+oldSize>>1;//扩容1.5倍
if(newSize-oldSize<0){
newSize = default_size;
}
if (newSize - MAX_ARRAY_SIZE>0){
capcityFinal(newSize);
}

}


private int capcityFinal(int newSize){
return (newSize > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE - 8 : newSize;
}
}

以上就是自己在看视频的时候做的一些整理,不全面主要是做一个记忆加深,有错误的地方也希望给予指正,谢谢!

最近一直面试找工作,一直没有合适的,没有公司约面试,现在只有一个外包腾讯的工作,目前自己也犹豫,所以还是希望了解外包腾讯的工作的大佬能给与一些建议。万分感谢。

发布了92 篇原创文章 · 获赞 7 · 访问量 7538

猜你喜欢

转载自blog.csdn.net/qq_40126996/article/details/104437456