一、 数据结构概论
1. 内存中的存储结构
内存是以字节为基本存储单位,每个基本存储空间都有自己的地址(注意:一个内存地址代表一个字节(8bit)的存储空间)
整型(int):4个字节
字符(char):1个字节,单个字符‘a’占1个字节,字符串‘abc’占3个字节
2. 数据结构的分类
2.1 线性结构
线性结构就是:数据结构中的各个节点具有线性关系
2.1.1 线性数据结构特点
- 线性结构式非空集
- 线性结构所有节点都最多只有一个直接前驱节点和一个直接后继节点
2.1.2 线性结构的实际存储方式
顺序表
将元素顺序的存放在一块 连续的存储区里,元素间的顺序关系由他们的存储顺序自然表示
顺序表又分为:
- 一体式存储
- 分离式存储
一体式存储
地址(域)和数据(域)在一起
分离式存储
地址(域)和数据(域)分开
num = [10, “A”, “ABC”]
地址的大小为4字节是固定的,我们可以不存储数据,而是存储地址
结论: 无论是 一体式结构 还是 分离式结构,顺序表在获取数据的时候直接通过下标偏移就可以找到数据所在空间的地址,而无需遍历后才可以获取地址,所以顺序表在获取地址操作时的时间复杂度:O(1)
顺序表的扩充
数据区更换为存储空间更大的区域时
扩充策略:
1️⃣ 每次扩充 增加固定数目的存储位置,如每次扩充增加10个元素位置,这种策略可称为线性增长
特点:节省空间,但是扩充频繁,操作次数多
2️⃣ 每次扩充 容量加倍,如每次扩充增加1倍存储空间
特点:减少了扩充操作的执行次数,但可能会浪费空间资源,以空间换时间,推荐的方式
**顺序表-增加元素
顺序表删除元素
顺序表存储方式的不足
需要连续的存储空间
2.1.3 典型线性结构
栈、队列等
2.1.4 链表结构
链表结构分类
单向链表:节点由1个元素域(也叫:数值域)和1个链接域(也叫:地址域)组成,每个节点的链接域都指向下个节点的地址,最后一个节点的地址域为:None。
a. 表元素域(数值域)item用来存放具体的数据
b. 链接域(地址域)next用来存放下一个节点的位置
c. 变量head指向链表的头节点(首节点)的位置,从head出发能找到表中的任意节点
单向循环链表:节点由1个元素域(也叫:数值域)和1个链接域(也叫:地址域)组成,每个节点的链接域都指向下个节点的地址,最后一个节点的地址域为:第一个节点的地址。
双向链表:节点由1个元素域(也叫:数值域)和2个链接域(也叫:地址域)组成,每个节点的前链接域指向上个节点的地址,每个节点的后链接域都指向下一个节点的地址。第1个节点的前链接域和最后1个节点的后链接域都为None。
双向循环列表:节点由1个元素域(也叫:数值域)和2个链接域(也叫:地址域)组成,每个节点的前链接域指向上个节点的地址,每个节点的后链接域都指向下一个节点的地址。第一个节点的前链接域指向最后一个节点的地址,最后一个节点的后链接域指向第一个节点的地址。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值None。
链表代码模拟
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project :python_project
@File :main.py
@Create at :2025/7/1 14:28
@version :V1.0
@Author :erainm
@Description : 测试类
'''
# 导包
from singlenode import SingleNode
from singlelinkedlist import SingleLinkedList
if __name__ == '__main__':
sn1 = SingleNode('张三')
sn2 = SingleNode('李四')
sn3 = SingleNode('王五')
print(sn1.data)
print(sn1.next)
print('-' * 20)
ll = SingleLinkedList(sn1)
print(f'链表对象:{ll}')
print(f'链表头节点:{ll.head}')
print('-' * 20)
print(ll.is_empty())
print(ll.length())
ll.add_head(sn2)
ll.append_end(sn3)
ll.insert_pos(2,"赵六")
ll.insert_pos(-1,"kimi")
ll.remove("赵六")
ll.remove("kimi")
ll.travel()
print(ll.search("kimi"))
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project :python_project
@File :singlenode.py
@Create at :2025/7/1 14:24
@version :V1.0
@Author :erainm
@Description : 链表的节点
'''
class SingleNode(object):
def __init__(self, data):
self.data = data
self.next = None
def __str__(self):
return str(self.data)
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project :python_project
@File :singlelinkedlist.py
@Create at :2025/7/1 14:26
@version :V1.0
@Author :erainm
@Description : 链表类
'''
from itertools import count
from linked_list.singlenode import SingleNode
class SingleLinkedList:
# 初始化属性,用head属性指向:链表的头节点
def __init__(self,node=None):
self.head = node
def __str__(self):
return f"SingleLinkedList(head={self.head.data if self.head else None})"
# is_empty(self) 判断链表是否为空
def is_empty(self):
return self.head is None
# length(self) 链表长度
def length(self):
count = 0
current = self.head
while current is not None:
count += 1
current = current.next
return count
# travel(self) 遍历整个链表
def travel(self):
current = self.head
while current:
print(current.data, end=' -> ')
current = current.next
print("None")
# add_head(self, item) 链表头部添加节点
def add_head(self,data):
new_node = SingleNode(data)
# 设置新节点指向旧的头节点
new_node.next = self.head
# 将新节点设置为头节点
self.head = new_node
# append_end(self, item) 链表尾部添节点
def append_end(self,data):
new_node = SingleNode(data)
# 判断列表书否为空,为空则新节点即为头节点
if self.is_empty():
self.head = new_node
else:
# 当前节点
curr = self.head
while curr.next is not None:
curr = curr.next
curr.next = new_node
# insert_pos(self, pos, item) 指定位置添加节点
def insert_pos(self, pos, data):
new_node = SingleNode(data)
if pos <= 0:
self.add_head(data)
elif pos >= self.length():
self.append_end(data)
else:
# 定义变量count 表示:插入前的那个元素的索引
count = 0
# 定义变量curr 表示:当前元素(节点),找到插入位置前的那个节点
curr = self.head
# 遍历 判断:只要count的值 小于 pos - 1 要小,就继续循环,循环结束后,count = pos -1
while count < pos - 1:
count += 1
curr = curr.next
new_node.next = curr.next
curr.next = new_node
# remove(self, item) 删除节点
def remove(self,item):
curr = self.head
pre = None
while curr is not None:
if curr.data == item:
# 判断要删除的节点是头节点
if curr == self.head:
self.head = curr.next
curr = None
else:
pre.next = curr.next
curr = None
break
else:
pre = curr
curr = curr.next
# search(self, item) 查找节点是否存在
def search(self,item):
curr = self.head
while curr is not None:
if curr.data == item:
return True
else:
curr = curr.next
return False
2.2 非线性结构
非线性结构就是:数据结构中各个节点之间具有多个对应关系
非线性结构特点
- 非线性结构式非空集
- 非线性结构的一个节点可能有多个直接前驱节点和多个直接后继节点
典型非线性结构
树结构和图结构等
3. 树形结构
3.1 树的概念
树(tree)就是一种非线性结构
树的特点:
① 每个节点有零个或多个子节点
② 没有父节点的节点称为根节点
③ 每一个非根节点有且只有一个父节点
④ 除了根节点外,每个子节点可以分为多个不相交的子树
树的种类
无序树:树中任意节点的子节点之间没有顺序关系,这种树成为无序树,也称自由树
有序树:树中任意节点的子节点之间有顺序关系,这种树称为有序树
霍夫曼树(用于信息编码):带权路径最短的二叉树称为哈夫曼树或最优二叉树
B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多余两个子树
二叉树:每个节点最多含有两个子树的树称为二叉树

二叉树的种类
a. 完全二叉树:对于一个二叉树,假设其深度为d(d>1)。除了d层外,其它各层的节点数据均已达到最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树。
b. 满二叉树:所有接节点都在最底层的完全二叉树
c. 非完全二叉树:也就是第d层所有节点从左向右没有连续地紧密排列
d. 平衡二叉树(AVL树):当且仅当任何节点的两颗子树的高度差不大于1的二叉树
为什么需要平衡二叉树:防止树退化为链表
e. 排序二叉树
二叉树的存储
二叉树的存储方式:链式存储(每个节点有两个指针域)
3.2 树的应用场景和数据库索引
树的应用场景
典型的应用:数据库的索引
聚簇索引
非聚簇索引
3.3 二叉树的概念和性质
概念
二叉树是每个节点最多有两个子树的树结构
通常子树被称作“左子树(left subtree)”和“右子树(right subtree)”
二叉树的性质
3.4 二叉树的广度优先遍历
广度优先可以找到最短路径:
相当于层次遍历,先把第1层给遍历完,看有没有终点,再把第2层遍历完,看有没有终点。
完全二叉树
二叉树广度优先 - 插入节点
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project :python_project
@File :tree_structure.py
@Create at :2025/7/2 16:21
@version :V1.0
@Author :erainm
@Description : 自定义代码 模拟树形结构:二叉树
'''
class Node:
def __init__(self, data):
self.data = data
self.lchild = None
self.rchild = None
class BinaryTree:
def __init__(self,root = None):
self.root = root # root 表示根节点
# 自定义函数,往二叉树中添加元素
def add(self,data):
# 判断根节点是否为空,若为空,则添加内容为根节点
if self.root is None:
self.root = Node(data)
return
# 创建队列,用于记录二叉树中的每个元素
queue = []
# 添加根节点到队列中
queue.append(self.root)
# 循环查找要添加的元素,在二叉树中 具体的位置
while True:
root_node = queue.pop(0) # pop 根据索引删除元素,并返回该元素
# 判断当前节点的左子树是否为空,为空就将新节点添加到这里,不为空就将左子树添加到队列中
if root_node.lchild is None:
root_node.lchild = Node(data)
break
else:
# 说明左子树不为空,就将其添加到队列中(一会可以循环到)
queue.append(root_node.lchild)
# 判断当前节点的右子树是否为空,为空就将新节点添加到这里,不为空就将右子树添加到队列中
if root_node.rchild is None:
root_node.rchild = Node(data)
break
else:
# 说明右子树不为空,就将其添加到队列中(一会可以循环到)
queue.append(root_node.rchild)
# 定义函数,遍历二叉树(广度优先)
def breadth_travel(self):
# 判断根节点是否为空,为空直接返回
if self.root is None:
return
queue = []
queue.append(self.root)
while len(queue) > 0:
node = queue.pop(0)
print(node.data, end=' ')
if node.lchild is not None:
queue.append(node.lchild)
if node.rchild is not None:
queue.append(node.rchild)
# 测试创建节点和为叉树
def demo01():
# 创建节点对象
node = Node("张三")
print(f"节点的内容:{node.data}")
print(f"节点的左子树:{node.lchild}")
print(f"节点的右子树:{node.rchild}")
# 测试链表
bt = BinaryTree(node)
print(f"二叉树对象:{bt}")
print(f"二叉树的根节点的内容:{bt.root.data}")
# 测试队列的pop函数
def demo02():
queue = []
queue.append('A')
queue.append('B')
queue.append('C')
print(queue)
print(queue.pop(0))
print(queue.pop(0))
print(queue)
# 测试广度优先 遍历二叉树
def demo03():
bt = BinaryTree()
bt.add("A")
bt.add("B")
bt.add("C")
bt.add("D")
bt.add("E")
bt.add("F")
bt.breadth_travel()
if __name__ == '__main__':
# demo01()
# demo02()
demo03()
3.5 二叉树的三种深度优先遍历
深度优先往往可以很快找到搜索路径:
比如:先找到一个节点看看是不是终点,若不是继续往深层去找,直到找到终点。
中序、先序、后序 属于深度优先算法
先序(查找顺序):根、左、右
中序(查找顺序):左、根、右
后序(查找顺序):左、右、根
记忆小密法:看根的位置,在前就是先序,在中,就是中序,最后,就是后序。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project :python_project
@File :tree_structure.py
@Create at :2025/7/2 16:21
@version :V1.0
@Author :erainm
@Description : 自定义代码 模拟树形结构:二叉树
'''
class Node:
def __init__(self, data):
self.data = data
self.lchild = None
self.rchild = None
class BinaryTree:
def __init__(self,root = None):
self.root = root # root 表示根节点
# 自定义函数,往二叉树中添加元素
def add(self,data):
# 判断根节点是否为空,若为空,则添加内容为根节点
if self.root is None:
self.root = Node(data)
return
# 创建队列,用于记录二叉树中的每个元素
queue = []
# 添加根节点到队列中
queue.append(self.root)
# 循环查找要添加的元素,在二叉树中 具体的位置
while True:
root_node = queue.pop(0) # pop 根据索引删除元素,并返回该元素
# 判断当前节点的左子树是否为空,为空就将新节点添加到这里,不为空就将左子树添加到队列中
if root_node.lchild is None:
root_node.lchild = Node(data)
break
else:
# 说明左子树不为空,就将其添加到队列中(一会可以循环到)
queue.append(root_node.lchild)
# 判断当前节点的右子树是否为空,为空就将新节点添加到这里,不为空就将右子树添加到队列中
if root_node.rchild is None:
root_node.rchild = Node(data)
break
else:
# 说明右子树不为空,就将其添加到队列中(一会可以循环到)
queue.append(root_node.rchild)
# 定义函数,遍历二叉树(广度优先)
def breadth_travel(self):
# 判断根节点是否为空,为空直接返回
if self.root is None:
return
queue = []
queue.append(self.root)
while len(queue) > 0:
node = queue.pop(0)
print(node.data, end=' ')
if node.lchild is not None:
queue.append(node.lchild)
if node.rchild is not None:
queue.append(node.rchild)
# 定义函数,遍历二叉树(深度优先 - 前序:根、左、右)
def preorder_travel(self, root):
if root is not None:
print(root.data, end=' ')
self.preorder_travel(root.lchild) # 递归获取左子树
self.preorder_travel(root.rchild) # 递归获取右子树
# 定义函数,遍历二叉树(广度优先 - 中序:左、根、右)
def inorder_travel(self, root):
if root is not None:
self.inorder_travel(root.lchild) # 递归获取左子树
print(root.data, end=' ')
self.inorder_travel(root.rchild) # 递归获取右子树
# 定义函数,遍历二叉树(广度优先 - 后序:左、右、根)
def postorder_travel(self, root):
if root is not None:
self.postorder_travel(root.lchild) # 递归获取左子树
self.postorder_travel(root.rchild) # 递归获取右子树
print(root.data, end=' ')
# 测试创建节点和为叉树
def demo01():
# 创建节点对象
node = Node("张三")
print(f"节点的内容:{node.data}")
print(f"节点的左子树:{node.lchild}")
print(f"节点的右子树:{node.rchild}")
# 测试链表
bt = BinaryTree(node)
print(f"二叉树对象:{bt}")
print(f"二叉树的根节点的内容:{bt.root.data}")
# 测试队列的pop函数
def demo02():
queue = []
queue.append('A')
queue.append('B')
queue.append('C')
print(queue)
print(queue.pop(0))
print(queue.pop(0))
print(queue)
# 测试广度优先 遍历二叉树
def demo03():
bt = BinaryTree()
bt.add("A")
bt.add("B")
bt.add("C")
bt.add("D")
bt.add("E")
bt.add("F")
bt.breadth_travel()
# 测试深度优先 - 先序
def demo04():
bt = BinaryTree()
bt.add("0")
bt.add("1")
bt.add("2")
bt.add("3")
bt.add("4")
bt.add("5")
bt.add("6")
bt.add("7")
bt.add("8")
bt.add("9")
bt.preorder_travel(bt.root)
# 测试深度优先 - 中序
def demo05():
bt = BinaryTree()
bt.add("0")
bt.add("1")
bt.add("2")
bt.add("3")
bt.add("4")
bt.add("5")
bt.add("6")
bt.add("7")
bt.add("8")
bt.add("9")
bt.inorder_travel(bt.root)
# 测试深度优先 - 后序
def demo06():
bt = BinaryTree()
bt.add("0")
bt.add("1")
bt.add("2")
bt.add("3")
bt.add("4")
bt.add("5")
bt.add("6")
bt.add("7")
bt.add("8")
bt.add("9")
bt.postorder_travel(bt.root)
if __name__ == '__main__':
# demo01()
# demo02()
# demo03() # A B C D E F
# demo04() # 0 1 3 7 8 4 9 2 5 6
# demo05() # 7 3 8 1 9 4 0 5 2 6
demo06() # 7 8 3 9 4 1 5 6 2 0
3.6 二叉树遍历结果反推二叉树的结构
思路:
通过先序遍历可以确定哪个元素是根节点,通过中序遍历可以指导左子树都有哪些节点、右子树、都有哪些节点。
① 有了树(先序、中序表示),根据先序确认根节点,中序确定左子树、右子树,有了左子树和右子树,相当于2颗树
② 重复步骤一,直到划分完毕