数据结构——链表

链表是一种线性表数据结构,它用一组不需要连续的内存空间

在这里插入图片描述

三种常见的链表结构

单链表、双向链表、循环链表

单链表

链表通过指针将一组零散的内存块串联在一起。其中,我们把内存块称为链表的"结点"。为了将所有的结点串起来,每个链表的结点除了存储数据之外,还需要记录链上的下一个结点地址 next指针。
在这里插入图片描述

  • 头结点用来记录链表的基地址,可以用来遍历整条链表
  • 尾指针:不是指向下一个结点,而是指向一个空地址NULL,表示链表上最后一个结点
  • 因为链表存储空间本身不是连续的,所以插入和删除一个数据比数组快,O(1)。
    在这里插入图片描述
  • 缺点:查找一个元素时间复杂度为O(n),因为链表非连续存储(只记录下一个节点指针),需要根据指针结点依次遍历,知道找到相应的结点。
循环链表
  • 尾指针指向链表的头节点
    在这里插入图片描述
双向链表

在这里插入图片描述

  • 从结构上,双向链表可以在O(1)情况下找到前驱结点,这样是的双向链表比单链表简单高效,空间换时间的设计思想
  • 对于一个有序链表,双向链表的按值查询的效率比单链表高一些。因为,可以记录上次查找的位置p,每次查询可以根据要查找的值与p的大小关系,决定是往前还是往后查找,所以平均只需查找一半的数据
分析链表删除删除操作:

删除的2中情况:

  • 对于删除结点中值等于某个给定值的结点:不管单双链表,尽管单纯的删除操作时间复杂度为O(1),但遍历查找的时间是主要消耗点O(n),根据时间复杂度分析中的加法法则,删除值等于给定值的结点对应的链表操作的总时间复杂度O(n)。
  • 对于删除给定指针指向的结点:
    删除某个结点q需要找到其前驱结点,而单链表就得从头结点开始遍历链表,知道p->nex = q,说明p是q的前驱结点,但对于双向链表O(1)就可以搞定了。
分析链表删除插入操作:
  • 同理,如果在某个结点前面插入一个结点,双向链表可以在O(1)搞定,而单向链表O(n);
空间换时间设计思想
  • 缓冲就是空间换时间,当执行较慢的程序,可以消耗更多的内存来优化;而消耗过多内存的程序,可以通过更多的时间来降低内存消耗
双向循环链表在这里插入图片描述
链表vs数组性能比拼

在这里插入图片描述

  • 数组优点:连续内存空间,可以借助CPU的缓存机制
  • 如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致内存不足。如果声明的数组过小,则可能出现不够用的情况。这时需要申请更大内存空间,把原数组拷进去,非常耗时。链表本身没有大小限制。天然支持动态扩容,这是最大区别。
  • 如果对内存非常苛刻,数组更适合,因为链表中的每个结点都消耗额外的额外的存储空间去存储一份指向下一个结点的指针,所以消耗会翻倍,而且,频繁插入和删除操作会导致频繁的内存申请和释放,容易造成内存碎片。
如何基于链表实现LRU缓冲
  • 目的:缓冲大小有限,当缓冲被用满,就需要缓存淘汰策略来决定数据的清理和保留
  • 思路:维护一个有序的单链表,越靠近链表尾部的结点是越早之前访问的,当有一个新的数据被访问时,从链表头开始顺序遍历链表.
    • 如果次数据之前已经被缓冲到链表中,我们遍历这个数据对应的结点,并将其从原来的位置删除,然后插入到链表头部
    • 如果数据没有在缓冲中:
      • 若缓冲未满,直接将链表插入头部
      • 如缓冲已满,则将链表尾结点删除,将新的数据插到链表头部
  • 优化的思考:m缓冲访问的时间复杂度是多少,因为不管缓冲有没有满,都需要遍历一遍链表O(n),可以继续优化,引入散列表来记录每一个位置,将缓冲的时间复杂度降为O(1)。
链表代码易错

-指针引用的理解:将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,反过来说,指针中存储了这个变量的内存地址,指向这个变量,通过指针就是找到这个变量。
- p-next = q,p结点中的next指针存储了q结点的内存地址
- p->next = p->next->next,p结点next指针存储了p结点的下下一个结点的内存地址

  • 插入操作,插入x 先让x指向下一个结点,再让x前一个结点指向x
  • 删除操作要释放结点对应的内存空间,不然会内存泄漏
  • 利用哨兵简化实现难度,处理边界问题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哈希表是一种高效的数据结构,可以用来存储和查找键值对。其中,哈希函数将键映射到一个特定的桶中,每个桶中存储一组键值对。在哈希表中,如果两个键被映射到同一个桶中,就会发生碰撞。为了解决这个问题,可以使用链表法。 链表法是一种解决哈希表碰撞问题的方法。具体来说,对于哈希表中的每个桶,可以使用一个链表来存储所有映射到该桶的键值对。如果发生碰撞,只需要将新的键值对添加到链表的末尾即可。 下面是一个使用链表法实现哈希表的示例代码: ```python class Node: def __init__(self, key, value): self.key = key self.value = value self.next = None class HashTable: def __init__(self, capacity): self.capacity = capacity self.buckets = [None] * capacity def hash_function(self, key): return hash(key) % self.capacity def put(self, key, value): index = self.hash_function(key) node = self.buckets[index] while node: if node.key == key: node.value = value return node = node.next new_node = Node(key, value) new_node.next = self.buckets[index] self.buckets[index] = new_node def get(self, key): index = self.hash_function(key) node = self.buckets[index] while node: if node.key == key: return node.value node = node.next return None def remove(self, key): index = self.hash_function(key) node = self.buckets[index] prev = None while node: if node.key == key: if prev: prev.next = node.next else: self.buckets[index] = node.next return prev = node node = node.next ``` 在这个示例中,我们定义了一个Node类来表示哈希表中的每个节点,每个节点包含一个键、一个值和一个指向下一个节点的指针。我们还定义了一个HashTable类来实现哈希表,其中包含一个桶数组和一些基本的操作方法,如put、get和remove。 在put方法中,我们首先使用哈希函数计算出键的索引,然后遍历桶中的链表,查找该键是否已经存在于哈希表中。如果找到了该键,我们只需要更新其对应的值即可。否则,我们创建一个新的节点,并将其添加到链表的开头。 在get方法中,我们同样使用哈希函数计算出键的索引,然后遍历桶中的链表,查找该键的值。如果找到了该键,我们返回其对应的值。否则,返回None。 在remove方法中,我们首先使用哈希函数计算出键的索引,然后遍历桶中的链表,查找该键。如果找到了该键,我们将其从链表中删除即可。 总的来说,链表法是一种简单且常用的哈希表解决碰撞问题的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值