数据结构算法

单链表

链表代码前置知识:

  • cur.next = new_node:代表current的next指针指向新节点
  • 链表有数值域和链接域

代码实现:节点类

包含两个属性:

  • item:数值域(元素域)
  • next :地址域(链接域) 不是他的地址,而是他的下一个节点地址
1
2
3
4
5
6
# 自定义SingleNode类,表示节点类
class SingleNode:
# 初始化属性
def __init__(self, item):
self.item = item # 元素域(数值域)
self.next = None # 链接域(地址域)

代码实现:链表类

该部分代码包含一个属性

  • head 表示头结点 指向链表的第一个节点

多个行为:

  • is_empty(self) :链表是否为空
  • length(self):判断链表长度
  • traverse(self):遍历整个链表
  • add(self,item) :给链表头部添加元素
  • append(self,item):链表尾部添加元素
  • insert(self,item):指定位置添加元素
  • remove(self,item):删除节点
  • search(self,item):查询节点是否存在

判断是否为空

1
2
3
4
5
6
7
8
9
10
11
12
# is_empty 链表是否为空
def is_empty(self):
# 思路:判断头结点是否为None,如果为None,则链表为空
# 写法一 if else
# if self.head is None:
# return True
# else:
# return False
# 写法二
# return True if self.head is None else False
# 写法三(推荐)
return self.head is None

判断链表长度

1
2
3
4
5
6
7
8
9
10
def length(self):  # 判断链表长度
# 默认从头开始
cur = self.head
# 初始化计数器
count = 0
# 开始遍历 ,只要当前节点不为空,就一直循环
while cur is not None:
count += 1
cur = cur.next
return count

遍历整个链表

1
2
3
4
5
6
7
def traverse(self):  # 遍历整个链表
# 找到默认开头节点
cur = self.head
# 定义循环判断
while cur is not None:
print(f"数值域:{cur.item}", end="")
cur = cur.next

在链表头部添加元素

1
2
3
4
5
6
7
def add(self, item):  # 给链表头部添加元素
# 1、创建一个新的节点
new_node = SingleNode(item)
# 设置新节点的地址域,指向头节点
new_node.next = self.head
# 设置头节点为的新节点
self.head = new_node

在链表尾部添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
def append(self, item):  # 链表尾部添加元素
new_node = SingleNode(item)
if self.is_empty():
self.head = new_node
else:
# 走到这里,说明链表不为空,需要找到链表的尾节点
# 默认从头开始
cur = self.head
while cur.next is not None:
# 地址后移
cur = cur.next
# 走到这里,cur就是最后一个节点,所以我们设置它的地址指向新的节点
cur.next = new_node

在链表中间添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def insert(self, pos, item):  # 指定位置添加元素
# 头插
if pos <= 0:
self.add(item)
# 尾插
elif pos >= self.length():
self.append(item)
else: # 任意位置
# 走到这里,说明pos给合法的,即:中间值,想到找到插入到哪个元素
cur = self.head
# 定义当前节点的位置
count = 0
while count < pos - 1:
# 走到这里,说明还没有找到插入前的那个节点,就节点后移,计数器+1
cur = cur.next
count += 1
# 走到这里,cur就是插入位置前的那个节点,封装新节点
new_node = SingleNode(item)
# 设置新节点的地址域,指向插入位置前的那个节点的地址域
new_node.next = cur.next
# 设置插入位置前的那个节点的地址域指向新节点
cur.next = new_node

根据数值域删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)的地址域指向当前节点的地址域即可
pre.next = cur.next
cur.next = None
# 走到这里,说明删除成功,返回即可
return
else:
# 走到这里,说明当前节点不是要删除的节点,后移,pre也要后移
pre = cur
cur = cur.next

查找元素

1
2
3
4
5
6
7
8
9
10
11
def search(self, item):  # 查询节点是否存在
# 默认从头节点开始
cur = self.head
# 只要当前节点不为空,就一直循环
while cur is not None:
if cur.item == item:
return True
# 如果当前节点不是要找的节点,后移
cur = cur.next
# 走到这里,所以节点就都找完了,还没有找到 ,返回False
return False

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# 自定义SingleLinkList类,表示链表
class SingleLinkList:
def __init__(self, node=None):
self.head = node

