数据的存储和组织形式
程序 = 数据结构 + 算法
一. 算法介绍
概述目的
都是可以提高程序的效率(性能), 面试高频考点
数据结构介绍
数据的存储和组织形式, 同样的空间, 不同的结构, 存储的数据不同, 操作方式也不同
算法介绍
为了解决实际的业务问题, 而考虑出来的方法和思路 => 算法
算法具有独立性, 即: 他是解决问题的思路和方法, 不依赖于语言, 用C能实现, 用Java能实现, 用python也能实现
算法5大特性
-
有输入 算法具有0个或者多个输入
-
有输出 算法至少有1个或多个输出
-
有穷性 算法在有限的步骤之后会自动结束而不会死循环, 并且每个步骤都在可接受的时间内完成
-
确定性 算法中的每一步都具有确定的含义, 不会出现二义性
-
可执行 算法的每一步都是可行的, 执行有限的次数完成
案例
经典案例: a + b + c = 1000, a^2 + b^2 = c^2
方式1: 穷举法, 155秒
import time
start = time.time()
for a in range(1001):
for b in range(1001):
for c in range(1001):
if a ** 2 + b ** 2 == c ** 2 and a + b + c == 1000:
print(a, b, c)
end = time.time()
print(f'执行了: {end - start}')
方式2: 穷举法, 88.9秒
import time
start = time.time()
for a in range(1001):
for b in range(1001):
for c in range(1001):
if a + b + c == 1000 and a ** 2 + b ** 2 == c ** 2:
print(a, b, c)
end = time.time()
print(f'执行了: {end - start}')
方式3: 代入法(从a, b可得c, 减少一层循环), 0.236秒
import time
start = time.time()
for a in range(1001):
for b in range(1001):
c = 1000 - a - b
if a ** 2 + b ** 2 == c ** 2:
print(a, b, c)
end = time.time()
print(f'执行了: {end - start}')
衡量算法的优略
不能单纯的只依靠程序的执行时间, 因为程序执行时间也会收到机器, 硬件, 其他硬件的影响.
所以可以假定 计算机执行每个步骤的时间都是固定的, 只考虑算法的 执行步骤即可.
代码执行总时间(T) = 操作步骤数量 * 操作步骤执行时间
时间复杂度
时间复杂度T 是关于n的函数
概述
可以反应一个算法的优略, 他表示一个算法随着问题规模的变化而表现出来的主要趋势.

大O标记法
忽略次要条件, 只考虑主要条件(随着问题规模变化而变化的内容), 就会得到: 大O标记法, 标记的效率
例如: 4中 方式1穷举法 的时间复杂度为 : O(n³)
例如: 4中 方式3代入法 的时间复杂度为 : O(n²)
时间复杂度计算
-
基本操作: O(1) => 固定步骤
-
顺序结构: 加法
-
循环结构: 乘法
-
分支结构: 取最大项
-
只考虑主要条件, 不考虑次要条件
细节
-
如非特别说明, 考虑时间复杂度都是考虑: 最坏时间复杂度, 它算是算法的一种保证
-
常见的时间复杂度, 效率从高到低分别是:
O(1) > O(logn) > O(n) > O(nlogn) > O(n²) > O(n³)
空间复杂度(了解)
-
空间复杂度指的是: 算法的运算过程中, 临时占用的空间大小, 也用大O标记法标记, 具体如下:
O(1) < O(logn) < O(n) < O(n²) < O(n³)
-
开发思想: 时空转换, 即: 用空间换空间 / 用空间换时间
-
数据结构是算法的载体
二. 数据结构
线性结构
特点:每个节点都只能有1个子节点(后继节点) 和 1个父节点(前驱节点)
代表:列表, 栈, 列表, 链表...
顺序表
概述: 属于线性结构的一种, 例如: 栈, 队列都属于顺序表
特点: 所有元素在内存中以 连续 的内存空间 来存
分类
一体式存储: 地址和数据一起存储
分离式存储: 地址和数据分离存储
扩容策略
-
每次扩容增加固定的条数目, 拿时间环空间
-
每次扩容容量翻倍, 拿空间换时间
细节:
顺序表主要存储 数据区 和信息区, 数据区和信息区在一起的叫: 一体式存储, 分开存储 的叫: 分离式存储
顺序表增删
方式1: 末尾增删: 时间复杂度: O(1)
方式2: 中间增删(非保序) 时间复杂度: O(1)
方式3: 中间增删(保序) 时间复杂度: O(n)
链表
概述: 他是由节点组成, 每个节点由 数值域 和 地址域 组成
特点: 所有元素在内存中以 非连续 的内存空间来存储, 即: 有地就行
链表介绍
-
概述: 属于线性结构, 即: 每个节点都只能有1个子节点(后继节点) 和 1个父节点(前驱节点)
链表可以看作是 用链条把所有节点连接起来的 一种结构
-
节点概述: 由元素域(数值域) 和地址域(连接域)组成, 其中 数值域存储的是 数值, 地址域存储的是下个节点的地址
分类(根据节点)

