链结人生:探索线性链表的奥秘

简介: 线性链表(Linked List)是一种常见且重要的数据结构,用于在计算机科学和编程中管理和组织数据。它提供了一种灵活的方式来存储一系列元素,而不需要在内存中分配一块连续的存储空间。在本文中,我们将深入探讨线性链表的概念、特点、操作以及应用,并通过实例演示它在解决问题中的作用。

线性链表(Linked List)是一种常见且重要的数据结构,用于在计算机科学和编程中管理和组织数据。它提供了一种灵活的方式来存储一系列元素,而不需要在内存中分配一块连续的存储空间。在本文中,我们将深入探讨线性链表的概念、特点、操作以及应用,并通过实例演示它在解决问题中的作用。


概念与特点

线性链表是由一系列节点(Node)组成的数据结构,每个节点包含两个要素:数据(通常是一个值)和指向下一个节点的引用。与数组不同,链表的节点可以在内存中随意分散,它们通过引用相互链接。


链表分为单向链表和双向链表两种类型,它们在引用方式上略有不同:


单向链表

单向链表的每个节点只有一个指向下一个节点的引用。每个节点包含两个主要部分:存储数据的元素以及指向下一个节点的指针。单向链表的最后一个节点指向空值(null),表示链表的结束。


双向链表

双向链表的每个节点同时具有指向下一个节点和上一个节点的引用。这种结构使得在某些情况下更容易进行逆向操作。然而,相对于单向链表,双向链表需要额外的存储空间来存储上一个节点的引用。


链表的特点包括:


动态大小:链表的大小可以在运行时动态地调整,而不需要预先分配固定大小的内存空间。

插入和删除效率高:由于链表的节点不需要在内存中连续存储,插入和删除元素的效率较高。

随机访问低效:与数组相比,链表的随机访问效率较低,需要从头开始遍历链表才能访问特定位置的元素。

基本操作

插入操作

在链表中插入一个节点涉及到改变节点的指针引用。例如,要在单向链表中插入一个新节点,需要将新节点的“下一个节点”指针指向原节点的下一个节点,并将原节点的“下一个节点”指针指向新节点。在双向链表中,插入操作还需要更新新节点和相邻节点之间的引用。


删除操作

删除链表中的节点同样需要调整节点的指针引用。在单向链表中删除节点,只需要将前一个节点的“下一个节点”指针跳过当前节点,直接指向当前节点的下一个节点。在双向链表中,删除操作还需要更新相邻节点之间的引用。


查找操作

链表的查找操作通常需要遍历整个链表,直到找到目标节点为止。这使得链表在随机访问方面效率较低,但对于插入和删除操作,它却有优势。


我们选择了一个简单的问题LRU缓存并在后面进行了解答

链表在许多问题中都有广泛的应用。一个典型的例子是Least Recently Used(LRU)缓存算法,它使用链表来管理缓存中的数据。


在LRU缓存中,当缓存满时,新的数据需要替换掉最近最少使用的数据。这意味着我们需要实时跟踪数据的使用顺序。链表正是解决这个问题的理想数据结构。


我们可以使用双向链表来实现LRU缓存。每次访问一个数据时,我们将其移动到链表的头部。当需要替换数据时,只需要删除链表尾部的数据即可。


这里笔者用一个简单的程序来说明清楚LRU缓存:



#include

#include


class LRUCache {

private:

   struct Node {

       int key;

       int value;

       Node* prev;

       Node* next;

       Node(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {}

   };

 

   int capacity;

   std::unordered_map cache;

   Node* head;

   Node* tail;

 

   // 将节点移动到链表头部

   void moveToHead(Node* node) {

       // 从当前位置移除节点

       node->prev->next = node->next;

       node->next->prev = node->prev;

     

       // 将节点移到链表头部

       node->prev = head;

       node->next = head->next;

       head->next->prev = node;

       head->next = node;

   }

 

   // 移除链表尾部节点

   Node* removeTail() {

       Node* node = tail->prev;

       tail->prev = node->prev;

       node->prev->next = tail;

       return node;

   }


public:

   LRUCache(int capacity) : capacity(capacity) {

       head = new Node(-1, -1);

       tail = new Node(-1, -1);

       head->next = tail;

       tail->prev = head;

   }

 

   // 获取键对应的值,并将节点移动到链表头部

   int get(int key) {

       if (cache.find(key) != cache.end()) {

           Node* node = cache[key];

           // 移动被访问的节点到链表头部

           moveToHead(node);

           return node->value;

       }

       return -1;

   }

 

   // 插入键值对到缓存,如果缓存已满则删除最久未使用的节点