# is_empty 链表是否为空
def is_empty(self):
# 思路:判断头结点是否为None,如果为None,则链表为空
# 写法一 if else
# if self.head is None:
# return True
# else:
# return False
# 写法二
# return True if self.head is None else False
# 写法三(推荐)
return self.head is None

def length(self): # 判断链表长度
# 默认从头开始
cur = self.head
# 初始化计数器
count = 0
# 开始遍历 ,只要当前节点不为空,就一直循环
while cur is not None:
count += 1
cur = cur.next
return count

def traverse(self): # 遍历整个链表
# 找到默认开头节点
cur = self.head
# 定义循环判断
while cur is not None:
print(f"数值域:{cur.item}", end="")
cur = cur.next

def add(self, item): # 给链表头部添加元素
# 1、创建一个新的节点
new_node = SingleNode(item)
# 设置新节点的地址域,指向头节点
new_node.next = self.head
# 设置头节点为的新节点
self.head = new_node

def append(self, item): # 链表尾部添加元素
new_node = SingleNode(item)
if self.is_empty():
self.head = new_node
else:
# 走到这里,说明链表不为空,需要找到链表的尾节点
# 默认从头开始
cur = self.head
while cur.next is not None:
# 地址后移
cur = cur.next
# 走到这里,cur就是最后一个节点,所以我们设置它的地址指向新的节点
cur.next = new_node

def insert(self, pos, item): # 指定位置添加元素
# 头插
if pos <= 0:
self.add(item)
# 尾插
elif pos >= self.length():
self.append(item)
else: # 任意位置
# 走到这里,说明pos给合法的,即:中间值,想到找到插入到哪个元素
cur = self.head
# 定义当前节点的位置
count = 0
while count < pos - 1:
# 走到这里,说明还没有找到插入前的那个节点,就节点后移,计数器+1
cur = cur.next
count += 1
# 走到这里,cur就是插入位置前的那个节点,封装新节点
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)的地址域指向当前节点的地址域即可
pre.next = cur.next
cur.next = None
# 走到这里,说明删除成功,返回即可
return
else:
# 走到这里,说明当前节点不是要删除的节点,后移,pre也要后移
pre = cur
cur = cur.next

def search(self, item): # 查询节点是否存在
# 默认从头节点开始
cur = self.head
# 只要当前节点不为空,就一直循环
while cur is not None:
if cur.item == item:
return True
# 如果当前节点不是要找的节点,后移
cur = cur.next
# 走到这里,所以节点就都找完了,还没有找到 ,返回False
return False


if __name__ == '__main__':
# 1.1测试节点类
node1 = SingleNode(10)
# 1.2 打印当前节点的元素域 和链接域
print(f"元素域(数值域):{node1.item}")
print(f"链接域(地址域):{node1.next}")
print(f"node1对象{node1}")
print(f"node1类型{type(node1)}")
print("---------------测试链接类-------------------")
# 2.1 测试链接类
my_linklist = SingleLinkList(node1)
print(f"头结点:{my_linklist}")
print(f"头节点的元素域{my_linklist.head.item}") # 10
print(f"头节点的地址域{my_linklist.head.next}") # None
print("---------------测试链表是否为空-------------------")
# 3.1测试链表是否为空
print(my_linklist.is_empty())
print("---------------测试头插-------------------")
my_linklist.add(20)
my_linklist.add(30)
print("---------------测试尾插-------------------")
my_linklist.append(40)
my_linklist.append(50)
print("---------------测试任意位置插入-------------------")

my_linklist.insert(-1, 2)
my_linklist.insert(8, 55)
my_linklist.insert(2, 32)

print("---------------测试删除-------------------")
my_linklist.remove(2)
my_linklist.remove(30)
my_linklist.remove(60)
print("---------------测试查找-------------------")
print(my_linklist.search(2))
print(my_linklist.search(32))

print("---------------测试链表长度-------------------")
print(f"当前链表长度为:{my_linklist.length()}")
print("---------------测试遍历-------------------")
my_linklist.traverse()

排序

