云计算百科
云计算领域专业知识百科平台

数据结构与算法- 第 2 章 常用数据结构

第 2 章 常用数据结构

抽象数据类型(Abstract Data Type,简称 ADT)是计算机科学中一个重要的概念,它是对数据的一种抽象描述,关注数据的逻辑特性和操作,而不涉及具体的实现细节。

抽象数据类型通常由以下两部分组成:

  • 数据对象:描述了该数据类型所包含的数据元素以及它们之间的逻辑关系。例如,在一个栈的抽象数据类型中,数据对象是一系列按后进先出(LIFO)原则组织的元素。

  • 操作集合:定义了对数据对象可以执行的操作。对于栈来说,常见的操作包括入栈(push)、出栈(pop)、查看栈顶元素(peek)等。

抽象数据类型与数据结构的关系

  • 抽象数据类型:强调的是数据的逻辑特性和操作的功能,是一种抽象的概念,不涉及具体的实现细节。它是从用户的角度来描述数据和操作的。

  • 数据结构:是抽象数据类型的具体实现,它关注的是数据在计算机内存中的存储方式和操作的具体实现算法。例如,栈这种抽象数据类型可以用数组或链表等数据结构来实现。

2.1 数组

2.1.1 数组的概述

数组是一种线性数据结构,将相同类型的元素顺序地存储在连续的内存空间中,每个元素都有一个索引。

在这里插入图片描述

由于数组元素在内存中是连续存储的,所以只要知道数组的起始位置,以及数组元素的类型(单个元素的长度),就可以根据索引计算出任意元素的位置。

数组在创建时需要指定长度,并且数组一旦创建,长度就无法改变,如果需要扩容,只能创建一个更大的数组,再将原数据拷贝到新数组。并且由于数组的连续性,插入和删除数据可能需要移动其他元素。

在 Python 中,并没有像其他一些编程语言(如 C、Java)那样严格意义上的 “数组” 概念,但有多种数据结构可以用来模拟数组的功能,最常用的是列表(list),另外还有 array 模块的数组和 numpy 库的 ndarray。

通过Python 的list列表实现一个动态数组,它内部存储的实际上是对象的引用(指针),而不是对象本身。每个引用指向内存中存储实际对象的位置。

