【算法基础】数组和链表,动态数组,循环数组,链表的变种

news2024/11/24 16:38:39

目录

1 数组(Array)

1.1 定义和特点

1.2  基本操作

1.3  数组的时间复杂度

1.4  应用场景

2 链表(Linked List)

2.1 定义和特点:

2.1.1 单向链表(Singly Linked List)

2.1.2 双向链表(Doubly Linked List):

2.1.3 循环链表(Circular Linked List):

2.2  基本操作

2.2.1 创建链表:

2.2.2 插入节点:

2.2.3 删除节点

2.2.4 遍历链表

2.3 时间复杂度

2.4 应用场景


1 数组(Array)

  • 数组是一种线性数据结构,由相同类型的元素组成,每个元素通过索引来访问。
  • 元素在内存中是连续存储的。
  • 数组的大小通常在创建时固定,不易扩展或缩小。

1.1 定义和特点

示例: 

# 创建一个整数数组,包含5个元素
my_array = [10, 20, 30, 40, 50]

# 访问数组中的元素,通过索引访问
print(my_array[0])  # 输出:10
print(my_array[3])  # 输出:40

# 修改数组中的元素
my_array[2] = 35

# 遍历数组并打印所有元素
for element in my_array:
    print(element)

# 输出:
# 10
# 20
# 35
# 40
# 50

          数组(Array)是一种常见的线性数据结构,它包含相同类型的元素,并且每个元素都可以通过索引来访问。数组中的元素在内存中是连续存储的,这意味着它们的地址是连续的,这有助于快速的元素访问。数组的大小通常在创建时固定,不易扩展或缩小,这是数组与其他动态数据结构(如动态数组或链表)的主要区别。

        在示例中,我们创建了一个包含5个整数元素的数组,并使用索引来访问和修改元素。数组中的元素在内存中是连续存储的,这样我们可以高效地访问它们。请注意,数组的大小在创建时固定,如果需要更多的元素,需要创建一个新的数组。


1.2  基本操作

  • 创建数组:声明数组类型和大小。
  • 访问元素:使用索引访问特定位置的元素。
  • 插入元素:将新元素插入到数组的指定位置。
  • 删除元素:从数组中删除指定位置的元素。
  • 更新元素:修改数组中特定位置的元素值。

创建数组

  • 创建数组时,通常需要声明数组的类型和大小。不同编程语言可能有不同的语法来创建数组。
  • 数组的大小表示数组可以容纳多少个元素,一旦大小固定,通常不能轻松改变。
my_array = [0, 0, 0, 0, 0] # 创建一个包含5个元素的整数数组

 访问元素

可以使用数组的索引来访问特定位置的元素。索引通常从0开始递增,表示元素在数组中的位置。

value = my_array[2] # 访问索引为2的元素,将值赋给变量value

 插入元素

        在数组中插入元素通常需要指定要插入的位置和要插入的值。插入操作可能需要移动后面的元素以腾出空间。

my_array.insert(2, 25) # 在索引为2的位置插入值25

  删除元素: 

从数组中删除元素通常需要指定要删除的位置。删除操作可能需要移动后面的元素以填补空白。

del my_array[3] # 删除索引为3的元素

 更新元素

更新数组中的元素值只需通过索引访问元素并将新值分配给该元素。

my_array[1] = 15 # 更新索引为1的元素值为15

这些操作是数组的常见操作,但请注意,数组的性质取决于编程语言和库的实现。在某些语言中,数组可能是固定大小的,而在其他语言中,可能存在动态数组,它们可以自动扩展以容纳更多元素。确保了解所使用的编程语言或库中数组的行为和限制。

# 创建一个整数数组
my_array = [1, 2, 3, 4, 5]

# 访问数组元素
first_element = my_array[0]  # 第一个元素
second_element = my_array[1]  # 第二个元素

# 修改数组元素
my_array[2] = 10  # 将第三个元素修改为10