时间复杂度:

  • 冒泡排序:
    • 最优的时间复杂度$O(n)$
    • 最坏的时间复杂度$O(n²)$
  • 选择排序:
    • 最优:$O(n)$
    • 最差:$O(n²)$

算法是否稳定:

  • 稳定的排序算法有:插入排序、冒泡排序、鸡尾酒排序、归并排序、二叉树排序、基数排序等;
  • 不稳定排序算法包括:希尔排序、堆排序、快速排序、选择排序等。

下是常见排序算法的时间复杂度、空间复杂度及稳定性总结,按平均时间复杂度排序:

排序算法 平均时间复杂度 最坏时间复杂度 最好时间复杂度 空间复杂度 是否稳定 备注
冒泡排序 O(n²) O(n²) O(n) O(1) 稳定 优化后可提前终止(无交换时停止)。
选择排序 O(n²) O(n²) O(n²) O(1) 不稳定 每次选最小元素交换,可能破坏相同元素相对位置(如 [5, 5, 2])。
插入排序 O(n²) O(n²) O(n) O(1) 稳定 对小规模或基本有序数据效率高。
希尔排序 O(n log n) O(n²) O(n log n) O(1) 不稳定 插入排序的改进版,依赖步长序列。
归并排序 O(n log n) O(n log n) O(n log n) O(n) 稳定 需额外空间合并,适合外部排序(如大数据文件)。
快速排序 O(n log n) O(n²) O(n log n) O(log n) 不稳定 平均最快,但最坏情况(如已排序)退化为O(n²),递归栈空间消耗。
堆排序 O(n log n) O(n log n) O(n log n) O(1) 不稳定 原地排序,但跳跃交换可能破坏稳定性(如 [5, 5, 2])。
计数排序 O(n + k) O(n + k) O(n + k) O(n + k) 稳定 非比较排序,k为数据范围,适合小范围整数。
桶排序 O(n + k) O(n²) O(n) O(n + k) 稳定 数据需均匀分布到桶中,否则退化为O(n²)。
基数排序 O(n × k) O(n × k) O(n × k) O(n + k) 稳定 k为数字位数,需稳定子排序(如计数排序)。

冒泡排序

Bubble Sort核心思想:(带指针)

😌首先,遍历整个数组,找到最小的元素。

  • 假设第一个元素是最小的,然后从第二个元素开始逐个与它比较。

  • 如果发现有比当前假设的最小元素更小的元素,就更新最小元素的索引。

😜当遍历完整个数组后,将找到的最小元素与数组的第一个元素交换位置。

  • 此时,数组的第一个元素就是最小的,已经处于正确的位置。

😛接着,在剩下的未排序元素(从第二个元素开始到最后一个元素)中重复上述步骤。

  • 再次遍历剩余元素,找到其中最小的元素,并与剩余元素中的第一个元素(即数组的第二个元素)交换位置。

😝不断重复这个过程

  • 每次都将剩余未排序元素中的最小元素放置在已排序部分的末尾,直到整个数组都被排序。

bubbleSort

Python实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def bubble_sort(my_list):
# 1、获取列表的长度 n代表列表长度
n = len(my_list)
# 定义循环, 外循环控制比较的轮数 内循环控制比较次数
for i in range(n - 1): # i 0 1 2 3
for j in range(n - 1 - i):
# 具体的比较动作:相邻的两两元素比较,大的往后
if my_list[j] > my_list[j + 1]:
my_list[j], my_list[j + 1] = my_list[j + 1], my_list[j]

my_list=[5,3,4,7,2]
print(f"排序前:{my_list}")
bubble_sort(my_list)
print(f"排序后:{my_list}")

image-20250622104428121

选择排序

Selection Sort核心思想:

  1. 初始化:将列表分为已排序部分和未排序部分。初始时,已排序部分为空,未排序部分为整个列表。
  2. 查找最小值:在未排序部分中查找最小的元素。
  3. 交换位置:将找到的最小元素与未排序部分的第一个元素交换位置。
  4. 更新范围:将未排序部分的起始位置向后移动一位,扩大已排序部分的范围。
  5. 重复步骤:重复上述步骤,直到未排序部分为空,列表完全有序。

selectionSort

