数据结构与算法之美【总结笔记】 -- 链表

链表与数组是想对的一种数据结构

链表的特点为:内存不连续,每个节点存有下一节点的地址(双向链表还需要存储前一节点的地址),以头结点的地址作为链表的基地址,尾节点指向空地址。

延伸:尾节点指向头结点,形成循环链表。每个节点存储前一结点的地址,形成双向链表。每个节点存储前一结点的地址,尾结点指向头结点,形成循环双向链表。

因为每个节点都需要额外的空间存储下一节点的地址,所有链表所占的空间更大,但由于节点间通过存储地址连接,所有链表中节点的地址不连续,不需要一整块内存来服务。同时,由于是指针连接,链表的插入和删除操作不需要对其他数据进行转移,时间复杂度为O(1),但是弊端在于,不是连续内存,所以不支持寻址公式进行随机访问,只能从头结点以此遍历来寻找目标节点,时间复杂度为O(n)。

 

数据结构与算法之美【总结笔记】 -- 链表_第1张图片

双向链表与单链表比较

问题为“删除定指针指向的节点”,如果是单链表,只能从头结点依次遍历寻找到该节点后再进行操作;如果是双向连败,则可以直接获取前节点和后节点,直接进行删除操作。

对于有序链表进行查询,可以记录下上一次查询的位置P,下一次查询是与P所在节点比较,来确定是往前找还是往后找。

双向链表在某些功能上比单链表要更好用,但是其占的内存也更多,典型以空间换时间。

 

链表与数组的比拼

可能大多数人第一反应是,删除和插入所需时间不同,但是,抛开实际应用做对比就是耍流氓,确实链表的插入和删除很快,都是O(1),但是,实际应用是,如果给你一个链表,和一个节点,让你在某节点之后插入此节点,在执行插入之前,还要先执行查询操作啊,查到目标位置之后,在进行插入,而这个查询的操作,时间复杂度就是O(n),所以,在普通情况下,数组和链表的插入和删除操作,时间复杂度都是O(n)。

正确的比较方式和选择要求应该着眼于他们的另一大不同点,内存。

数组的内存要求是,在声明的时候就先去申请一块指定大小的内存,如果剩余内存不够大则会申请失败报错,然后当数据存满了的时候,需要重新申请内存,然后进行数据转移。有点为,内存连续,便于cup的缓存,可以随机访问,操作简单。

链表的内存要求是,在声明时并不会申请太多内存,增加节点的时候,也只会增加该节点占用的内存,而且内存可以不连续,只要有空就可以,并且链表会自动扩容。但是他的缺点也很明显,需要多余的内存来存储指针,内存消耗翻倍,如果出现频繁的插入删除操作则会引起频繁的内存申请和释放,容易产生内存碎片。

 

ps:

链表的反转

1.声明一个新链表,循环目标链表存入新链表中

时间复杂度为O(n),空间复杂度为O(n)

2.将头结点作为基准,将其后面的第一个节点转成头结点,以此进行

时间复杂度为O(n),空间复杂度为O(1)

public LinkedListNew replace(LinkedListNew linkedListNew){
        LinkedNode firstNode = linkedListNew.first;
        while (firstNode.getNextNode() != null){
            LinkedNode toolNode = firstNode.getNextNode();
            firstNode.setNextNode(toolNode.getNextNode());
            toolNode.setNextNode(linkedListNew.first);
            linkedListNew.first = toolNode;
        }
        return linkedListNew;
    }

3.将尾结点前的第一个节点转移成尾结点,依次进行

时间复杂度为O(n),空间复杂度为O(1)

 

 

 

你可能感兴趣的:(数据结构与算法)