# 获取数组的长度
array_length = len(my_array)  # 返回数组的长度,这里为5

# 插入元素(在索引位置2插入新元素)
my_array.insert(2, 6)  # [1, 2, 6, 10, 4, 5]

# 删除元素(删除第四个元素)
del my_array[3]  # [1, 2, 6, 4, 5]

1.3  数组的时间复杂度

  • 访问元素:O(1)
  • 插入和删除元素(在特定位置):O(n)
  • 更新元素:O(1)

访问元素:O(1) 

        访问数组中特定索引位置的元素的时间复杂度是常数时间,因为可以通过索引直接计算元素的内存地址。

my_array = [1, 2, 3, 4, 5] 
element = my_array[2] # 访问索引为2的元素(值为3),时间复杂度为O(1)

插入和删除元素(在特定位置):O(n)

  • 插入和删除数组中特定位置的元素需要将该位置之后的元素都向后移动或向前移动,以便为新元素或填充元素腾出空间。因此,这些操作的时间复杂度是线性的,与数组长度相关。

示例

my_array = [1, 2, 3, 4, 5]
my_array.insert(2, 6)  # 在索引2处插入元素6,需要将后面的元素移动,时间复杂度为O(n)
del my_array[3]  # 删除索引3处的元素,需要将后面的元素移动,时间复杂度为O(n)

更新元素:O(1)

  • 更新数组中特定索引位置的元素的时间复杂度是常数时间,因为只需找到索引并修改该位置的值。

示例:

my_array = [1, 2, 3, 4, 5]
my_array[2] = 10  # 更新索引2处的元素为10,时间复杂度为O(1)

        需要注意的是,虽然插入和删除元素的时间复杂度为O(n),但在某些情况下,如果操作是在数组末尾进行的,实际上可能只需常数时间。但在最坏情况下,需要移动大量元素,因此平均时间复杂度为O(n)。

        总结:数组的时间复杂度取决于具体的操作,访问元素的操作非常高效(O(1)),但插入和删除元素的操作通常较慢(O(n)),更新元素的操作也非常高效(O(1))。因此,在选择数据结构时,需要根据不同的操作需求权衡数组的优缺点。

1.4  应用场景

  • 适用于需要快速访问元素和已知大小的情况。
  • 数组用于实现各种数据结构,如栈、队列和矩阵等。

数组在以下情况下非常适用:

  1. 快速访问元素:由于数组中的元素在内存中是连续存储的,因此可以通过索引非常快速地访问特定位置的元素。这使得数组非常适合需要频繁访问元素的应用。

  2. 已知大小:数组的大小通常在创建时固定,不易扩展或缩小。因此,当知道数据集的大小是固定的或可以提前确定时,使用数组是一个不错的选择。

  3. 实现其他数据结构:数组可以用于实现各种其他数据结构,如栈、队列、矩阵等。例如,栈可以通过数组的末尾进行操作,队列可以通过数组的前端进行操作,矩阵可以使用多维数组表示。

示例1:使用数组实现栈

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        if not self.is_empty():
            return self.items.pop()

    def is_empty(self):
        return len(self.items) == 0

    def peek(self):
        if not self.is_empty():
            return self.items[-1]

    def size(self):
        return len(self.items)

# 创建一个栈并进行操作
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.pop())  # 输出: 3
print(stack.peek())  # 输出: 2

示例2:使用数组表示矩阵

# 创建一个3x3矩阵
matrix = [[1, 2, 3],
          [4, 5, 6],
          [7, 8, 9]]

# 访问矩阵中的元素
element = matrix[0][1]  # 访问第一行第二列的元素,输出: 2

# 更新矩阵中的元素
matrix[1][2] = 10  # 更新第二行第三列的元素为10

# 输出整个矩阵
for row in matrix:
    print(row)