Python 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def select_sort(my_list):
# 获取列表长度
n = len(my_list)
# 外层循环控制比较轮数
for i in range(n - 1):
# 定义变量min_index,记录本轮最小值的索引
min_index = i
# 内循环控制 每次比较轮数:次数
for j in range(i + 1, n): # 1234 234 34 4
# 具体的比较过程,索引min_index和索引j比较
if my_list[j] < my_list[min_index]:
min_index = j
# 走到这里了。说明本轮已经找到最小值了,判断,并交换
if min_index != i:
my_list[min_index], my_list[i] = my_list[i], my_list[min_index]

image-20250622104453994

插入排序

入排序(Insertion Sort)原理:

  • 把列表分为两个部分,假设第一个元素是有序的,剩下的是无序的。
  • 每次都从无序的中获取一个元素。和有序前面的元素进行比较
  • 决定出他的位置,进行插入。直至无序列表的元素操作完毕。剩下的列表就是:有序的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def insert_sort(my_list):
n = len(my_list)
for i in range(1, n):
for j in range(i, 0, -1):
# 具体比较过程
if my_list[j] < my_list[j - 1]:
my_list[j], my_list[j - 1] = my_list[j - 1], my_list[j]
else:
# 走到这里说明元素已经找到了自己的位置
break
if __name__ == '__main__':
my_list = [5, 3, 4, 7, 2]
print(f"排序前:{my_list}")
insert_sort(my_list)
print(f"排序后:{my_list}")

image-20250622113530869

查找

二分查找法

折半搜索,也称二分查找算法、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。

递归二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
"""
二分(折半)查找:
概述:
属于查找类的算法,相对来说效率比较高。时间复杂度 o(log n)
前提:
列表必须有序
原理:
1:比较 要查找元素和列表中的值,如果一样则返回True。程序结束
2:如果 要查找的元素比中值小,去前半段(中值前)查找。
3:如果 要查找的元素比中值大,去后半段(中值后)查找。
4:重复上述动作,直至找完。如果找完了,还找不到。就返回False
"""
# my_list 原列表 target要查找的目标值
def binary_search_re(my_list, target):
# 获取列表长度
n = len(my_list)
# 判断列表是否为空
if n == 0:
return False
mid = n // 2
# 比较 要查找的元素 和 中值
if my_list[mid] == target:
return True
elif target < my_list[mid]:
# 如果要查找的元素 比中值小 ,去前半段(中值前查找)。递归调用
# [:mid] 采用切片 ,包左不包右
return binary_search_re(my_list[:mid], target)
else:
return binary_search_re(my_list[mid + 1:], target)
return False


if __name__ == '__main__':
my_list = [2, 3, 5, 6, 8, 10, 16, 18, 20]
print(binary_search_re(my_list,60))

非递归二分查找

计算中间位置时不应该使用(high+ low) / 2的方式,因为加法运算可能导致整数越界,最好使用整除法(high+ low) // 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def bisearch_search(my_list, target):
# 定义变量 start 和 end 分别代表列表开始与结束 (索引)
start = 0
end = len(my_list) - 1
# 循环查找,只要满足start<=end 就一直找
while start <= end:
# 计算中值
mid = (start + end) // 2
if my_list[mid] == target:
return True
elif target < my_list[mid]:
# 如果要查找的元素比中值小 ,就去前半段(中值前)查找,即修改end的值
end = mid - 1
else:
start = mid + 1
return False


if __name__ == '__main__':
my_list = [8, 22, 31, 32, 44, 55, 56, 70, 78]
print(bisearch_search(my_list, -1))

之前整理的一篇文章:https://liamjohnson-w.github.io/2023/07/03/2023.07.03/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# 定义Node类,表示二叉树节点
class Node:
# 初始化属性
def __init__(self, item):
self.item = item
self.lchild = None
self.rchild = None


# 自定义BinaryTree ,表示二叉树
class BinaryTree:
def __init__(self, node=None):
self.root = node

