1. 数据结构与算法
1.1 什么是数据结构
数据结构是存储、组织数据的方式
1.2 什么是算法
算法是为了实现业务目的的各种方法和思路
1.3 算法的五大特性
- 输入:0个输入或多个输入
- 输出:1个或多个输出
- 有穷性:在有限的步骤之后会自动结束,而不会无限的循环,且每一个步骤可以在可接受的实践内完成
- 确定性:每一步都有确定的含义不会出现二义性
- 可行性:每一步都是可行的,即每一步都能够执行有限的次数完成。
1.4 时间复杂度
时间效率一般是看操作步骤的数量。时间复杂度是随着问题规模不断变化的一个趋势。
对于一些基本的操作,例如赋值、输入、输出等,时间复杂度为O(1)
对于顺序操作,时间复杂度也是O(1)
循环操作,时间复杂度为O(n)
对于分支结构,时间复杂度一般取分支的时间复杂度的最大值。
1.5 时间复杂度排序
通常情况下,我们只关注算法的最坏时间复杂度。一般只关注最高次项。
时间复杂度越低,效率越高。
O(n^3) > O(n^2) > O(nlogn) > O(n) > O(logn) > O(1)
1.6 空间复杂度
空间复杂度是指算法所耗费的存储空间
内存是以字节为基本单位的,1024b = 1kb,每个基本存储空间都有自己的地址。
2. 数据结构的分类
数据结构按照逻辑结构的不同,可以分为线性结构和非线性结构。
2.1 线性结构
线性结构:所有的结点最多只有一个直接前驱结点和一个直接后继结点。
线性结构按照存储方式的不同,又分为顺序表和链表。
常见的线性结构有栈、队列。
2.2 非线性结构
非线性结构:一个结点可以有多个直接前驱和多个直接后继,常见的非线性结构有树结构、图结构。
2.3 顺序表
2.3.1 什么是顺序表
将元素顺序地存放在一块连续的存储区里,存储是连续的。
顺序表的完整信息包括两部分:数据区和信息区(即,容量和已有元素个数)
顺序表存储数据有两种情况,分别是一体式结构、分离式结构。且时间复杂度都是O(1)
2.3.2 一体式结构
对于相同的数据类型,变量会记住首元素地址,然后按照偏移公式m*(n-1)个字节去查找。其中m代表是数据类型的大小,例如int类型占4个字节,m=4
n代表的是元素个数。
如下图:
存储类型为整型,即占4个字节。利用便宜公式:4*(n-1)可得:
第一个元素1,通过0x11偏移0个字节找到
第二个元素2,通过0x11偏移4个字节找到
第三个元素3,通过0x11偏移8个字节找到
2.3.3 分离式结构
对于不同的数据类型,变量会记住首元素地址所在的地址,然后按照偏移公式找到对应的地址,再根据地址找对应的元素。
如下图:
存储的数据为地址,地址也占4个字节。利用便宜公式:4*(n-1)可得:
第一个元素A,通过0x11偏移0个字节找到所在地址是0x21,然后再根据0x21去找元素所在位置。
第二个元素2,通过0x11偏移4个字节找到所在地址是0x31,然后再根据0x31去找元素所在位置。
第三个元素3,通过0x11偏移8个字节找到所在地址是0x41,然后再根据0x41去找元素所在位置。
2.3.4 顺序表的时间复杂度
顺序表的增加和删除有两种方式。
无论是增加还是删除,在头部操作时间复杂度都是O(n),在尾部操作时间复杂度都是O(1)
Python中的列表就是一种顺序表。
2.4 链表
2.4.1 定义
元素放在通过链接构造起来的一系列存储块中,存储是非连续的。
2.4.2 单链表
单链表是链表的一种形式,每个节点包含两个域,一个元素域,一个链接域,最后一个节点的链接域则指向一个空值。
上图中,head是头结点,指向第一个节点,第一个节点的next指向第二个节点。
2.4.3 python实现单链表
- 首先要创建一个节点,节点包含两个两部分,一个是属性值,一个是next域
- 然后创建一个链表,包含头指针和链表方法
- 实现链表的添加元素、删除元素、查找元素等方法。
- 单链表可以为空表,所以初始化时,可以不传入节点参数,
- 遍历节点的时候,要用临时指针,不能使用头指针,因为头指针永远指向第一个节点
2.4.4 单链表代码实现
"""
@Time : 2024/1/16 21:21
@Author :Ceyyen_Koo
@File :my_node.py
@IDE :PyCharm
@coding: utf-8
@DESC:单链表的单个节点
"""
class MyNode(object):
def __init__(self, value):
self.value = value
self.next = None
"""
@Time : 2024/1/16 21:23
@Author :Ceyyen_Koo
@File :single_linked_list.py
@IDE :PyCharm
@coding: utf-8
@DESC:单链表
注意点:
遍历、添加的时候不能用头指针,要新增一个临时的指针
"""
from my_node import MyNode
class SingleLinkedList:
def __init__(self, node=None):
# 设置头指针,使其指向节点
self.head = node
# 判断单链表是否为空
def is_empty(self):
if self.head is None:
return True
else:
return False
# 获取单链表的长度
def length(self):
size = 0
cur_pointer = self.head
while cur_pointer is not None:
size += 1
cur_pointer = cur_pointer.next
return size
# 遍历单链表所有的结点,以列表的形式返回
def loop_list(self):
cur_pointer = self.head
new_list = list()
while cur_pointer is not None:
new_list.append(cur_pointer.value)
cur_pointer = cur_pointer.next
return new_list
# 头部添加元素方法
def append_start(self, val):
print("单链表头部添加元素开始")
head_node = MyNode(val)
cur_pointer = self.head
self.head = head_node
self.head.next = cur_pointer
print("单链表头部添加元素结束")
# 指定位置添加元素
def append_middle(self, val, pos):
"""
:param val: 新增的元素值
:param pos: 新增的位置,例如第0位
:return: None
"""
print("单链表指定位置添加元素开始")
if pos == 0:
print(f"开始执行头部添加元素方法")
self.append_start(val)
print(f"头部添加元素方法执行完毕")
elif pos >= self.length():
print(f"开始执行尾部添加元素方法")
self.append_end(val)
print(f"头部添加元素方法执行完毕")
else:
cur_pointer = self.head
for i in range(pos - 1):
cur_pointer = cur_pointer.next
new_node = MyNode(val)
new_node.next = cur_pointer.next
cur_pointer.next = new_node
print("单链表指定位置添加元素结束")
# 尾部添加元素方法
def append_end(self, val):
print("单链表尾部添加元素开始")
new_child_node = MyNode(val)
cur_pointer = self.head
# 遍历所有结点,寻找最后一个结点
if cur_pointer is None:
self.head = new_child_node
else:
while cur_pointer.next is not None:
cur_pointer = cur_pointer.next
cur_pointer.next = new_child_node
print("单链表尾部添加元素结束")
# 删除尾结点
def del_tail(self):
if self.head is None:
return
if self.length() == 1:
self.head = None
return
if self.length() == 2:
self.head = self.head.next
return
cur_pointer = self.head
for i in range(self.length() - 2):
cur_pointer = cur_pointer.next
cur_pointer.next = None
# 删除节点
def del_element(self, val=None, pos=None):
"""
删除元素节点,可传递两个参数。要么只传pos,要么只传val,pos优先级高于val
:param val: 删除元素的值
:param pos: 指定删除元素的位置,例如第0位,第1位
:return: None
"""
if pos is not None:
# 执行按位置删除
# 判断传入pos是否合法
if pos == 0:
# 删除头结点
self.head = self.head.next
return
else:
cur_pointer = self.head
for i in range(pos - 1):
cur_pointer = cur_pointer.next
if pos >= self.length() - 1:
cur_pointer.next = None
else:
cur_pointer.next = cur_pointer.next.next
return
elif val is not None:
pre_pointer = None # 添加临时辅助指针
# 按值删除
cur_pointer = self.head
while cur_pointer is not None:
if cur_pointer.value == val:
# 要删除的元素在头结点
if cur_pointer == self.head:
self.head = self.head.next
else:
pre_pointer.next = cur_pointer.next
return
else:
pre_pointer = cur_pointer
cur_pointer = cur_pointer.next
# 查找结点是否存在
def find_element(self, val):
cur_pointer = self.head
while cur_pointer is not None:
if cur_pointer.value == val:
return True
else:
cur_pointer = cur_pointer.next
return False
if __name__ == '__main__':
# 创建一个结点
my_node = MyNode(10)
# 创建一个单链表
sing_list = SingleLinkedList(my_node)
# 查看单链表头指针指向
print(sing_list.head)
# 查看单链表头指针指向的结点元素
print(sing_list.head.value)
print(sing_list.head.next)
print(sing_list.length())
print(sing_list.loop_list())
sing_list.append_end(20)
sing_list.append_end(30)
sing_list.append_end(40)
print(f"头结点指向元素的值是{sing_list.head.value}")
# 循环遍历单链表
print(sing_list.head.value)
print(sing_list.head.next.value)
print(sing_list.head.next.next)
print(sing_list.is_empty())
print(sing_list.length())
print(sing_list.loop_list())
sing_list2 = SingleLinkedList()
print(sing_list2.is_empty())
print(sing_list2.length())
print(sing_list2.loop_list())
print(sing_list2.append_start(20))
print(sing_list2.append_start(30))
print(sing_list2.append_start(40))
print(sing_list2.length())
print(sing_list2.loop_list())
sing_list2.append_middle(66, 2)
print(sing_list2.loop_list())
sing_list2.append_middle(77, 0)
print(sing_list2.loop_list())
sing_list2.append_middle(88, 4)
print(sing_list2.loop_list())
sing_list2.append_middle(99, 6)
print(sing_list2.loop_list())
sing_list2.del_element(pos=2) # 从0位开始,删除第2位的元素
print(sing_list2.loop_list())
sing_list2.del_element(pos=0) # 删除头结点
print(sing_list2.loop_list())
sing_list2.del_element(pos=4) # 删除尾节点
print(sing_list2.loop_list())
sing_list2.del_element(val=20) # 删除元素值为20的结点
print(sing_list2.loop_list())
sing_list2.del_element(val=66) # 删除元素值为66的结点
print(sing_list2.loop_list())
sing_list2.del_element(val=40) # 删除元素值为40的结点
print(sing_list2.loop_list())
print(sing_list2.find_element(88))
sing_list2.append_start(36)
print(sing_list2.find_element(36))
print(sing_list2.loop_list())
2.5 栈
栈是一种运算受限的线性表,因为只允许在表的一端进行插入和删除运算,而这一端被称为栈顶。
2.5.1 特点
栈的特点是先进后出。进出栈的时间复杂度为O(1)
2.5.2 应用
函数中存在局部变量和全局变量,函数的局部变量会随着函数完毕而销毁。即全局变量先入栈,然后局部变量入栈,函数完毕后,局部变量出栈。
2.5.3 代码实现
class MyStack(object):
"""
先进后出
"""
def __init__(self):
self.__items = []
# 进栈
def push(self, item):
self.__items.append(item)
# 出栈
def pop(self):
return self.__items.pop()
def loop_list(self):
new_list = list()
num = 0
for item in self.__items:
new_list.insert(-num, item)
num += 1
return f"栈顶{new_list}栈底"
if __name__ == '__main__':
my_stack = MyStack()
my_stack.push(1)
my_stack.push(2)
my_stack.push(3)
print(my_stack.loop_list())
print(my_stack.pop())
print(my_stack.loop_list())
print(my_stack.pop())
print(my_stack.loop_list())
2.6 队列
队列是一种特殊的线性表,只允许在头部进行删除操作,在尾部进行插入操作,进行插入操作的端称为队尾,进行删除操作的端称为队头。
2.6.1 作用及应用
例如:用户发起多个请求任务,这些请求会进入队列,后端开启多个应用程序从队列中获取任务。
起到了缓冲压力的作用。
2.6.2 代码实现队列
class MyQueue(object):
def __init__(self):
self.__queue = []
# 尾部插入
def append_end(self, val):
self.__queue.append(val)
# 头部删除
def del_head(self):
return self.__queue.pop(0)
# 判断队列是否为空
def is_empty(self):
return len(self.__queue) == 0
# 返回队列的大小
def size(self):
return len(self.__queue)
# 查看当前队列
def loop_list(self):
print("队头:", end=" ")
for i in range(self.size()):
print(self.__queue[i], end=" ")
print("队尾")
if __name__ == '__main__':
my_queue = MyQueue()
my_queue.append_end(20)
my_queue.append_end(30)
my_queue.append_end(40)
my_queue.loop_list()
2.7 双端队列
2.7.1 什么是双端队列
双端队列是一种具有队列和栈的性质的数据结构,双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行,双端队列可以在队列任意一端入队和出队。
2.7.2 双端队列代码实现
class MyDeque(object):
def __init__(self):
self.__deque = []
# 队头加入元素
def append_head(self, val):
self.__deque.insert(0, val)
# 队尾加入元素
def append_tail(self, val):
self.__deque.append(val)
# 队头删除元素
def del_head(self):
self.__deque.pop(0)
# 队尾删除元素
def del_tail(self):
self.__deque.pop(-1)
# 返回队列的大小
def size(self):
return len(self.__deque)
# 判断双端队列是否为空
def is_empty(self):
return self.size() == 0
# 查看当前队列
def loop_list(self):
print("队头:", end=" ")
for i in self.__deque:
print(i, end=" ")
print("队尾")
if __name__ == '__main__':
my_deque = MyDeque()
print(my_deque.is_empty())
my_deque.append_head(10)
my_deque.append_head(20)
my_deque.append_head(30)
my_deque.loop_list()
print("队头删除")
my_deque.del_head()
my_deque.loop_list()
print("队尾删除")
my_deque.del_tail()
my_deque.loop_list()
my_deque.append_tail(40)
my_deque.append_tail(50)
my_deque.append_tail(60)
my_deque.loop_list()
print("队尾删除")
my_deque.del_tail()
my_deque.loop_list()