# 输出:
# [1, 2, 3]
# [4, 5, 10]
# [7, 8, 9]

        总之,数组在需要快速访问元素和已知大小的情况下非常有用,并且可以用于实现各种常见的数据结构和数据表示。


2 链表(Linked List)

2.1 定义和特点

  • 链表是一种线性数据结构,由节点组成,每个节点包含数据和指向下一个节点的指针。
  • 链表中的节点在内存中不一定是连续存储的,它们通过指针连接在一起。
  • 链表可以具有不同的类型,如单向链表、双向链表和循环链表。

        链表(Linked List)是一种常见的线性数据结构,与数组不同,链表中的元素(节点)不是在内存中连续存储的,而是通过指针相互连接在一起。每个节点包含两个部分:数据和指向下一个节点的指针。

主要的链表类型包括:

2.1.1 单向链表(Singly Linked List)

  • 每个节点包含数据和一个指向下一个节点的指针。
  • 最后一个节点的指针通常指向空(null)。

2.1.2 双向链表(Doubly Linked List):

  • 每个节点包含数据、指向下一个节点的指针和指向前一个节点的指针。
  • 可以在前后两个方向上遍历链表。

2.1.3 循环链表(Circular Linked List):

  • 单向或双向链表的一个变种,最后一个节点指向第一个节点,形成一个循环。

        链表的优势在于可以在运行时动态分配内存,因此不需要像数组一样预先指定大小

        这使得链表在某些情况下更灵活,但也导致了访问元素的效率较低,因为需要按顺序遍历链表。


示例(Python中的单向链表):

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None  # 初始时没有下一个节点

# 创建链表
node1 = Node(10)
node2 = Node(20)
node3 = Node(30)

# 连接节点
node1.next = node2
node2.next = node3

# 遍历链表并打印数据
current_node = node1
while current_node is not None:
    print(current_node.data)
    current_node = current_node.next

 这是一个简单的单向链表示例,其中每个节点包含一个整数数据和指向下一个节点的指针。链表从node1开始,依次包含node2和node3,最后一个节点的指针为None。遍历链表时,我们从第一个节点开始,通过next指针移动到下一个节点,直到链表的末尾。

2.2  基本操作

  • 创建链表:声明头节点并初始化为空。
  • 插入节点:在链表中插入新节点,通常有头插法和尾插法。
  • 删除节点:从链表中删除指定节点。
  • 遍历链表:按顺序访问链表中的所有节点。

        链表是一种基本的数据结构,它由节点组成,每个节点包含数据和指向下一个节点的指针。下面详细解释链表的基本操作,并附带示例。

2.2.1 创建链表

        链表的创建通常从声明头节点开始,初始时头节点为空。头节点是链表的入口,用于访问整个链表。

2.2.2 插入节点

        在链表中插入新节点有两种常见的方法:

  • 头插法(Insert at the Beginning):将新节点插入到链表的开头,新节点成为新的头节点。
  • 尾插法(Insert at the End):将新节点插入到链表的末尾。

2.2.3 删除节点

        从链表中删除指定节点通常有以下步骤:

  • 找到待删除节点的前一个节点(前驱节点)。
  • 更新前驱节点的指针,将其指向待删除节点的下一个节点,从而绕过待删除节点。

2.2.4 遍历链表

        遍历链表意味着按顺序访问链表中的所有节点,通常使用循环来实现。

下面是一个简单的示例,演示了如何创建、插入、删除和遍历链表:

# 定义链表节点类
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

# 定义链表类
class LinkedList:
    def __init__(self):
        self.head = None

    # 插入节点到链表末尾(尾插法)
    def append(self, data):
        new_node = Node(data)
        if not self.head:
            self.head = new_node
        else:
            current = self.head
            while current.next:
                current = current.next
            current.next = new_node

    # 删除节点
    def delete(self, data):
        if not self.head:
            return
        if self.head.data == data:
            self.head = self.head.next
            return
        current = self.head
        while current.next:
            if current.next.data == data:
                current.next = current.next.next
                return
            current = current.next

    # 遍历链表并打印节点数据
    def display(self):
        current = self.head
        while current:
            print(current.data, end=" -> ")
            current = current.next
        print("None")