   void put(int key, int value) {

       if (cache.find(key) != cache.end()) {

           Node* node = cache[key];

           node->value = value;

           // 将更新后的节点移动到链表头部

           moveToHead(node);

       } else {

           if (cache.size() >= capacity) {

               // 从链表尾部移除最久未使用的节点

               Node* removedNode = removeTail();

               cache.erase(removedNode->key);

               delete removedNode;

           }

           // 创建新节点并添加到链表头部

           Node* newNode = new Node(key, value);

           cache[key] = newNode;

           moveToHead(newNode);

       }

   }

 

   // 析构函数,释放内存

   ~LRUCache() {

       for (auto it = cache.begin(); it != cache.end(); ++it) {

           delete it->second;

       }

       delete head;

       delete tail;

   }

};


int main() {

   LRUCache cache(2);

   cache.put(1, 1);

   cache.put(2, 2);

   std::cout << cache.get(1) << std::endl; // 输出: 1

   cache.put(3, 3); // 移除键 2

   std::cout << cache.get(2) << std::endl; // 输出: -1 (未找到)

   std::cout << cache.get(3) << std::endl; // 输出: 3

   return 0;

}

通过上述示例,我们可以更好地理解了链表在实际问题中的应用。这也展示了数据结构在解决计算机科学中的各种问题时所起到的关键作用。无论是用于构建基础数据结构还是在高级算法中扮演角色,理解链表的概念和操作都是成为出色程序员的重要一步。

目录
相关文章
|
7月前
|
JSON 缓存 API
美股实时行情与 K 线数据对接
本文详解如何用StockTV全球金融API快速接入美股实时行情、K线、指数及IPO等数据,支持NYSE/NASDAQ双交易所,提供REST/WS低延迟接口,涵盖个股、指数、涨跌榜等全场景,助开发者高效构建全球资产配置工具。(239字)
|
运维 监控 安全
深入理解微服务架构:设计原则、挑战与实践
深入理解微服务架构:设计原则、挑战与实践
1101 93
|
9月前
|
存储 Linux 编译器
C 语言学习资源精选:从入门到精通的高效资源清单
本文为C语言学习者提供从入门到精通的完整资源指南,涵盖各阶段优质视频、书籍、博客、开源项目及学习社区,并结合高效学习方法,帮助初学者摆脱资源焦虑,系统掌握语法、指针、内存管理等核心知识,进阶嵌入式与底层开发,稳步提升编程能力。
|
存储 设计模式 数据可视化
DDD新手入门:领域模型设计的七个核心概念
小米,29岁程序员,分享领域模型落地知识。文章解析领域、子域、限界上下文、领域对象、聚合、工厂与仓库等概念,助你理解领域驱动设计。
1521 1
|
网络协议 Unix Linux
U-BOOT小全(一)
U-BOOT小全(一)
665 0
公众号“请勿插入不合法的图文消息链接”错误解决办法(Markdown)
公众号“请勿插入不合法的图文消息链接”错误解决办法(Markdown)
1494 2
|
机器学习/深度学习 人工智能 自然语言处理
全球名校AI课程库(5)| Stanford斯坦福 · 深度学习课程『Deep Learning』
吴恩达与助教在斯坦福开设的深度学习课程,内容覆盖基础知识、各类神经网络、实际应用等排,是很多人的深度学习入门课。
3067 1
全球名校AI课程库(5)| Stanford斯坦福 · 深度学习课程『Deep Learning』
|
安全 网络协议 算法
华为防火墙配置(防火墙NAT)
防火墙NAT概述、防火墙NAT策略介绍、NAT策略分类、NAT策略组成、NAT策略匹配规则、NAT策略处理流程、源NAT的使用限制、目的NAT的使用限制、与双机热备结合使用的限制、其它使用限制、源NAT简介、源NAT分类、目的NAT简介、目的NAT分类、防火墙NAT配置、案例、配置过程、测试
1638 1
华为防火墙配置(防火墙NAT)
|
JavaScript 前端开发
时间戳(获取时间戳
时间戳通常是指自某个特定时间(如1970年1月1日00:00:00 UTC)以来的秒数或毫秒数。在JavaScript中,可以使用Date对象来处理时间戳。
|
存储 弹性计算 数据库
深度解析云服务器ECS的核心构件与架构
本文深入研究了云服务器ECS(Elastic Compute Service)的核心构件与架构,详细介绍了其基本构成、生命周期、物理架构、网络架构以及与其他云服务的关系。通过代码示例,读者可以全面了解ECS在云计算环境中的运作方式和实际应用。
1475 0

热门文章

最新文章