# 定义add函数,表示添加节点
def add(self, item):
# 1.item封装为节点
new_node = Node(item)
# 2.判断根节点是否为null,如果为空,设置当前节点为跟节点
if self.root is None:
self.root = new_node
return
# 3.创建一个队列,添加根节点到队列中
queue = []
queue.append(self.root)
# 4.通过while True 死循环 找到空缺的节点位置
while True:
# 5.获取队列第一个元素
node = queue.pop(0)
# 6.判断当前节点左子树是否为空
if node.lchild is None:
# 6.1把新节点当做当前节点的左子树,并结束
node.lchild = new_node
return
else:
# 6.2走到这里,说明左子树不为空,把当前节点的左子树添加队列中
queue.append(node.lchild)

# 7.判断当前节点的右子树是否为空
if node.rchild is None:
# 7.1把新节点设置为当前节点的右子树 ,并结束
node.rchild = new_node
return
else:
# 走到这里,说明右子树不为空,把当前节点的右子树,添加到队列中
queue.append(node.rchild)

# 定义遍历二叉树 广度优先遍历 (逐层遍历,一层一层的遍历)
def breadth(self):
# 1.判断根节点是否为空
if self.root is None:
return
# 2.创建队列
queue = []
queue.append(self.root)
# 3.循环打印内容
while len(queue) != 0:
# 4.获取第一个元素
node = queue.pop(0)
# 5.打印该节点的元素域
print(node.item, end=" ")
# 6.判断当前节点的左子树是否存在,存在添加到队列中
if node.lchild is not None:
queue.append(node.lchild)

# 7.判断当前节点的右子树是否存在,存在添加到队列中
if node.rchild is not None:
queue.append(node.rchild)

# 深度优先之先序遍历(根左右)
def preorder(self, root):
# 1.判断根节点是否不为空,不为空才打印
if root is not None:
print(root.item, end=" ")
# 递归遍历左子树
self.preorder(root.lchild)
# 递归遍历右子树
self.preorder(root.rchild)

# 深度优先之中序遍历(左根右)
def inorder(self, root):
# 1.判断根节点是否不为空,不为空才打印
if root is not None:
# 递归遍历左子树
self.inorder(root.lchild)
print(root.item, end=" ")
# 递归遍历右子树
self.inorder(root.rchild)

# 深度优先之后序遍历(左右根)
def postorder(self,root):
if root is not None:
#遍历左子树
self.postorder(root.lchild)
#遍历右子树
self.postorder(root.rchild)
#打印根节点
print(root.item,end=" ")


def dm1_1_测试节点和二叉树类():
# 1-创建节点
node1 = Node("A")
# 打印节点的元素域 左子树 右子树
print(node1.item) # a
print(node1.lchild) # None
print(node1.rchild) # None

print("--------二叉树类----------")
# #创建了一个空树
# bt = BinaryTree()
# print(bt.root) #None

print("--------测试二叉树----------")
bt = BinaryTree(node1)
print(bt.root) # <__main__.Node object at 0x000001E7F19438B0>
print(bt.root.item)
print(bt.root.lchild)
print(bt.root.rchild)


def dm2_2_测试添加节点():
bt = BinaryTree()
# 添加元素
bt.add("A")
bt.add("B")
bt.add("C")
bt.add("D")
bt.add("E")
bt.add("F")
bt.add("G")
bt.add("H")
bt.add("I")
bt.add("J")


def dm3_3_测试添加_遍历节点():
bt = BinaryTree()
# 添加元素
bt.add("A")
bt.add("B")
bt.add("C")
bt.add("D")
bt.add("E")
bt.add("F")
bt.add("G")
bt.add("H")
bt.add("I")
bt.add("J")
bt.breadth()


def dm4_4_测试深度优先_先序遍历节点():
bt = BinaryTree()
# 添加元素
bt.add("A")
bt.add("B")
bt.add("C")
bt.add("D")
bt.add("E")
bt.add("F")
bt.add("G")
bt.add("H")
bt.add("I")
bt.add("J")
print("先序遍历(根左右)")
bt.preorder(bt.root)
print()
print("中序遍历(左根右)")
bt.inorder(bt.root)
print()
print("后序遍历(左右根)")
bt.postorder(bt.root)



if __name__ == '__main__':
# dm1_1_测试节点和二叉树类()
# dm2_2_测试添加节点()
# dm3_3_测试添加_遍历节点()
dm4_4_测试深度优先_先序遍历节点()