# 创建一个空链表
my_linked_list = LinkedList()

# 插入节点
my_linked_list.append(1)
my_linked_list.append(2)
my_linked_list.append(3)

# 遍历并显示链表
my_linked_list.display()  # 输出: 1 -> 2 -> 3 -> None

# 删除节点
my_linked_list.delete(2)

# 再次遍历并显示链表
my_linked_list.display()  # 输出: 1 -> 3 -> None

2.3 时间复杂度

  1. 访问节点:O(n)
  2. 插入和删除节点:O(1)(如果已知节点位置)或O(n)(如果需要查找位置)

        链表是一种线性数据结构,其访问、插入和删除操作的时间复杂度取决于操作的位置以及是否已知节点的位置。下面详细解释链表的时间复杂度,并附带示例。

访问节点

        链表中的节点没有像数组那样通过索引直接访问,而是需要从头节点开始沿着指针逐个遍历节点,因此访问节点的时间复杂度为O(n),其中n是链表的长度。

插入节点

        插入节点的时间复杂度取决于插入位置:

  • 如果已知要插入的位置,如链表的头部,那么插入节点的时间复杂度为O(1),因为只需要更新指针。
  • 如果需要在链表中间或末尾插入节点,但不知道插入位置,那么需要先遍历链表找到插入位置,因此插入节点的时间复杂度为O(n)。

删除节点:删除节点的时间复杂度也取决于删除位置:

  • 如果已知要删除的节点位置,如链表的头部,那么删除节点的时间复杂度为O(1),因为只需要更新指针。
  • 如果需要删除链表中间或末尾的节点,但不知道删除位置,那么需要先遍历链表找到删除位置,因此删除节点的时间复杂度为O(n)。

下面是一个示例,演示了访问、插入和删除节点的操作以及它们的时间复杂度:

# 定义链表节点类
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

# 定义链表类
class LinkedList:
    def __init__(self):
        self.head = None

    # 插入节点到链表末尾(尾插法)
    def append(self, data):
        new_node = Node(data)
        if not self.head:
            self.head = new_node
        else:
            current = self.head
            while current.next:
                current = current.next
            current.next = new_node

    # 删除节点
    def delete(self, data):
        if not self.head:
            return
        if self.head.data == data:
            self.head = self.head.next
            return
        current = self.head
        while current.next:
            if current.next.data == data:
                current.next = current.next.next
                return
            current = current.next

    # 访问节点(需要遍历链表)
    def access(self, index):
        current = self.head
        count = 0
        while current:
            if count == index:
                return current.data
            current = current.next
            count += 1
        return None

# 创建一个空链表
my_linked_list = LinkedList()

# 插入节点
my_linked_list.append(1)
my_linked_list.append(2)
my_linked_list.append(3)

# 访问节点(通过索引访问第二个节点)
value = my_linked_list.access(1)
print("访问节点:", value)  # 输出: 2

# 删除节点
my_linked_list.delete(2)

# 访问节点(通过索引访问第二个节点,此时已删除节点2)
value = my_linked_list.access(1)
print("访问节点:", value)  # 输出: 3

2.4 应用场景

  • 适用于需要频繁插入和删除节点的情况,或者在不知道数据量的情况下存储数据。
  • 链表常用于实现栈、队列、哈希表、LRU缓存等数据结构。
  • 特殊类型的链表,如双向链表,可用于双向遍历。

 

  1. 动态数组

    • 动态数组是基于数组的数据结构,可以自动扩展或缩小大小以容纳不同数量的元素。
    • 动态数组通常用于需要灵活性和效率的场景,但可能需要更多内存。
  2. 循环数组

    • 循环数组是一种特殊的数组,可以循环访问其元素,通常用于环形缓冲区和循环队列。
  3. 链表的变种

    • 单向链表:每个节点有一个指向下一个节点的指针。
    • 双向链表:每个节点有一个指向前一个节点和下一个节点的指针。
    • 循环链表:尾节点指向头节点,形成一个闭环。

