前言
数据结构与算法,从基础的数据结构开始回顾,然后一直到算法深入,记录一些算法心得和笔记。知识输出才不容易遗忘,也方便自己随时复习学过的知识,然后每日一总结今天的算法刷题。
提示:以下是本篇文章正文内容。
一、关于数组的误区
刚学习数据结构这门课的同学,问起数组的特点,可能会回答到查找方便,查找时间复杂度为O(1),插入和删除效率低,时间复杂度为O(n)。
乍一听好像没有毛病。其实是有错误的。 数组是一个线性表,内存地址连续,存储同一数据类型。访问一个下标为 i 的数据的值时,其内存地址为 地址= 基地址 + i * type_data 。type_data为 该数组的数据类型的大小,int类型为4 字节。应该说的是数组的随机访问效率高,时间复杂度为 O(1)。如果是查找的话,就连一个排好序的数组,利用二分算法,它的查找时间复杂度也为 O(logn)。
如何解决插入效率低的问题呢? 插入优化:因为插入时,插到数组中间为 i 的下标时,需要移动后面 n- i 位,插入到第一个位置,它的时间复杂度就是O(n)。但是我们插入到最后一个位置时,它的随机访问可以使时间复杂度为O(1)。我们要是每一次插入或删除都移动后面所有位的数据的话,就很影响效率。所以我们要尽量在数组末尾插入。因此:插入一个下标为 i 的值时,可以直接将原来的值放到数组最后,再将该下标位置替换为新值,最后删除最后的元素即可。时间复杂度就为O(1)。
如何解决删除效率低的问题? 删除优化:如果直接删除数组中某一位置元素,需要向前移动后面所有元素的位置,避免造成数组内存的浪费。时间复杂度就是O(n)。但是我们可以不用每一次删除都直接实际删除,可以把将要删除的位置 标为待删 ,每次删除并不是真的删除,只是记录待删的数据,最后在内存不够时,就将多次删除操作集中执行,这样就大大减少了数据多次搬移的消耗。
二、关于链表的误区
链表地址不连续,不支持随机存储,查找效率为O(n),删除或者插入某个结点的时间复杂度为O(1)。 需要说明的是,其实这种说法是不太准确的,或者说是有先决条件的。这个删除或插入的操作是指纯删除或插入,由于单向链表只记录了后继结点的指针,但是删除和插入都还需要前驱指针,所以O(1)的复杂度是忽略了从头到尾查找前驱对查找消耗的。
单向链表:
比如,其中删除操作有两种情况:
- 删除结点中值等于某个给定值 的结点
- 删除给定指针指向的结点
第一种情况:是需要从头到尾查找一遍,比较值的大小的,所以这种情况时间复杂度为O(n). 第二种情况:使前一个指针指向该指针的下一个值,但是需要前驱指针,所以时间复杂度也为O(n)。 插入情况与删除情况相同。
双向链表:
针对单向链表的第二种情况,已经找到要删除的结点,但是我们需要找到该结点的前驱结点,而单链表并不支持直接获取前驱结点,所以,为了找到前驱结点还是要从头到尾开始遍历链表,找到前驱结点。
但是对于双向链表,由于结点保存了前驱和后继的结点的指针,可以直接找到前驱指针,因此针对第二种情况时间复杂度就变为O(1)。因此可以知道,双向链表插入和删除都比单向链表更加灵活高效,还支持双向循环。
而除了插入和删除有优势,对于一个有序链表,双向链表的按值查找效率也要比单链表高。因为可以记录上次查找的位置p,每次查找时,根据要查找的值与p的大小关系,决定向前还是向后查找。所以平均只需要查找一半的数据
链表实现LRU算法
LRU是操作系统的缓存淘汰算法的最近最久未使用淘汰策略。可以使用单向链表实现该算法。 步骤: 1.顺序遍历链表 2.如果该数据已经在链表中,则删除原位置的该节点,然后再将它插入到链表的头部。 3.如果该数据没有在链表中,又分为两种情况: a)如果缓存未满,直接加到链表的头部 b)如果缓存已满,则将链表尾部的结点删掉,将它插入到链表的头部。
总结
以上就是关于数组和链表的一些误区,也能更好地理解这两种最基础的数据结构。很多其他数据结构都是在这两种基础结构中,构建起来的,比如队列、栈、树等结构,都可以用数组或者链表实现。
|