算法与数据结构(数组与链表)

数组

线性数据结构。相同类型元素存储在连续内存空间,在其中的位置为索引。

  • 初始化数组
# 无初始值
arr: list[int] = [0] * 5
nums: list[int] = [1, 3, 2, 5, 4]
  • 访问元素
# 元素内存地址 = 数组内存地址+元素长度x元素索引
def random_access(nums: list[int]) -> int:
    random_index = random.randint(0, len(nums)-1)
    random_num = nums[random_index]
    return random_num
  • 插入元素
def insert(nums: list[int[, num: int, index: int):
	for i in range(len(nums)-1, index, -1):
		nums[i] = nums[i-1]
	nums[index] = num
  • 删除元素
def remove(nums: list[int], index: int):
	for i in range(index, len(nums)-1):
		nums[i] = nums[i+1]

数组插入删除平均时间复杂度高:O(n),数组长度不可变,超出会丢失元素,空的内存会浪费。

  • 遍历数组
def traverse(nums: list[int]):
	count = 0
	# 通过索引遍历数组
	for i in range(len(nums)):
		count += num
	# 直接遍历数组元素
	for num in nums:
		count += num
	# 同时遍历数据索引和元素
	for i, num in enumerate(nums):
		count ++ nums[i]
		count += num
  • 查找元素
# 线性查找
def find(nums: list[int], target: int) -> int:
	for i in range(len(nums)):
		if nums[i] == target:
			return i
	return -1
  • 扩容数组
# 难以保证数组之后内存可用,因此长度不可变。
def extend(nums: list[int], enlarge: int) -> list[int]:
	res = [0] * (len(nums) + enlarge)
	for i in range(len(nums)):
		res[i] = nums[i]
	return res

优点:空间效率高,支持随机访问,缓存局部性提高执行速度
缺点:插入删除效率低,长度不可变,空间浪费
应用:随机访问,排序和搜索,查找表,机器学习(向量、矩阵、张量间的线性代数,神经网络最常用数据结构),数据结构的事项(栈、队列、哈希表、堆、图、图领接矩阵二维数组)

链表

线性数据结构,每个元素是个节点对象,节点间用引用连接,引用记录下一个节点的内存。
头结点、尾结点(指向空)、链表比数组占用更多空间

class ListNode:
	def __init__(self, val: int):
		self.val: int = val  # 节点值
		self.next: ListNode | None = None # 指向下一节点
  • 初始化链表
# 初始化节点
n0 = ListNode(1)
n1 = ListNode(3)
n2 = ListNode(2)
n3 = ListNode(5)
n4 = ListNode(4)
# 构建节点间引用
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4
  • 插入节点
# 在n0后面插入P
def insert(n0: ListNode, P: ListNode):
	n1 = n0.next
	P.next = n1
	n0.next = P
  • 删除节点
# 删除节点n0后的首个节点
def remove(n0: ListNode):
	if not n0.next:
		return
	P = n0.next
	n1 = P.next
	n0.next = n1
  • 访问节点
# 访问节点效率低,需要遍历找
def access(head: ListNode, index: int) -> ListNode | None:
	for _ in range(index):
		if not head:
			return None
		head = head.next
	return head
  • 查找节点
def find(head: ListNode, target: int) -> int:
	index = 0 
	while head:
		if head.val == target:
			return index
		head = head.next
		index += 1
	return -1
  • 链表类型
# 单向链表:尾结点指向None
# 环形链表:尾结点指向头结点
# 双向链表:包含指向前驱节点和后继结点。更占内存。
class ListNode:
	def __init__(self, val:int):
		self.val: int == val
		self.next: ListNode | None = None
		self.prev: ListNode | None = None
  • 链表应用

单向链表

  • 实现栈与队列:插入和删除在链表一端则为栈。插入在一端,删除在另一端为队列。
  • 哈希表:解决哈希冲突。
  • 图:邻接表。图的每个顶点都与一个链表关联,链表中元素代表与该顶点相连的其他顶点。

双向链表(用于需要快速查前一个和后一个元素)

  • 高级数据结构:红黑树、B树,需要访问父节点。
  • 浏览器历史:网页浏览,用户点击前进或后退。
  • LRU算法:缓存淘汰算法。需要找到最近最少使用的数据,支持快速添加和删除节点。
    环形链表(用于需要周期性操作的场景,如操作系统资源调度)
  • 时间片轮转调度算法:CPU调度算法。对进程组循环,每个进程赋予一个时间片,用完切换下一个进程。
  • 数据缓存区:音频、视频播放器。数据流分成多个缓冲块放入环形,实现无缝播放。

列表

抽象概念。可以基于链表和数组实现。使用长度不可变的数组实现会使其实用性降低,因此使用动态数组实现列表。
常用操作包括:初始化列表、访问元素、插入与删除(尾部插入O(1))、遍历列表、拼接列表、排序列表。许多编程语言内置了列表。

  • 简易列表实现
class MyList:
	def __init__(self):
		self._capacity: int = 10  # 列表容量
		self._arr: list[int] = [0] * self._capacity  # 数组
		self._size: int = 0  # 列表长度
		self._extend_ratio: int = 2  #每次扩容列表的倍数

	def size(self) -> int:
		return selt._size

	def capacity(self) -> int:
		return self._capacity

	def get(self, index: int) -> int:
		if index<0 or index >=self._size:
			return IndexError("索引越界")
		return self._arr[index]

	def set(self, num:int, index: int):
		if index<0 or index >=self._size:
			return IndexError("索引越界")
		self._arr[index] = num

	def add(self, num: int):
		if self.size() == self.capacity():
			self.extend_capacity()
		self._arr[self._size] = num
		self._size += 1

	def insert(self, num:int, index:int):
		if index<0 or index >=self._size:
			return IndexError("索引越界")
		if self.size() == self.capacity():
			self.extend_capacity()
		for j in range(self._size-1, index-1, -1):
			self._arr[j+1] = self._arr[j]
		self._arr[index] = num
		self._size += 1

	def remove(self, index: int) -> int:
		if index<0 or index >=self._size:
			return IndexError("索引越界")
		num = self._arr[index]
		for j in range(index, self._size-1):
			self_arr[j] = self_arr[j+1]
		self._size -= 1
		return num
		
	def extend_capacity(self):
		self._arr = self._arr + [0]*self.capacity() * (self._extend_ratio - 1)
		self._capacity = len(self._arr)

	def to_arr(self) -> list[int]:
		# 返回有效长度的列表
		return self._arr[: self._size]

内存有限且随着反复申请与释放,空闲内存碎片化程度越来越高,从而导致内存利用率降低。数组其连续存储,相对不容易导致内存碎片化。且数组具有更高的缓存命中率,因此在操作效率上优于链表。但是选择使用哪个取决于具体场景。

力扣算法题

# 数组简单题
"""1、给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?"""


# 暴力枚举
class Solution:
    @staticmethod
    def twoSum(nums, target):
        for i in range(len(nums)):
            for j in range(i + 1, len(nums)):
                if nums[i] + nums[j] == target:
                    return [i, j]
        return []


# 哈希表
class Solution1:
    @staticmethod
    def twoSum(nums, target):
        hash_map = {}
        for i in range(len(nums)):
            if target-nums[i] in hash_map:
                return [hash_map[target-nums[i]], i]
            hash_map[nums[i]] = i
        return []


nums1 = [2, 7, 11, 15]
target1 = 9
nums2 = [3, 2, 4]
target2 = 6
nums3 = [3, 3]
target3 = 6
res = Solution1.twoSum(nums3, target3)
print(res)
"""数组简单题

"""
def solute(list1):
    if not list1:
        return 0
    length = len(list1)
    fast = slow = 1
    while fast < length:
        if list1[fast] != list1[fast - 1]:
            list1[slow] = list1[fast]
            slow += 1
        fast += 1
    return slow


list2 = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4]
list3 = [1, 1, 2]
count1 = solute(list3)
print(count1)
print(list3)
"""链表中等题
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
提示:
每个链表中的节点数在范围 [1, 100] 内
0 <= Node.val <= 9
题目数据保证列表表示的数字不含前导零
"""
class ListNode:
    def __init__(self, val, next_node=None):
        self.val = val
        self.next_node = next_node

    @staticmethod
    def add_two_numbers(ln1, ln2):
        carry = 0
        tail = None
        head = None
        while ln1 is not None or ln2 is not None:
            n1 = ln1.val if ln1 is not None else 0
            n2 = ln2.val if ln2 is not None else 0
            sum1 = n1 + n2 + carry
            if not tail:
                head = ListNode(sum1 % 10)
                tail = head
            else:
                tail.next_node = ListNode(sum1 % 10)
                tail = tail.next_node
            carry = sum1 // 10
            if ln1:
                ln1 = ln1.next_node
            if ln2:
                ln2 = ln2.next_node
        if carry > 0:
            tail.next_node = ListNode(carry)
        return head


LN1 = ListNode(2)
LN2 = ListNode(4)
LN3 = ListNode(3)
LN1.next_node = LN2
LN2.next_node = LN3
LN4 = ListNode(5)
LN5 = ListNode(6)
LN6 = ListNode(4)
LN4.next_node = LN5
LN5.next_node = LN6

LN7 = ListNode(0)
LN8 = ListNode(0)

LN9 = ListNode(9)
LN10 = ListNode(9)
LN11 = ListNode(9)
LN12 = ListNode(9)
LN13 = ListNode(9)
LN14 = ListNode(9)
LN15 = ListNode(9)
LN9.next_node = LN10
LN10.next_node = LN11
LN11.next_node = LN12
LN12.next_node = LN13
LN13.next_node = LN14
LN14.next_node = LN15
LN16 = ListNode(9)
LN17 = ListNode(9)
LN18 = ListNode(9)
LN19 = ListNode(9)
LN16.next_node = LN17
LN17.next_node = LN18
LN18.next_node = LN19

res = ListNode.add_two_numbers(LN9, LN16)
while res:
    print(res.val)
    res = res.next_node

你可能感兴趣的:(数据结构,算法,链表)