以上是数组和链表的基本知识大纲,它们是数据结构中的重要概念,对于理解和设计算法以及解决各种编程问题非常重要。在实际编程中,选择合适的数据结构取决于问题的需求和性能要求。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1048861.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Axure RP9 引入eCharts图表

一、 ECharts 地址:https://echarts.apache.org/zh/index.html 概述:一个基于 JavaScript 的开源可视化图表库 提供了很多图标样式案例 二、 Axure引入eCharts图表步骤 步骤一:打开Axure,添加矩形元素,调整矩形所…

Mybatis 二级缓存

之前我们介绍过映射器与XML配置职责分离,本篇我们在此基础上介绍Mybatis中二级缓存的使用。 如果您对映射器与XML配置职责分离不太了解,建议您先进行了解后再阅读本篇,可以参考: Mybatis 映射器与XML配置职责分离https://blog.c…

轻松解决软件游戏msvcr120.dll丢失问题,msvcr120.dll丢失的修复步骤分享

msvcr120.dll 丢失可能会让许多软件和游戏无法正常运行,给用户带来无尽的困扰。当你尝试打开某个程序时,可能会弹出一个提示框,告诉你缺少 msvcr120.dll 文件。当你尝试运行某个游戏时,可能会遇到无法启动或运行一段时间后崩溃的问…

java项目中数据权限实现思路

一、需求: 同样的页面,不同的账号登录进去,看到的数据不一样。 二、权限管理的方式 RBAC模型 角色与数据权限的关系: 比如管理员角色,他的数据权限是全部,那么拥有该角色的用户,所能看到的数…

基于YOLOv8模型的蜜蜂目标检测系统(PyTorch+Pyside6+YOLOv8模型)

摘要:基于YOLOv8模型的蜜蜂目标检测系统可用于日常生活中检测与定位蜜蜂目标,利用深度学习算法可实现图片、视频、摄像头等方式的目标检测,另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目标检测算法训练数据集…

低代码软件:业务经理的利器!快速掌握使用技巧

低代码的出现,让应用开发不再是开发人员的专属工作。要知道在企业中,业务开发压力加上开发人手不够导致开发团队会积压大量请求。不仅拖慢了业务进程,也难免造成开发软对和业务团队之间的矛盾。 而成熟的业务经理在行业中深耕多年&#xff0…

基于PHP+MySQL的家教平台

摘要 设计和实现基于PHP的家教平台是一个复杂而令人兴奋的任务。这个项目旨在为学生、家长和教师提供一个便捷的在线学习和教授平台。本文摘要将概述这个项目的关键方面,包括用户管理、课程管理、支付处理、评价系统、通知系统和安全性。首先,我们将建立…

Golang的测试、基准测试和持续集成

在Golang中,内置的垃圾回收器处理内存管理,自动执行内存分配和释放。 单元测试是软件开发中至关重要的一个方面,它确保了代码的正确性并在开发过程中尽早发现错误。在Go中,编写有效的单元测试非常简单,并为开发人员提…

Mysql8安装+重装的数据备份方法【提供Mysql8.0.27版本的压缩包】

文章目录 Mysql8压缩安装包下载安装流程压缩包解压配置环境变量 初始化数据库连接数据库修改密码Mysql重装/重装系统 的数据库备份方法数据备份数据还原 Mysql8压缩安装包下载 压缩包下载路径 安装流程 压缩包解压 首先将压缩包解压,下图是解压之后的文件目录&a…

Leetcode 71. 简化路径