-
单向链表:
每个节点由1个数值域和1个地址域组成, 且每个节点的地址域指向下个节点的地址, 最后1个节点的地址域为None
-
单项循环链表:
每个节点由1个数值域和1个地址域组成, 且每个节点的地址域指向下个节点的地址, 最后1个节点的地址域为: 第1个节点的地址
-
双向链表
每个节点由1个数值域和2个地址域组成, 且每个节点的地址域分别指向前1个节点的地址 和 后1个节点的地址.
第1个节点的(前)地址域为: None, 最后1个节点的(后)地址域为: None
-
双项循环链表
每个节点由1个数值域和2个地址域组成, 且每个节点的地址域分别指向前1个节点的地址 和 后1个节点的地址.
第1个节点的(前)地址域为: 最后1个节点的地址,
最后1个节点的(后)地址域为: 第1个节点的地址
单链表演示
分析
节点类: Single Node
属性:
item 表示: 数值域
next 表示: 地址域, 即: 指向下个节点的地址
链表类: SingleLinkedList
属性:
head 表示: 头节点, 即: 默认指向链表第一个节点的地址
行为:
is_empty(self)链表是否为空
length(self)链表长度
travel(self) 遍历整个链表
add(self,item)链表头部添加元素
append(self,item)链表尾部添加元素
insert(self,pos,item)指定位置添加元素
remove(self,item)删除节点
search(self,item)查找节点是否存在
代码
# 定义节点类
class SingleNode(object):
# 初始化节点属性
def __init__(self, item):
self.item = item
self.next = None
# 定义链表类
class SingleLinkedList(object):
# 初始化链表属性
def __init__(self, head=None):
self.head = head
# 判断链表是否为空
def is_empty(self):
return self.head is None
# 链表长度
def length(self):
# 定义计数器
count = 0
# 定义当前节点, 初值指向头节点
cur = self.head
# 判断当前节点是否为空, 不为空遍历
while cur is not None:
# 计数器加1
count += 1
# 设置当前节点指向下个节点
cur = cur.next
# 返回链表长度计数器
return count
# 遍历链表
def travel(self):
# 定义当前节点, 初值指向头节点
cur = self.head
# 判断当前节点是否为空, 不为空遍历
while cur is not None:
print(cur.item)
# 设置当前节点指向下个节点
cur = cur.next
# 头插法
def add(self, item):
# 将要添加的数据封装成节点
new_node = SingleNode(item)
# 设置新节点的地址域为头节点
new_node.next = self.head
# 将头节点为新节点
self.head = new_node
# 尾插
def append(self, item):
# 将要添加的节点封装成节点
new_node = SingleNode(item)
# 判断当前头节点是否为空
if self.head is None:
# 为空直接将头节点设置为新节点
self.head = new_node
else:
# 不为空则遍历链表
cur = self.head
# 判断当前节点的地址域是否不为空
while cur.next is not None:
# 这里是当前节点的地址域不为空, 继续遍历
cur = cur.next
# 这里找到最后节点, 将最后节点的地址域设置为新节点
cur.next = new_node
# 插入到指定位置(索引从0开始)
def insert(self, pos, item):
# 判断插入的位置是否合法
if pos <= 0:
# 小于0, 则在头部插入
self.add(item)
elif pos >= self.length():
# 大于链表长度, 则在尾部插入
self.append(item)
else:
# 定义计数器
count = 0
# 定义存储当前的节点
cur = self.head
# 判断是否为要插入位置的前一个节点
while count < pos - 1:
cur = cur.next
count += 1
# 具体插入动作
new_node = SingleNode(item)
# 顺序不能变
new_node.next = cur.next
cur.next = new_node
# 删除节点
def remove(self, item):
# 当前节点
cur = self.head
# 当前节点的前一个节点
pre = None
while cur is not None:
if cur.item == item:
# 被删除的为头节点
if cur == self.head:
# 将头节点设为当前节点地址域
self.head = cur.next
else:
# 设置当前的前一个节点为当前节点的地址域
pre.next = cur.next
break
else:
# 将当前节点设为前一个节点
pre = cur
# 将当前节点的地址域设为当前节点
cur = cur.next
# 查询元素
def search(self, item):
# 定义变量存储当前节点
cur = self.head
# 遍历
while cur is not None:
if cur.item == item:
return True
else:
cur = cur.next
return False
if __name__ == '__main__':
# 测试节点类
node1 = SingleNode('张三')
print(node1)
print(node1.item)
print(node1.next)
print('-' * 21)
# 测试创建空链表
linkedlist1 = SingleLinkedList()
print(linkedlist1.head)
print('-' * 21)
# 测试创建链表, 头节点指向node1节点
linkedlist2 = SingleLinkedList(node1)
print(linkedlist2.head)
print(linkedlist2.head.item)
print(linkedlist2.head.next)
print('-' * 21)
# 测试头添加
linkedlist2.add('李四')
linkedlist2.add('王五')
# 测试尾部添加
linkedlist2.append('麻子')
linkedlist2.append('孙二')
# 添加到指定位置
linkedlist2.insert(-10, '-10')
linkedlist2.insert(200, '200')
linkedlist2.insert(2, '2')
# 删除元素
linkedlist2.remove('-10')
linkedlist2.remove('2')
linkedlist2.remove('200')
# 查找
print('查找')
print(linkedlist2.search('-2'))
print(linkedlist2.search('麻子'))
print('-' * 21)
# 测试是否为空
print('空')
print(linkedlist1.is_empty())
print(linkedlist2.is_empty())
print('-' * 21)
# 链表长度
print('长度')
print(linkedlist1.length())
print(linkedlist2.length())
print('-' * 21)
# 遍历链表
print('遍历')
print('')
linkedlist1.travel()
linkedlist2.travel()
# print('-' * 21)
非线性结构
特点: 每个节点都可以有n个子节点(后继节点) 和 n个父节点(前驱节点)
代表: 树, 图......








![数学建模笔记——TOPSIS[优劣解距离]法](https://img-blog.csdnimg.cn/img_convert/1d21edf66944b3ca39b4a238b9a0b9f9.png)