什么是数组

  • 用来存储相同数据类型的数据结构

  • 数组是有序的,可以通过下标获取元素

  • 数组在内存中会分配一片连续的存储空间

  • 数组在创建时需要指定长度,并且数组一旦创建,长度就无法改变,如果需要扩容,只能创建一个更大的数组,再将原数据拷贝到新数组

  • 但是 python中没有数组,如果想使用数组,可以使用array.array()或者numpy模块的ndarray,而array.array是支持动态扩容的

    下面是一个例子:

    import array

    list1 = [1,2,3,"hello"]
    print(list1)

    # 需要是相同数据类型
    arr = array.array("i",[1,2,3])
    print(arr)

    num = 1
    print(id(num)) # 140719834157992
    print(id(arr[0])) # 140719834157992

    这个是list的创建逻辑:

    在这里插入图片描述

    array.array使用数组逻辑:

    在这里插入图片描述

    会发现 和list有区别:

    • array.array中因为要求相同数据类型,不能指向 hello的地址

    • array.array创建的对象中存储的不是地址,而是具体的值/二进制数

    • print(id(num)) 和 print(arr[0])的地址相同

    因为如果指定的是 i 的话,如下图,如果存储值只需要2个字节空间大小就可以,但如果是存储地址就需要8个空间,改为存储值 更节省空间。

    而在获取地址的时候,它不会直接把值给你,而是需要进行 创建对象,而在创建对象的时候它发现 小整数池中已经存在,因此就不进行创建了,而是直接拿过来使用,所以两个打印的结果一样

    在这里插入图片描述

    2.1.2 顺序表

    我们根据数组的概述得知: Python语言中的list、array都是动态,不需要关注扩容的过程,而在一些其他编程语言中,比如C、java中的数组是需要手动扩容的, 它们的底层都是顺序表 ,只不过是不同的存储方式

    顺序表包含:

    • 一体式存储(如C、java中的数组,Python中的元组)

    • 分离式存储 (如Python中的list、array.array,java的ArrayList)

    一体式存储:

    创建的对象中信息包含:类型、个数、容量、具体元素。 当扩容的时候,会创建新的对象,并指向它的地址

    在这里插入图片描述

    分离式存储:

    分离式存储会发现仍旧创建了对象,但list中存储的是数据的地址,当需要扩容的时候,也进行了新的数据对象的创建,但list内部只是更改了存储数据 的地址,list本身并没有扩容

    在这里插入图片描述

    2.1.3 数组的功能定义

    方法说明
    size() 返回数组中元素个数
    is_empty() 判断数组是否为空
    insert(index, item) 在指定位置插入元素
    append(item) 在末尾插入元素
    remove(index) 删除指定位置的元素
    set(index, item) 修改指定位置的元素
    get(index) 获取指定位置的元素
    find(item) 查找数组中某个元素的位置
    for_each(func) 遍历数组

    2.1.4 数组的创建

    实现一个动态数组。

    class Array:
    def __init__(self):
    *"""初始化数组"""*
    * *# 数组的初始化容量
    self.__capacity = 7
    # 数组中元素的个数
    self.__size = 0
    # 真正存储数据的对象, 创建了一个全是0的数组
    self.__items = [0] * 7

    def __str__(self):
    *"""打印数组"""*
    * *arr_str = "["
    for i in range(self.__size):
    arr_str += str(self.__items[i])
    if i < self.__size – 1:
    arr_str += ", "
    arr_str += "]"
    return arr_str

    # 方法转为属性
    @property
    def size(self):
    *"""获取数组元素个数"""*
    * *return self.__size

    def is_empty(self):
    *"""判断数组是否为空"""*
    * *return self.__size == 0

    2.1.5 数组扩容

    当数组容量占满后,我们可以创建一个新的数组,容量为之前数组的2倍,并将之前数组的元素拷贝到新数组中。

    def __grow(self):
    *"""数组扩容"""*
    * *# 创建新的数据 容量是原来的2倍
    self.__capacity *= 8
    new___items = [0] * self.__capacity

    # 将老数组数据 复制到新的数组中
    for i in range(self.__size):
    new___items[i] = self.__items[i]

    # 将数组存储数据的对象地址 指向新的数组
    self.__items = new___items

    2.1.6 添加/插入元素

    在中间插入元素时,将指定位置及其之后的元素全部向后移动一个位置,并将指定位置改为新的元素。

    在这里插入图片描述

    如果直接从前边向后移动,索引会混乱,因此**从后边向前边遍历, **以上述 数组创建为例,那么应该从索引5的位置遍历,到索引3

    在这里插入图片描述

    #添加元素
    def insert(self,index,item):
    if index < 0 or index > self.__size:
    raise IndexError("index out of range")

    # 判断是否需要扩容
    if self.__size == self.__capacity:
    self.__grow()

    for i in range(self.__size,index,-1):
    self.__items[i] = self.__items[i-1]

    self.__items[index] = item

    self.__size += 1

    # 向数组中追加元素
    def append(self,item):
    self.insert(self.__size,item)

    2.1.7 删除元素

    删除数组中指定位置的元素时,将该位置之后的所有元素向前移动一个位置。

    在这里插入图片描述

    把索引为3的值赋值到索引为2的位置,索引为4的位置赋值到索引为3的位置,

    # 向数组中追加元素
    def append(self,item):
    self.insert(self.__size,item)

    # 从数组指定的位置删除元素
    def remove(self,index):
    if index < 0 or index >= self.__size:
    raise IndexError("index out of range")

    for i in range(index, self.__size-1):
    self.__items[i] = self.__items[i+1]

    self.__size -= 1

    for i in range(index, self.__size):这样写也不会报错,但上相比于上面代码,多执行了一步

    在这里插入图片描述

    因为不管是size还是size-1如何处理,最后一个索引会被删掉

    2.1.8修改元素

    # 修改元素
    def set(self,index,item):
    if index < 0 or index >= self.__size:
    raise IndexError("index out of range")

    self.__items[index] = item

    2.1.9访问元素

    def get(self, index):
    """访问元素"""
    if index < 0 or index >= self.__size:
    raise IndexError
    return self.__items[index]

    2.1.10查询元素

    def find(self, item):
    """查找元素"""
    for i in range(self.__size):
    if self.__items[i] == item:
    return i
    return -1

    2.1.11遍历元素

    def for_each(self, func):
    """遍历数组"""
    for i in range(self.__size):
    func(self.__items[i])

    2.1.12 完整代码

    class Array:
    def __init__(self):
    """初始化数组"""
    self.__capacity = 8
    self.__size = 0
    self.__items = [0] * 8

    def __str__(self):
    """打印数组"""
    arr_str = "["
    for i in range(self.__size):
    arr_str += str(self.__items[i])
    if i < self.__size – 1:
    arr_str += ", "
    arr_str += "]"
    return arr_str

    @property
    def size(self):
    """获取数组元素个数"""
    return self.__size

    def is_empty(self):
    """判断数组是否为空"""
    return self.__size == 0

    def __grow(self):
    """数组扩容"""
    self.new___items = [0] * self.__capacity * 2
    for i in range(self.__size):
    self.new___items[i] = self.__items[i]
    self.__items = self.new___items
    self.__capacity *= 2

    def insert(self, index, item):
    """插入元素"""
    if index < 0 or index > self.__size:
    raise IndexError
    if self.__size == self.__capacity:
    self.__grow()
    for i in range(self.__size, index, -1):
    self.__items[i] = self.__items[i – 1]
    self.__items[index] = item
    self.__size += 1

    def append(self, item):
    """末尾插入元素"""
    self.insert(self.__size, item)

    def remove(self, index):
    """删除元素"""
    if index < 0 or index >= self.__size:
    raise IndexError
    for i in range(index, self.__size – 1):
    self.__items[i] = self.__items[i + 1]
    self.__size -= 1

    def set(self, index, item):
    """修改元素"""
    if index < 0 or index >= self.__size:
    raise IndexError
    self.__items[index] = item

    def get(self, index):
    """访问元素"""
    if index < 0 or index >= self.__size:
    raise IndexError
    return self.__items[index]

    def find(self, item):
    """查找元素"""
    for i in range(self.__size):
    if self.__items[i] == item:
    return i
    return -1

    def for_each(self, func):
    """遍历数组"""
    for i in range(self.__size):
    func(self.__items[i])

    2.2 链表

    2.2.1 链表的概述

    链表(Linked List)是一个线性结构,由一系列**节点(Node)**组成,每个节点包含一个数据元素和一个指向下一节点的指针(Pointer)。所有节点通过指针相连,形成一个链式结构。通常我们将链表中的第一个节点称为头结点,并将头结点的位置作为整个链表的位置标识。与数组不同,链表中每个节点分散的存储在内存中,每个节点都保存了当前节点的数据和下一节点的地址(指针)。

    在这里插入图片描述

    由于链表中节点通过指针相连,插入和删除节点只需要修改指针的指向即可,而不需要像数组那样移动数据。且链表不需要像数组那样预先指定大小,而是可以随时动态的增长或缩小。由于链表使用分散存储的方式,因而无需使用大段连续的内存空间。

    由于链表中的节点不是连续存储的,无法像数组一样根据索引直接计算出每个节点的地址。必须从头节点开始遍历链表,直到找到目标节点,这导致了链表的随机访问效率较低。链表的每个节点都需要存储指向下一个节点的指针,这会占用额外的存储空间。相比于数组,链表需要更多的内存空间来存储相同数量的数据元素。

    常见的链表包括三种:

    • 单向链表:单向链表的节点包含值和指向下一节点的引用。我们将首个节点称为头节点,将最后一个节点称为尾节点,尾节点指向空 None 。

    • 环形链表:将单向链表的尾节点指向头节点(首尾相接),则得到一个环形链表。在环形链表中,任意节点都可以视作头节点。

    • 双向链表:双向链表记录了两个方向的引用,同时包含指向后继节点(下一个节点)和前驱节点(上一个节点)的引用。

    单向链表:

    在这里插入图片描述

    双向链表:

    在这里插入图片描述

    环形链表

    在这里插入图片描述

    2.2.2 链表的功能定义

    方法说明
    size() 返回链表中元素个数
    is_empty() 判断链表是否为空
    insert(index, item) 在指定位置插入元素
    append(item) 在末尾插入元素
    remove(index) 删除指定位置的元素
    set(index, item) 修改指定位置的元素
    get(index) 获取指定位置的元素
    find(item) 查找链表中某个元素的位置
    for_each(func) 遍历链表

    2.2.3 链表的创建

    实现一个单向链表。

    class Node:
    *"""链表上的节点类"""*
    * *def __init__(self, data,next=None):
    self.data = data
    self.next = next

    class MyLinkedList:
    def __init__(self):
    # 链表上的元素个数
    self.__size = 0
    self.__head = None

    @property
    def size(self):
    return self.__size

    def is_empty(self):
    return self.__size == 0

    def __str__(self):
    res_list = []

    # 怎么取出链表上的元素
    node = self.__head
    while node:
    res_list.append(str(node.data))
    node = node.next

    return "->".join(res_list)

    2.2.4 添加/插入元素

    在这里插入图片描述

    在这里插入图片描述

    def insert(self, index, item):
    """插入元素"""
    if index < 0 or index > self.__size:
    raise IndexError
    if index == 0:
    # 插入到头部,需要新建一个节点,然后让新节点的next指向原来的head,然后让head指向新节点
    self.__head = Node(item, self.__head)
    else:
    # 插入到中间,先找到index-1位置的节点
    node = self.__head
    for i in range(**index – 1**):
    node = node.next
    # 新节点的next指向index位置的节点,然后让index-1位置的节点的next指向新节点
    #(新节点指向后一个节点)
    node.next = Node(item, node.next)
    self.__size += 1

    链表中没有索引可以理解为索引,链表中想拿到元素只能从头开始做循环。如果插入到头部,重新指向一下新的头部节点,如果插入到中间,按索引算 比如在1和2中间插入(新索引2的位置),就是在索引为1的后边插入,就需要先找到1(index-1)的位置,从而拿到1的数据,而拿到1的数据只需要获取1前边的索引位置的信息.next就可以拿到了,就是索引为0的位置,相当于实际拿到的是index-2的位置,但因为range左开右闭,因此实际写为index-1

    在末尾插入元素时,使用insert()并将index设置为链表长度。

    def append(self, item):
    """末尾插入元素"""
    self.insert(self.__size, item)

    2.2.5 删除元素

    在这里插入图片描述

    def remove(self, index):
    """删除元素"""
    if index < 0 or index >= self.__size:
    raise IndexError
    if index == 0:
    # 下一个节点改为头节点
    self.__head = self.__head.next
    else:
    # 找到index-1位置的节点,然后让index-1位置的节点的next指向index位置的节点的next
    node = self.__head
    for i in range(index – 1):
    node = node.next
    node.next = node.next.next
    self.__size -= 1

    2.2.6 修改元素

    def set(self, index, item):
    """修改元素"""
    if index < 0 or index >= self.__size:
    raise IndexError
    node = self.__head
    for i in range(index):
    #比如索引为3,node就是第二个索引位置,node.next就是第三个索引节点
    node = node.next
    node.data = item

    2.2.7 访问元素

    def get(self, index):
    """访问元素"""
    if index < 0 or index >= self.__size:
    raise IndexError
    node = self.__head
    for i in range(index):
    node = node.next
    return node.data

    2.2.8 查找元素

    def find(self, item):
    """查找元素"""
    node = self.__head
    while node:
    if node.data == item:
    return True
    node = node.next
    return False

    获取元素第一次的索引

    def find(self,item):
    node = self.__head
    for i in range(self.__size):
    if node.data == item:
    return i
    node = node.next

    return -1

    2.2.9 遍历链表

    def for_each(self, func):
    """遍历链表"""
    node = self.__head
    while node:
    func(node)
    node = node.next

    2.2.10 完整代码

    class Node:
    def __init__(self, data, next=None):
    self.data = data
    self.next = next

    class LinkedList:
    def __init__(self):
    """初始化链表"""
    self.__head = None
    self.__size = 0

    def __str__(self):
    """打印链表"""
    result = []
    current = self.__head
    while current:
    result.append(str(current.data))
    current = current.next
    return " -> ".join(result)

    @property
    def size(self):
    """获取链表元素个数"""
    return self.__size

    def is_empty(self):
    """判断链表是否为空"""
    return self.__size == 0

    def insert(self, index, item):
    """插入元素"""
    if index < 0 or index > self.__size:
    raise IndexError
    if index == 0:
    # 插入到头部,需要新建一个节点,然后让新节点的next指向原来的head,然后让head指向新节点
    self.__head = Node(item, self.__head)
    else:
    # 插入到中间,先找到index-1位置的节点
    node = self.__head
    for i in range(index – 1):
    node = node.next
    # 新节点的next指向index位置的节点,然后让index-1位置的节点的next指向新节点
    node.next = Node(item, node.next)
    self.__size += 1

    def append(self, item):
    """末尾插入元素"""
    self.insert(self.__size, item)

    def remove(self, index):
    """删除元素"""
    if index < 0 or index >= self.__size:
    raise IndexError
    if index == 0:
    self.__head = self.__head.next
    else:
    # 找到index-1位置的节点,然后让index-1位置的节点的next指向index位置的节点的next
    node = self.__head
    for i in range(index – 1):
    node = node.next
    node.next = node.next.next
    self.__size -= 1

    def set(self, index, item):
    """修改元素"""
    if index < 0 or index >= self.__size:
    raise IndexError
    node = self.__head
    for i in range(index):
    node = node.next
    node.data = item

    def get(self, index):
    """访问元素"""
    if index < 0 or index >= self.__size:
    raise IndexError
    node = self.__head
    for i in range(index):
    node = node.next
    return node.data

    def find(self, item):
    """查找元素"""
    node = self.__head
    while node:
    if node.data == item:
    return True
    node = node.next
    return False

    def for_each(self, func):
    """遍历链表"""
    node = self.__head
    while node:
    func(node)
    node = node.next

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 数据结构与算法- 第 2 章 常用数据结构
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!