文章目录 题目代码&#xff08;9.28 首刷调试看解析&#xff09; 题目 Leetcode 71. 简化路径 代码&#xff08;9.28 首刷调试看解析&#xff09; class Solution { public:string simplifyPath(string path) {vector<string> parts;int start 0;for(int i 1; i <…

【C++11保姆级教程】空指针(nullptr),long long类型,char16_t和char32_t类型

文章目录 前言一、空指针(nullptr)1.1概念解释1.2形象比喻1.3示例代码1.4空指针nullptr的优势 二、long long类型2.1概念解释2.2形象比喻2.3示例代码2.4优势2.5劣势 三、char16_t和char32_t类型3.1概念解释3.2形象比喻3.3示例代码3.4优势3.5劣势 总结 前言 在C11标准中引入了许…

C# 数组

C# 数组 数组简单数组多维数组锯齿数组Array类数组的接口枚举 数组 如果需要使用同一类型的多个对象&#xff0c;就可以使用集合和数组。C#用特殊的记号声明和使用数组。 简单数组 在声明数组时&#xff0c;应先定义数组中元素的类型&#xff0c;其后是一个空方括号和一个变…

计算机毕业设计 基于SSM的垃圾分类管理系统(以医疗垃圾为例)的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

高性能MySQL第四版

主要列出与第三版的区别 第一章、MySQL架构 MySQL逻辑架构 左右分别是第三和第四版。 第四版架构图里把第二层的“查询缓存”去掉了&#xff0c;也去掉了对应的文字描述。 连接管理和安全 “每个 客户 端 连接 都会 在 服务器 进程 中 拥有 一个 线程” 第四版对这句话增…

英语——分享篇——每日100词——501-600

hill——will愿意——他不愿意去小山里 Easter——east东方(熟词)er儿(拼音)——东方的儿子都过复活节 exhibition——ex前夫(熟词)hi嗨(熟词)bition比神(谐音)——展览会上前夫很嗨&#xff0c;比神还开心 chase——vt.追捕&#xff0c;追逐&#xff0c;追赶——cha茶se色——…

国庆day1

消息队列 代码 发送 #include<myhead.h> //声明一个消息结构体 typedef struct {long msgtype; //消息类型char data[1024]; //消息正文 }Msg_s; #define SIZE sizeof(Msg_s)-sizeof(long) //消息正文的大小 int main(int argc, const char *argv[]) {key_t key; /…

HashMap底层源码,数据结构

HashMap的底层结构在jdk1.7中由数组链表实现&#xff0c;在jdk1.8中由数组链表红黑树实现&#xff0c;以数组链表的结构为例。 JDK1.8之前Put方法&#xff1a; JDK1.8之后Put方法&#xff1a; HashMap基于哈希表的Map接口实现&#xff0c;是以key-value存储形式存在&#xff0c…

lwip开发指南2

目录 NTP 协议实验NTP 简介NTP 实验硬件设计软件设计下载验证 lwIP 测试网速JPerf 网络测速工具JPerf 网络实验硬件设计软件设计下载验证 HTTP 服务器实验HTTP 协议简介HTTP 服务器实验硬件设计下载验证 网络摄像头&#xff08;ATK-MC5640&#xff09;实验ATK-MC5640 简介SCCB …

wait函数与waitpid函数

1.函数介绍 2.wait函数 #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *wstatus); 功能&#xff1a;等待任意一个子进程结束&#xff0c;如果该子进程结束了&#xff0c;此函数会回收子进程的资源 参数&#xff1a; -int *wstatus&#xff1a;…

26602-2011 工业用2-吡咯烷酮 知识梳理

声明 本文是学习GB-T 26602-2011 工业用2-吡咯烷酮. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了工业用2-吡咯烷酮的要求、试验方法、检验规则以及标志、包装、运输和贮存等。 本标准适用于γ-丁内酯和氨合成制得的2-吡咯烷酮…