├── .idea
├── Data_Structure_with_Python.iml
├── encodings.xml
├── misc.xml
├── modules.xml
├── vcs.xml
└── workspace.xml
├── 0.0.算法效率衡量.md
├── 0.1.算法效率衡量.py
├── 1.0.Python内置类型性能分析.md
├── 1.1.Python内置类型性能分析.py
├── 2.0.顺序表.md
├── 2.1单链表.py
├── 2.2单项循环链表.py
├── 2.3双向链表.py
├── 3.栈.py
├── 4.1队列.py
├── 4.2双端队列.py
├── 5.0.排序算法.md
├── 5.1冒泡排序.py
├── 5.2选择排序.py
├── 5.3插入排序.py
├── 5.4快速排序.py
├── 5.5希尔排序.md
├── 5.5希尔排序.py
├── 5.6.归并排序.py
├── 6.二分查找.py
├── 7.0.树.md
├── 7.1二叉树的表示与遍历.py
├── README.md
└── image
├── 0.0.png
├── 0.1.png
├── 1.0.png
├── 1.1.png
├── 2.0.png
├── 5.0.png
├── 7.0.png
└── 7.1.png
/.idea/Data_Structure_with_Python.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
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 |
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 | 1553167947475
166 |
167 |
168 | 1553167947475
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
--------------------------------------------------------------------------------
/0.0.算法效率衡量.md:
--------------------------------------------------------------------------------
1 | # 算法效率衡量
2 | ## 对于算法的时间效率,我们可以用“大O记法”来表示。
3 |
4 | “大O记法”:对于单调的整数函数f,如果存在一个整数函数g和实常数c>0,使得对于充分大的n总有f(n)<=c*g(n),就说函数g是f的一个渐近函数(忽略常数),记为f(n)=O(g(n))。也就是说,在趋向无穷的极限意义下,函数f的增长速度受到函数g的约束,亦即函数f与函数g的特征相似。
5 |
6 | 时间复杂度:假设存在函数g,使得算法A处理规模为n的问题示例所用时间为T(n)=O(g(n)),则称O(g(n))为算法A的渐近时间复杂度,简称时间复杂度,记为T(n)
7 |
8 | ## 最坏时间复杂度
9 | 算法完成工作最多需要多少基本操作,即最坏时间复杂度
10 |
11 | ## 时间复杂度的几条基本计算规则
12 | 基本操作,即只有常数项,认为其时间复杂度为O(1)
13 | 顺序结构,时间复杂度按加法进行计算
14 | 循环结构,时间复杂度按乘法进行计算
15 | 分支结构,时间复杂度取最大值
16 | 判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略
17 | 在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度
18 |
19 | ## 常见时间复杂度
20 | 
21 | ## 常见时间复杂度之间的关系
22 | 
23 |
--------------------------------------------------------------------------------
/0.1.算法效率衡量.py:
--------------------------------------------------------------------------------
1 | # _*_ coding: UTF-8 _*_
2 | import time
3 |
4 | # 如果 a+b+c=1000,且 a^2+b^2=c^2(a,b,c 为自然数),如何求出所有a、b、c可能的组合?
5 |
6 | # 第一次尝试
7 | start_time = time.time()
8 | for a in range(1001):
9 | for b in range(1001):
10 | c = 1000 - a - b
11 | if a**2 + b**2 == c**2:
12 | print("a:%d, b:%d, c:%d " %(a,b,c))
13 | end_time = time.time()
14 | print("time:%f" %(end_time-start_time)) # 1.31
15 |
16 | print("*"*30)
17 | # 第二次尝试
18 | start_time = time.time()
19 | for a in range(1001):
20 | for b in range(1001-a):
21 | c = 1000 - a - b
22 | if a**2 + b**2 == c**2:
23 | print("a:%d, b:%d, c:%d " %(a,b,c))
24 | end_time = time.time()
25 | print("time:%f" %(end_time-start_time)) # 0.69
26 |
27 | # 时间复杂度: T(n) = O(n*n*(1+1)) = O(n*n) = O(n2)
--------------------------------------------------------------------------------
/1.0.Python内置类型性能分析.md:
--------------------------------------------------------------------------------
1 | # Python内置类型性能分析
2 | ## timeit模块
3 | timeit模块可以用来测试一小段Python代码的执行速度。
4 | ```py
5 | class timeit.Timer(stmt='pass', setup='pass', timer=)
6 | ```
7 |
8 | Timer是测量小段代码执行速度的类。
9 |
10 | stmt参数是要测试的代码语句(statment);
11 |
12 | setup参数是运行代码时需要的设置;
13 |
14 | timer参数是一个定时器函数,与平台有关。
15 | ```
16 | timeit.Timer.timeit(number=1000000)
17 |
18 | ```
19 | Timer类中测试语句执行速度的对象方法。number参数是测试代码时的测试次数,默认为1000000次。方法返回执行代码的平均耗时,一个float类型的秒数。
20 |
21 | # list内置操作的时间复杂度
22 | 
23 |
24 | # dict内置操作的时间复杂度
25 | 
26 |
27 | ## Python的内置数据结构
28 | Python给我们提供了很多现成的数据结构类型,这些系统自己定义好的,不需要我们自己去定义的数据结构叫做Python的内置数据结构,比如列表、元组、字典。
29 | ## Python的扩展数据结构
30 | 而有些数据组织方式,Python系统里面没有直接定义,需要我们自己去定义实现这些数据的组织方式,这些数据组织方式称之为Python的扩展数据结构,比如栈,队列等。
31 | ## 算法与数据结构的区别
32 |
33 | 数据结构只是静态的描述了数据元素之间的关系。
34 |
35 | 高效的程序需要在数据结构的基础上设计和选择算法。
36 |
37 | 程序 = 数据结构 + 算法
38 |
39 | 总结:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体
40 |
41 | ## 抽象数据类型(Abstract Data Type)
42 |
43 | 抽象数据类型(ADT)的含义是指一个数学模型以及定义在此数学模型上的一组操作。即把数据类型和数据类型上的运算捆在一起,进行封装。引入抽象数据类型的目的是把数据类型的表示和数据类型上运算的实现与这些数据类型和运算在程序中的引用隔开,使它们相互独立。
44 |
45 | 最常用的数据运算有五种:
46 |
47 | 插入
48 | 删除
49 | 修改
50 | 查找
51 | 排序
52 |
--------------------------------------------------------------------------------
/1.1.Python内置类型性能分析.py:
--------------------------------------------------------------------------------
1 | # _*_ conding: UTF-8 _*_
2 | from timeit import Timer
3 |
4 | def test1():
5 | l = []
6 | for i in range(1000):
7 | l = l + [i]
8 |
9 | def test2():
10 | l = []
11 | for i in range(1000):
12 | l.append(i)
13 |
14 | def test3():
15 | l = [i for i in range(1000)]
16 |
17 | def test4():
18 | l = list(range(1000))
19 |
20 | t1 = Timer("test1()", "from __main__ import test1")
21 | print("concat ", t1.timeit(number=1000), "seconds")
22 |
23 | t2 = Timer("test2()", "from __main__ import test2")
24 | print("append ", t2.timeit(number=1000), "seconds")
25 |
26 | t3 = Timer("test3()", "from __main__ import test3")
27 | print("comprehension ", t3.timeit(number=1000), "seconds")
28 |
29 | t4 = Timer("test4()", "from __main__ import test4")
30 | print("list range ", t4.timeit(number=1000), "seconds")
31 |
32 |
33 | """结果:
34 | concat 1.421693535000486 seconds
35 | append 0.1010527379994528 seconds
36 | comprehension 0.036659903999861854 seconds
37 | list range 0.018424673000481562 seconds
38 | """
39 |
40 | print("*"*30)
41 | # pop 测试
42 | x = [i for i in range(2000000)]
43 | pop_zero = Timer("x.pop(0)", "from __main__ import x")
44 | print("pop_zero ", pop_zero.timeit(number=1000), "seconds")
45 |
46 | pop_end = Timer("x.pop()", "from __main__ import x")
47 | print("pop_end ", pop_end.timeit(number=1000), "seconds")
48 |
49 | list_append = Timer("x.append(1)", "from __main__ import x")
50 | print("list_append(从后边插入) ", list_append.timeit(number=1000), "seconds")
51 |
52 | list_insert = Timer("x.insert(0,1)", "from __main__ import x")
53 | print("list_insert(从前边插入) ", list_insert.timeit(number=1000), "seconds")
54 |
55 | """
56 | pop_zero 3.0994806850003442 seconds
57 | pop_end 0.0001244389995918027 seconds
58 | list_append(从后边插入) 9.197799954563379e-05 seconds
59 | list_insert(从前边插入) 3.44098534800014 seconds
60 | """
--------------------------------------------------------------------------------
/2.0.顺序表.md:
--------------------------------------------------------------------------------
1 | # 线性表
2 | 一个线性表是某类元素的一个集合,还记录着元素之间的一种顺序关系。
3 |
4 | 根据线性表的实际存储方式,分为两种实现模型:
5 |
6 | 顺序表,将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示。
7 |
8 | 链表,将元素存放在通过链接构造起来的一系列存储块中。
9 |
10 | # 顺序表的两种存储方式:
11 |
12 | 元素内置(一体式结构):表里存储的元素大小固定
13 |
14 | 元素外置(分离式结构):表里只存储链接
15 |
16 | # 表中存储的两个信息
17 |
18 | 1.表中的元素集合
19 |
20 | 2.信息主要包括元素存储区的容量和当前表中已有的元素个数两项
21 |
22 | # 增加元素
23 |
24 | a. 尾端加入元素,时间复杂度为O(1)
25 |
26 | b. 中间插入,时间复杂度为O(n)
27 |
28 | # 删除元素
29 |
30 | a. 删除表尾元素,时间复杂度为O(1)
31 |
32 | b. 中间元素删除,时间复杂度为O(n)
33 |
34 | # Python中的顺序表
35 |
36 | list和tuple
37 |
38 | tuple是不可变类型,即不变的顺序表,因此不支持改变其内部状态的任何操作,而其他方面,则与list的性质类似。
39 |
40 | list就是一种采用分离式技术实现的动态顺序表。
41 |
42 |
43 | # 链表与顺序表的对比
44 |
45 | 链表失去了顺序表随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大,但对存储空间的使用要相对灵活。
46 |
47 | 链表与顺序表的各种操作复杂度如下所示:
48 |
49 | 
50 |
51 | 链表的主要耗时操作是**遍历查找**
52 |
53 | 顺序表查找很快,主要耗时的操作是**拷贝覆盖**
54 |
55 |
--------------------------------------------------------------------------------
/2.1单链表.py:
--------------------------------------------------------------------------------
1 | # _*_ conding: UTF-8 _*_
2 |
3 |
4 | # 单链表
5 | class SingleNode(object):
6 | """
7 | 单链表的节点
8 | """
9 | def __init__(self,item):
10 | # _item 存放数据元素
11 | self.item = item
12 | # _next 是下一个节点的标示
13 | self.next = None
14 |
15 | class SingleLinkList(object):
16 | """单链表"""
17 | def __init__(self):
18 | self._head = None
19 |
20 | def is_empty(self):
21 | """判断链表是否为空"""
22 | return self._head == None
23 |
24 | def length(self):
25 | """链表长度"""
26 | # cur初始时指向头节点
27 | cur = self._head
28 | count = 0
29 | # 尾节点指向None,当未达到尾部时
30 | while cur != None:
31 | count += 1
32 | # 将cur后移一个节点
33 | cur = cur.next
34 | return count
35 |
36 | def travel(self):
37 | """遍历链表"""
38 | cur = self._head
39 | while cur != None:
40 | print(cur.item)
41 | cur = cur.next
42 | print("")
43 |
44 | def add(self,item):
45 | """头部添加元素"""
46 | # 先创建一个保存item值的节点
47 | node = SingleNode(item)
48 | # 将新节点的链接域
49 | node.next = self._head
50 | # 将链表的头_head指向新节点
51 | self._head = node
52 |
53 | def append(self, item):
54 | """尾部添加元素"""
55 | node = SingleNode(item)
56 | # 先判断链表是否为空,若是空链表,则将_head指向新节点
57 | if self.is_empty():
58 | self._head = node
59 | # 若不为空,则找到尾部,将尾节点的next指向新节点
60 | else:
61 | cur = self._head
62 | while cur.next != None:
63 | cur = cur.next
64 | cur.next = node
65 |
66 | def insert(self, pos, item):
67 | """指定位置添加元素"""
68 | # 若指定位置pos为第一个元素之前,则执行头部插入
69 | if pos <= 0:
70 | self.add(item)
71 | # 若指定位置超过链表尾部,则执行尾部插入
72 | elif pos > (self.length() - 1):
73 | self.append(item)
74 | # 其他
75 | else:
76 | node = SingleNode(item)
77 | count = 0
78 | # pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置
79 | pre = self._head
80 | while count < (pos - 1):
81 | count += 1
82 | pre = pre.next
83 | # 先将新节点node的next指向插入点的位置的节点
84 | node.next = pre.next
85 | # 将插入位置的前一个节点的next指向新节点
86 | pre.next = node
87 |
88 | def remove(self,item):
89 | """删除节点"""
90 | cur = self._head
91 | pre = None
92 | while cur != None:
93 | # 找到了指定元素
94 | if cur.item == item:
95 | # 如果第一个就是删除的节点
96 | if not pre:
97 | # 将头指针指向头节点的后一个节点
98 | self._head = cur.next
99 | else:
100 | # 将删除位置前一个节点的next指向删除位置的后一个节点
101 | pre.next = cur.next
102 | break
103 | # 没有找到,链表后移继续找
104 | else:
105 | pre = cur
106 | cur = cur.next
107 |
108 | def search(self, item):
109 | """链表查找节点是否存在,并返回True或者False"""
110 | cur = self._head
111 | while cur != None:
112 | if cur.item == item:
113 | return True
114 | cur = cur.next
115 | return False
116 |
117 | if __name__ == "__main__":
118 | ll = SingleLinkList()
119 | ll.add(1)
120 | ll.add(2) # 头部添加
121 | ll.append(3) # 尾部添加
122 | ll.insert(2,4) # 指定位置插入
123 | print("length:", ll.length())
124 | ll.travel() # 遍历链表
125 | print(ll.search(3)) # 搜索链表是否存在3
126 | print(ll.search(5)) # 搜索链表是否存在5
127 | ll.remove(5) # 删除链表中的item:1
128 | print("length:",ll.length())
129 | ll.travel()
130 |
131 |
--------------------------------------------------------------------------------
/2.2单项循环链表.py:
--------------------------------------------------------------------------------
1 | # _*_ coding: UTF-8 _*_
2 |
3 |
4 | # 单链表
5 | class Node(object):
6 | """
7 | 单链表的节点
8 | """
9 | def __init__(self,item):
10 | # _item 存放数据元素
11 | self.item = item
12 | # _next 是下一个节点的标示
13 | self.next = None
14 |
15 | class SinCycLinkedlist(object):
16 | """单项循环链表"""
17 |
18 | def __init__(self):
19 | self._head = None
20 |
21 | def is_empty(self):
22 | """判断链表是否为空"""
23 | return self._head == None
24 |
25 | def lengeth(self):
26 | """返回链表的长度"""
27 | # 如果链表为空,返回长度为0
28 | if self.is_empty():
29 | return 0
30 | count = 1
31 | cur = self._head
32 | while cur.next != self._head:
33 | count += 1
34 | cur = cur.next
35 | return count
36 |
37 | def travel(self):
38 | """遍历链表"""
39 | if self.is_empty():
40 | return False
41 | cur = self._head
42 | print(cur.item)
43 | while cur.next != self._head:
44 | cur = cur.next
45 | print(cur.item)
46 | print("")
47 |
48 | def add(self,item):
49 | """头部添加节点"""
50 | node = Node(item)
51 | if self.is_empty():
52 | self._head = node
53 | node.next = self._head
54 | else:
55 | # 添加的节点指向_head
56 | node.next = self._head
57 | # 移到链表尾部,将尾部节点的next指向node
58 | cur = self._head
59 | while cur.next != self._head:
60 | cur = cur.next
61 | cur.next = node
62 | # _head指向添加node
63 | self._head = node
64 |
65 | def append(self, item):
66 | """尾部添加节点"""
67 | node = Node(item)
68 | if self.is_empty():
69 | self._head = node
70 | node.next = self._head
71 | else:
72 | # 移到链表尾部
73 | cur = self._head
74 | while cur.next != self._head:
75 | cur = cur.next
76 | cur.next = node
77 | node.next = self._head
78 |
79 | def insert(self, pos, item):
80 | """在指定位置添加节点"""
81 | if pos <= 0:
82 | self.add(item)
83 | elif pos > (self.lengeth() - 1):
84 | self.append(item)
85 | else:
86 | node = Node(item)
87 | cur = self._head
88 | count = 0
89 | # 移动到指定位置的前一个位置
90 | while count < (pos - 1):
91 | count += 1
92 | cur = cur.next
93 | node.next = cur.next
94 | cur.next = node
95 |
96 | def remove(self, item):
97 | """删除一个节点"""
98 | # 若链表为空,则直接返回
99 | if self.is_empty():
100 | return
101 | # 将cur指向头节点
102 | cur = self._head
103 | pre = None
104 | # 若头节点的元素就是要查找的元素item
105 | if cur.item == item:
106 | # 如果链表不止一个节点
107 | if cur.next != self._head:
108 | # 先找到尾节点,将尾节点的next指向第二个节点
109 | while cur.next != self._head:
110 | cur = cur.next
111 | # cur 指向了尾节点
112 | cur.next = self._head.next
113 | self._head = self._head.next
114 | else:
115 | # 链表只有一个节点
116 | self._head = None
117 | # 头节点不是要删除的元素
118 | else:
119 | pre = self._head
120 | while cur.next != self._head:
121 | # 找到了要删除的元素
122 | if cur.item == item:
123 | # 删除
124 | pre.next = cur.next
125 | return
126 | else:
127 | pre = cur
128 | cur = cur.next
129 | # cur 指向尾节点
130 | if cur.item == item:
131 | # 尾部删除
132 | pre.next = cur.next
133 |
134 | def search(self, item):
135 | """查找节点是否存在"""
136 | if self.is_empty():
137 | return False
138 | cur = self._head
139 | if cur.item == item:
140 | return True
141 | while cur.next != self._head:
142 | cur = cur.next
143 | if cur.item == item:
144 | return True
145 | return False
146 |
147 | if __name__ == "__main__":
148 | ll = SinCycLinkedlist()
149 | ll.add(1)
150 | ll.add(2)
151 | ll.append(3)
152 | ll.insert(2, 4)
153 | ll.insert(4, 5)
154 | ll.insert(0, 6)
155 | print("length:", ll.lengeth())
156 | ll.travel()
157 | print(ll.search(3))
158 | print(ll.search(7))
159 | ll.remove(1)
160 | print("length:",ll.lengeth())
161 | ll.travel()
162 |
163 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/2.3双向链表.py:
--------------------------------------------------------------------------------
1 | # _*_ coding: UTF-8 _*_
2 |
3 |
4 | class Node(object):
5 | """双向链表节点"""
6 | def __init__(self, item):
7 | self.item = item
8 | self.next = None
9 | self.prev = None
10 |
11 | class DLinkList(object):
12 | """双向链表"""
13 | def __init__(self):
14 | self._head = None
15 |
16 | def is_empty(self):
17 | """判断链表是否为空"""
18 | return self._head == None
19 |
20 | def length(self):
21 | """返回链表的长度"""
22 | cur = self._head
23 | count = 0
24 | while cur != None:
25 | count += 1
26 | cur = cur.next
27 | return count
28 |
29 | def travel(self):
30 | """遍历链表"""
31 | cur = self._head
32 | while cur != None:
33 | print(cur.item)
34 | cur = cur.next
35 | print("")
36 |
37 | def add(self, item):
38 | """头部插入元素"""
39 | node = Node(item)
40 | if self.is_empty():
41 | # 如果是空链表,将_head指向node
42 | self._head = node
43 | else:
44 | # 将node的next指向_head的头节点
45 | node.next = self._head
46 | # 将_head的头节点的prev指向node
47 | self._head.prev = node
48 | # 将_head指向node
49 | self._head = node
50 |
51 | def append(self, item):
52 | """尾部插入元素"""
53 | node = Node(item)
54 | if self.is_empty():
55 | # 如果是空链表,将_head指向node
56 | self._head = node
57 | else:
58 | # 移动到链表尾部
59 | cur = self._head
60 | while cur.next != None:
61 | cur = cur.next
62 | # 将尾节点cur的next指向node
63 | cur.next = node
64 | # 将node的prev指向cur
65 | node.prev = cur
66 |
67 | def search(self, item):
68 | """查找元素是否存在"""
69 | cur = self._head
70 | while cur != None:
71 | if cur.item == item:
72 | return True
73 | cur = cur.next
74 | return False
75 |
76 | def insert(self, pos, item):
77 | """在指定位置添加节点"""
78 | if pos <= 0:
79 | self.add(item)
80 | elif pos > (self.length() - 1):
81 | self.append(item)
82 | else:
83 | node = Node(item)
84 | cur = self._head
85 | count = 0
86 | # 移动到指定位置的前一个位置
87 | while count < (pos - 1):
88 | count += 1
89 | cur = cur.next
90 | # 1.将node的prev指向cur
91 | node.prev = cur
92 | # 2.将node的next指向cur的下一个节点
93 | node.next = cur.next
94 | # 3.将cur的下一个节点的prev指向node
95 | cur.next.prev = node
96 | # 4.将cur的next指向node
97 | cur.next = node
98 |
99 | def remove(self, item):
100 | """删除元素"""
101 | if self.is_empty():
102 | return
103 | else:
104 | cur = self._head
105 | # 如果首节点的元素就是要删除的元素
106 | if cur.item == item:
107 | # 如果链表只有这一个节点
108 | if cur.next == None:
109 | self._head = None
110 | else:
111 | # 将第二个个节点的prev设置为None
112 | cur.next.prev = None
113 | # 将_head指向第二个节点
114 | self._head = cur.next
115 | return
116 | # 首节点不是要删除的节点
117 | while cur != None:
118 | if cur.item == item:
119 | # 将cur的前一个节点的next指向cur的后一个节点
120 | cur.prev.next = cur.next
121 | # 将cur的后一个节点的prev指向cur的前一个节点
122 | cur.next.prev = cur.prev
123 | break
124 | cur = cur.next
125 |
126 | if __name__ == "__main__":
127 | ll = DLinkList()
128 | ll.add(1)
129 | ll.add(2)
130 | ll.append(3)
131 | ll.insert(2, 4)
132 | ll.insert(4, 5)
133 | ll.insert(0, 6)
134 | print("length:", ll.length())
135 | ll.travel()
136 | print(ll.search(3))
137 | print(ll.search(4))
138 | ll.remove(1)
139 | print("length:", ll.length())
140 | ll.travel()
141 |
142 |
--------------------------------------------------------------------------------
/3.栈.py:
--------------------------------------------------------------------------------
1 | # _*_ conding: UTF-8 _*_
2 |
3 | """
4 | 栈:先进后出
5 | 使用list实现,其中list的append就是栈的push;list的pop就是栈的pop;list的[-1]就是栈的peek
6 | """
7 |
8 | class Stack(object):
9 | """栈"""
10 | def __init__(self):
11 | self.items = []
12 |
13 | def is_empty(self):
14 | """判断是否为空"""
15 | return self.items == []
16 |
17 | def push(self, item):
18 | """加入元素"""
19 | self.items.append(item)
20 |
21 | def pop(self):
22 | """弹出元素"""
23 | return self.items.pop()
24 |
25 | def peek(self):
26 | """返回栈顶元素"""
27 | return self.items[- 1]
28 |
29 | def size(self):
30 | """返回栈的大小"""
31 | return len(self.items)
32 |
33 | if __name__ == "__main__":
34 | stack = Stack()
35 | stack.push("hello")
36 | stack.push("world")
37 | stack.push("itcast")
38 | print(stack.size())
39 | print(stack.peek())
40 | print(stack.pop())
41 | print(stack.pop())
42 | print(stack.pop())
43 |
--------------------------------------------------------------------------------
/4.1队列.py:
--------------------------------------------------------------------------------
1 | # _*_ coding: UTF-8 _*_
2 |
3 |
4 | """
5 | # 队列 (queue): 只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
6 | 先进先出
7 | """
8 |
9 | class Queue(object):
10 | """队列"""
11 | def __init__(self):
12 | self.items = []
13 |
14 | def is_empty(self):
15 | """判断队列是否为空"""
16 | return self.items == []
17 |
18 | def enqueue(self, item):
19 | """进队列"""
20 | return self.items.insert(0, item)
21 |
22 | def dequeue(self):
23 | """出队列"""
24 | return self.items.pop()
25 |
26 | def size(self):
27 | """返回队列大小"""
28 | return len(self.items)
29 |
30 |
31 | if __name__ == "__main__":
32 | q = Queue()
33 | q.enqueue("hello")
34 | q.enqueue("world")
35 | q.enqueue("itcast")
36 | print(q.size())
37 | print(q.dequeue())
38 | print(q.dequeue())
39 | print(q.dequeue())
40 |
--------------------------------------------------------------------------------
/4.2双端队列.py:
--------------------------------------------------------------------------------
1 | # _*_ coding: UTF-8 _*_
2 |
3 | """双端队列(deque:double-ended-queue)
4 | 可以在任一端入队和出队
5 | """
6 |
7 | class Deque(object):
8 | """双端队列"""
9 | def __init__(self):
10 | self.items = []
11 |
12 | def is_empty(self):
13 | """判断队列是否为空"""
14 | return self.items == []
15 |
16 | def add_front(self, item):
17 | """在队头添加元素"""
18 | self.items.append(item)
19 |
20 | def add_rear(self, item):
21 | """在队尾添加元素"""
22 | self.items.append(item)
23 |
24 | def remove_front(self):
25 | """从队头删除元素"""
26 | return self.items.pop(0)
27 |
28 | def remove_rear(self):
29 | """从队尾删除元素"""
30 | return self.items.pop()
31 |
32 | def size(self):
33 | """返回队列的大小"""
34 | return len(self.items)
35 |
36 | if __name__ == "__main__":
37 | deque = Deque()
38 | deque.add_front(1)
39 | deque.add_front(2)
40 | deque.add_rear(3)
41 | deque.add_rear(4)
42 | print(deque.size())
43 | print(deque.items)
44 | print(deque.remove_front())
45 | print(deque.remove_front())
46 | print(deque.remove_rear())
47 | print(deque.remove_rear())
48 |
--------------------------------------------------------------------------------
/5.0.排序算法.md:
--------------------------------------------------------------------------------
1 | # 排序算法
2 | 排序算法(Sorting Algorithm):是一种能将一串数据按照特定顺序进行排列的一种算法。
3 |
4 | # 稳定性
5 |
6 | 稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
7 |
8 | 当相等的元素是无法分辨的,比如像是整数,稳定性并不是一个问题。然而,假设以下的数对将要以他们的第一个数字来排序。
9 |
10 | (4, 1) (3, 1) (3, 7)(5, 6)
11 |
12 | 在这个状况下,有可能产生两种不同的结果,一个是让相等键值的纪录维持相对的次序,而另外一个则没有:
13 |
14 | (3, 1) (3, 7) (4, 1) (5, 6) (维持次序)
15 |
16 | (3, 7) (3, 1) (4, 1) (5, 6) (次序被改变)
17 |
18 | 不稳定排序算法可能会在相等的键值中改变纪录的相对次序,但是稳定排序算法从来不会如此。不稳定排序算法可以被特别地实现为稳定。作这件事情的一个方式是人工扩充键值的比较,如此在其他方面相同键值的两个对象间之比较,(比如上面的比较中加入第二个标准:第二个键值的大小)就会被决定使用在原先数据次序中的条目,当作一个同分决赛。然而,要记住这种次序通常牵涉到额外的空间负担。
19 |
20 | # 常见排序算法效率比较
21 |
22 | 
23 |
24 |
--------------------------------------------------------------------------------
/5.1冒泡排序.py:
--------------------------------------------------------------------------------
1 | # _*_ coding: UTF-8 _*_
2 | """冒泡排序
3 | 时间复杂度:O(n**2)
4 | 升序:每次只比较左右两个,大的放右边
5 | 稳定性:稳定
6 | """
7 |
8 | def bubble_sort(alist):
9 | for j in range(len(alist) - 1, 0, -1):
10 | # j表示每次遍历需要比较的次数,是逐渐减小的
11 | for i in range(j):
12 | if alist[i] > alist[i+1]:
13 | alist[i], alist[i+1] = alist[i+1], alist[i]
14 |
15 |
16 | li = [54,26,93,17,77,31,44,55,20]
17 | bubble_sort(li)
18 | print(li)
19 |
--------------------------------------------------------------------------------
/5.2选择排序.py:
--------------------------------------------------------------------------------
1 | # _*_ coding: UTF-8 _*_
2 | """选择排序(Selection sort)
3 | 升序:每次从 未排序区域中选出最大的 与 在未排序区域的第一个位置的数 交换
4 | 时间复杂度:O(n**2)
5 | 稳定性:不稳定
6 | """
7 |
8 | def selection_sort(alist):
9 | # 需要进行n-1次选择操作
10 | for i in range(len(alist) - 1):
11 | # 记录最小位置
12 | min_index = i
13 | for j in range(i + 1, len(alist)):
14 | if alist[j] < alist[min_index]:
15 | min_index = j
16 | # 如果选择出的数据不在正确的位置,进行交换
17 | if min_index != i:
18 | alist[i], alist[min_index] = alist[min_index], alist[i]
19 |
20 | alist = [54,226,93,17,77,31,44,55,20]
21 | print("origin alist:",alist)
22 | selection_sort(alist)
23 | print("selection_sort:",alist)
24 |
--------------------------------------------------------------------------------
/5.3插入排序.py:
--------------------------------------------------------------------------------
1 | # _*_ coding: UTF-8 _*_
2 | """插入排序(Insertion Sort)
3 | 升序:通过构建有序序列,将未排序数据,在已排序数据中从后向前扫描,找到相应位置并插入
4 | 时间复杂度:O(n)
5 | 稳定性:稳定
6 | """
7 |
8 | def insert_sort(alist):
9 | # 从第二个位置起,即下标为1的元素开始向前插入
10 | for i in range(1, len(alist)):
11 | # 从第i个元素开始向前比较,如果小于前一个元素,交换位置
12 | for j in range(i, 0, -1):
13 | if alist[j] < alist[j-1]:
14 | alist[j], alist[j-1] = alist[j-1], alist[j]
15 | alist = [54,26,93,17,77,31,44,55,20]
16 | print("origin:",alist)
17 | insert_sort(alist)
18 | print("insert_sort:",alist)
19 |
--------------------------------------------------------------------------------
/5.4快速排序.py:
--------------------------------------------------------------------------------
1 | # _*_ coding: UTF-8 _*_
2 | """快速排序(Quick-Sort)
3 | 1.从数列中挑出一个元素,称为"基准"(pivot),
4 | 2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。
5 | 在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
6 | 3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
7 |
8 | """
9 |
10 | def quick_sort(alist, start, end):
11 | """快速排序"""
12 |
13 | # 递归的退出条件
14 | if start >= end:
15 | return
16 |
17 | # 设定起始元素为要寻找位置的基准元素
18 | mid = alist[start]
19 |
20 | # low 为序列左边的由左向右移动的游标
21 | low = start
22 |
23 | # high 为序列右边的由右向左移动的游标
24 | high = end
25 |
26 | while low < high:
27 | # 如果low与high未重合,high指向的元素不比基准元素小,则high向左移动
28 | while low < high and alist[high] >= mid:
29 | high -= 1
30 | # 将high指向的元素放到low的位置上
31 | alist[low] = alist[high]
32 |
33 | # 如果low与high未重合,low指向的元素比基准元素小,则low向右移动
34 | while low < high and alist[low] < mid:
35 | low += 1
36 | # 将low指向的元素放到high的位置
37 | alist[high] = alist[low]
38 |
39 | # 退出循环后,low与high重合,此时所指位置为基准元素的正确位置
40 | # 将基准元素放到该位置
41 | alist[low] = mid
42 |
43 | # 对基准元素左边的子序列进行快速排序
44 | quick_sort(alist, start, low - 1)
45 |
46 | # 对基准元素的右边子序列进行快速排序
47 | quick_sort(alist, low + 1, end)
48 |
49 | alist = [54,26,93,17,77,31,44,55,20]
50 | print("origin:",alist)
51 | quick_sort(alist, 0, len(alist) - 1)
52 | print("quick_sort:",alist)
--------------------------------------------------------------------------------
/5.5希尔排序.md:
--------------------------------------------------------------------------------
1 | # 希尔排序原理
2 | 希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
3 |
4 | # 希尔排序过程
5 |
6 | 希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。
7 |
8 | 例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样(竖着的元素是步长组成):
9 | ```
10 | 13 14 94 33 82
11 | 25 59 94 65 23
12 | 45 27 73 25 39
13 | 10
14 |
15 | ```
16 |
17 | 然后我们对每列进行排序:
18 |
19 | ```
20 | 10 14 73 25 23
21 | 13 27 94 33 39
22 | 25 59 94 65 82
23 | 45
24 |
25 | ```
26 | 将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。这时10已经移至正确位置了,然后再以3为步长进行排序:
27 | ```
28 | 10 14 73
29 | 25 23 13
30 | 27 94 33
31 | 39 25 59
32 | 94 65 82
33 | 45
34 | ```
35 | 排序之后变为:
36 | ```
37 | 10 14 13
38 | 25 23 33
39 | 27 25 59
40 | 39 65 73
41 | 45 94 82
42 | 94
43 |
44 | ```
45 |
46 | 最后以1步长进行排序(此时就是简单的插入排序了)
47 |
48 |
49 |
--------------------------------------------------------------------------------
/5.5希尔排序.py:
--------------------------------------------------------------------------------
1 | # _*_ coding: UTF-8 _*_
2 | """希尔排序(shell sort)
3 | gap = 是子序列的个数
4 | 时间复杂度:O(n**2)
5 | """
6 |
7 | def shell_sort(alist):
8 | n = len(alist)
9 |
10 | # 初始步长
11 | gap = n//2
12 | while gap > 0:
13 | # 按步长进行插入排序
14 | for i in range(gap, n):
15 | #print("i:",i,alist[i])
16 | j = i
17 | # 插入排序
18 | while j >= gap and alist [j - gap] > alist[j]:
19 | #print("j",alist[j - gap], alist[j])
20 | alist[j - gap], alist[j] = alist[j], alist[j - gap]
21 | j -= gap
22 |
23 | # 得到新的步长
24 | gap = gap//2
25 |
26 | alist = [54,26,93,17,77,31,44,55,20]
27 | print("origin:",alist)
28 | shell_sort(alist)
29 | print("shell_sort:",alist)
30 |
--------------------------------------------------------------------------------
/5.6.归并排序.py:
--------------------------------------------------------------------------------
1 | """归并排序
2 | 归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。
3 | 将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
4 | 时间复杂度:O(nlogn)
5 | 稳定性:稳定
6 | """
7 |
8 | def merge_sort(alist):
9 | if len(alist) <= 1:
10 | return alist
11 | # 二次分解
12 | mid = len(alist)//2
13 | left = merge_sort(alist[:mid])
14 | right = merge_sort(alist[mid:])
15 | # 合并
16 | return merge(left, right)
17 |
18 | def merge(left, right):
19 | """合并操作,将两个有序数组left[]和right[]合并成一个大的有序数组"""
20 | # left 与 right 的下标指针
21 | l, r = 0, 0
22 | result = []
23 | while l < len(left) and r < len(right):
24 | if left[l] < right[r]:
25 | result.append(left[l])
26 | l += 1
27 | else:
28 | result.append(right[r])
29 | r += 1
30 | result += left[l:]
31 | result += right[r:]
32 | return result
33 |
34 | alist = [54,26,93,17,77,31,44,55,20]
35 | print("origin:", alist)
36 | print("merge_sort:",merge_sort(alist))
37 |
--------------------------------------------------------------------------------
/6.二分查找.py:
--------------------------------------------------------------------------------
1 | """二分查找
2 | 二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。
3 | 时间复杂度:O(logn)
4 | """
5 |
6 | # 法1.非递归实现
7 | def binary_search(alist, item):
8 | first = 0
9 | last = len(alist) - 1
10 | while first <= last:
11 | midpoint = (first + last)//2
12 | if alist[midpoint] == item:
13 | return True
14 | elif item < alist[midpoint]:
15 | last = midpoint - 1
16 | else:
17 | first = midpoint + 1
18 | return False
19 |
20 | testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42,]
21 | print("origin list:",testlist)
22 | print("binary_search 3:",binary_search(testlist, 3))
23 | print("binary_search 13:",binary_search(testlist, 13))
24 |
25 |
26 |
27 | # *********************************************************
28 |
29 | # 法2.递归实现
30 | def binary_search_re(alist, item):
31 | if len(alist) == 0:
32 | return False
33 | else:
34 | midpoint = len(alist)//2
35 | if alist[midpoint] == item:
36 | return True
37 | else:
38 | if item < alist[midpoint]:
39 | return binary_search_re(alist[:midpoint], item)
40 | else:
41 | return binary_search_re(alist[midpoint + 1:], item)
42 |
43 | testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42,]
44 | print("origin list:",testlist)
45 | print("binary_search 3:",binary_search_re(testlist, 3))
46 | print("binary_search 13:",binary_search_re(testlist, 13))
47 |
--------------------------------------------------------------------------------
/7.0.树.md:
--------------------------------------------------------------------------------
1 | # 树
2 | 树(英语:tree)是一种抽象数据类型(ADT)
3 | 
4 | # 树的术语
5 |
6 | - 节点的度:一个节点含有的子树的个数称为该节点的度;
7 | - 树的度:一颗树中,最大的节点的度称为树的度;
8 | - 叶节点或终端节点:度为零的节点;
9 | - 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
10 | - 子节点:一个节点含有的子树的根节点称为该节点的子节点;
11 | - 兄弟节点:具有相同父节点的节点互相称为兄弟节点;
12 | - 节点的层次:从根节点开始定义起,根为第一层,根的子节点为第二层,以此类推;
13 | - 树的高度或深度:树中节点的最大层次;
14 | - 堂兄弟节点:父节点在同一层次的节点互为堂兄弟;
15 | - 节点的祖先:从根节点到该节点所经分支上的所有节点;
16 | - 子孙:以某一节点为根的子树中任一节点都称为该节点的子孙;
17 | - 森林:由m(m >= 0)颗互不相交的树的集合称为森林;
18 |
19 | # 树的种类
20 | - 无序树:树中任意节点的子节点之间没有顺序关系
21 | - 有序树:树中任意节点的子节点之间有顺序关系
22 | - - 二叉树:每个节点最多含有两个子树
23 | - - 完全二叉树:除最底层最后一个外其他必须有子树
24 | - - 满二叉树:所有叶节点都在最底层的完全二叉树
25 | - - 平衡二叉树:当且仅当任何节点的两颗子树的高度差不大于1的二叉树
26 | - - 排序二叉树:二叉搜索树
27 | - 霍夫曼树(用于信息编码):带权路径最短的二叉树
28 | - B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有杜宇两个子树
29 |
30 | # 常见的一些树的应用场景
31 |
32 | 1.xml,html等,那么编写这些东西的解析器的时候,不可避免用到树
33 | 2.路由协议就是使用了树的算法
34 | 3.mysql数据库索引
35 | 4.文件系统的目录结构
36 | 5.所以很多经典的AI算法其实都是树搜索,此外机器学习中的decision tree也是树结构
37 |
38 |
39 |
40 | # 二叉树的遍历
41 | ## 深度优先遍历和广度优先遍历
42 | 深度优先一般用递归,广度优先一般用队列。一般情况下能用递归实现的算法大部分也能用堆栈来实现。
43 |
44 | ### 深度优先遍历
45 |
46 |
47 | 先序遍历 在先序遍历中,我们先访问根节点,然后递归使用先序遍历访问左子树,再递归使用先序遍历访问右子树
48 | 根节点->左子树->右子树
49 |
50 | def preorder(self, root):
51 | """递归实现先序遍历"""
52 | if root == None:
53 | return
54 | print root.elem
55 | self.preorder(root.lchild)
56 | self.preorder(root.rchild)
57 |
58 | 中序遍历 在中序遍历中,我们递归使用中序遍历访问左子树,然后访问根节点,最后再递归使用中序遍历访问右子树
59 | 左子树->根节点->右子树
60 |
61 | def inorder(self, root):
62 | """递归实现中序遍历"""
63 | if root == None:
64 | return
65 | self.inorder(root.lchild)
66 | print root.elem
67 | self.inorder(root.rchild)
68 |
69 | 后序遍历 在后序遍历中,我们先递归使用后序遍历访问左子树和右子树,最后访问根节点
70 | 左子树->右子树->根节点
71 |
72 | def postorder(self, root):
73 | """递归实现后续遍历"""
74 | if root == None:
75 | return
76 | self.postorder(root.lchild)
77 | self.postorder(root.rchild)
78 | print root.elem
79 |
80 | 
81 |
82 | 前序和后序在本质上都是将父节点与子结点进行分离,但并没有指明左子树和右子树的能力,因此得到这两个序列只能明确父子关系,而不能确定一个二叉树。
83 |
84 | 由二叉树的中序和前序遍历序列可以唯一确定一棵二叉树 ,由前序和后序遍历则不能唯一确定一棵二叉树
85 |
86 | 由二叉树的中序和后序遍历序列可以唯一确定一棵二叉树,由前序和后序遍历则不能唯一确定一棵二叉树
--------------------------------------------------------------------------------
/7.1二叉树的表示与遍历.py:
--------------------------------------------------------------------------------
1 | # _*_ coding: UTF-8 _*_
2 | """二叉树
3 |
4 | """
5 | # 通过使用Node类中定义三个属性,分别为elem本身的值,还由左右子树
6 | class Node(object):
7 | """节点类"""
8 | def __init__(self, elem = -1, lchild = None, rchild = None):
9 | self.elem = elem
10 | self.lchild = lchild
11 | self.rchild = rchild
12 |
13 | # 树的创建,创建一个树的类,并给一个root根节点,一开始为空,随后添加节点
14 | class Tree(object):
15 | """树类"""
16 | def __init__(self, root = None):
17 | self.root = root
18 |
19 | def add(self, elem):
20 | """为树添加节点"""
21 | node = Node(elem)
22 | # 如果树是空的,则对根节点赋值
23 | if self.root is None:
24 | self.root = node
25 | return
26 | else:
27 | queue = []
28 | queue.append(self.root)
29 | # 对已有节点进行层次遍历
30 | while queue:
31 | # 弹出队列的第一个元素
32 | cur = queue.pop(0)
33 | if cur.lchild == None:
34 | cur.lchild = node
35 | return
36 | elif cur.rchild == None:
37 | cur.rchild = node
38 | return
39 | else:
40 | # 如果左右子树都不为空,加入队列继续判断
41 | queue.append(cur.lchild)
42 | queue.append(cur.rchild)
43 |
44 | # ××××××××× 深度优先遍历 ××××××××××××××××××
45 | def preorder(self, root):
46 | """递归实现先序遍历"""
47 | if root == None:
48 | return
49 | print(root.elem, end= " ")
50 | self.preorder(root.lchild)
51 | self.preorder(root.rchild)
52 |
53 | def inorder(self, root):
54 | """递归实现中序遍历"""
55 | if root == None:
56 | return
57 | self.inorder(root.lchild)
58 | print(root.elem, end= " ")
59 | self.inorder(root.rchild)
60 |
61 | def postorder(self, root):
62 | """递归实现后续遍历"""
63 | if root == None:
64 | return
65 | self.postorder(root.lchild)
66 | self.postorder(root.rchild)
67 | print(root.elem, end= " ")
68 |
69 | # ×××××××××× 广度优先遍历 ×××××××××
70 | # 从树的root开始,从上到下从从左到右遍历整个树的节点
71 | def breadth_travel(self):
72 | """利用队列实现树的层次遍历"""
73 | if self.root == None:
74 | return
75 | queue = [self.root]
76 | while queue:
77 | node = queue.pop(0)
78 | print(node.elem, end= " ")
79 | if node.lchild != None:
80 | queue.append(node.lchild)
81 | if node.rchild != None:
82 | queue.append(node.rchild)
83 |
84 |
85 |
86 | if __name__ == "__main__":
87 | t = Tree()
88 | for i in range(10):
89 | t.add(i)
90 | t.breadth_travel()
91 | print("\n")
92 | t.preorder(t.root)
93 | print("\n")
94 | t.inorder(t.root)
95 | print("\n")
96 | t.postorder(t.root)
97 |
98 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Data_Structure_with_Python
2 | 这是我在学习《基于Python的数据结构》的时候的笔记与代码
3 | 主要参考:[数据结构与算法(Python)](https://jackkuo666.github.io/Data_Structure_with_Python_book/)
4 |
5 |
6 |
7 |
8 | # [0.0.算法效率衡量](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/0.0.%E7%AE%97%E6%B3%95%E6%95%88%E7%8E%87%E8%A1%A1%E9%87%8F.md)
9 | [代码](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/0.1.%E7%AE%97%E6%B3%95%E6%95%88%E7%8E%87%E8%A1%A1%E9%87%8F.py)
10 |
11 | ## 对于算法的时间效率,我们可以用“大O记法”来表示。
12 |
13 | “大O记法”:对于单调的整数函数f,如果存在一个整数函数g和实常数c>0,使得对于充分大的n总有f(n)<=c*g(n),就说函数g是f的一个渐近函数(忽略常数),记为f(n)=O(g(n))。也就是说,在趋向无穷的极限意义下,函数f的增长速度受到函数g的约束,亦即函数f与函数g的特征相似。
14 |
15 | 时间复杂度:假设存在函数g,使得算法A处理规模为n的问题示例所用时间为T(n)=O(g(n)),则称O(g(n))为算法A的渐近时间复杂度,简称时间复杂度,记为T(n)
16 |
17 | ## 最坏时间复杂度
18 | 算法完成工作最多需要多少基本操作,即最坏时间复杂度
19 |
20 | ## 时间复杂度的几条基本计算规则
21 | 基本操作,即只有常数项,认为其时间复杂度为O(1)
22 | 顺序结构,时间复杂度按加法进行计算
23 | 循环结构,时间复杂度按乘法进行计算
24 | 分支结构,时间复杂度取最大值
25 | 判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略
26 | 在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度
27 |
28 | ## 常见时间复杂度
29 | 
30 | ## 常见时间复杂度之间的关系
31 | 
32 |
33 |
34 | # [1.Python内置类型性能分析](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/1.0.Python%E5%86%85%E7%BD%AE%E7%B1%BB%E5%9E%8B%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90.md)
35 | [代码](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/1.1.Python%E5%86%85%E7%BD%AE%E7%B1%BB%E5%9E%8B%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90.py)
36 | ## timeit模块
37 | timeit模块可以用来测试一小段Python代码的执行速度。
38 | ```py
39 | class timeit.Timer(stmt='pass', setup='pass', timer=)
40 | ```
41 |
42 | Timer是测量小段代码执行速度的类。
43 |
44 | stmt参数是要测试的代码语句(statment);
45 |
46 | setup参数是运行代码时需要的设置;
47 |
48 | timer参数是一个定时器函数,与平台有关。
49 | ```
50 | timeit.Timer.timeit(number=1000000)
51 |
52 | ```
53 | Timer类中测试语句执行速度的对象方法。number参数是测试代码时的测试次数,默认为1000000次。方法返回执行代码的平均耗时,一个float类型的秒数。
54 |
55 | ## list内置操作的时间复杂度
56 | 
57 |
58 | ## dict内置操作的时间复杂度
59 | 
60 |
61 | ## Python的内置数据结构
62 | Python给我们提供了很多现成的数据结构类型,这些系统自己定义好的,不需要我们自己去定义的数据结构叫做Python的内置数据结构,比如列表、元组、字典。
63 | ## Python的扩展数据结构
64 | 而有些数据组织方式,Python系统里面没有直接定义,需要我们自己去定义实现这些数据的组织方式,这些数据组织方式称之为Python的扩展数据结构,比如栈,队列等。
65 | ## 算法与数据结构的区别
66 |
67 | 数据结构只是静态的描述了数据元素之间的关系。
68 |
69 | 高效的程序需要在数据结构的基础上设计和选择算法。
70 |
71 | 程序 = 数据结构 + 算法
72 |
73 | 总结:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体
74 |
75 | ## 抽象数据类型(Abstract Data Type)
76 |
77 | 抽象数据类型(ADT)的含义是指一个数学模型以及定义在此数学模型上的一组操作。即把数据类型和数据类型上的运算捆在一起,进行封装。引入抽象数据类型的目的是把数据类型的表示和数据类型上运算的实现与这些数据类型和运算在程序中的引用隔开,使它们相互独立。
78 |
79 | 最常用的数据运算有五种:
80 |
81 | 插入
82 | 删除
83 | 修改
84 | 查找
85 | 排序
86 |
87 | # [2.顺序表](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/2.0.%E9%A1%BA%E5%BA%8F%E8%A1%A8.md)
88 | 代码:
89 | [单链表](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/2.1%E5%8D%95%E9%93%BE%E8%A1%A8.py)
90 | [单项循环链表](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/2.2%E5%8D%95%E9%A1%B9%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8.py)
91 | [双向链表](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/2.3%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.py)
92 |
93 | ## 线性表
94 | 一个线性表是某类元素的一个集合,还记录着元素之间的一种顺序关系。
95 |
96 | 根据线性表的实际存储方式,分为两种实现模型:
97 |
98 | 顺序表,将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示。
99 |
100 | 链表,将元素存放在通过链接构造起来的一系列存储块中。
101 |
102 | ## 顺序表的两种存储方式:
103 |
104 | 元素内置(一体式结构):表里存储的元素大小固定
105 |
106 | 元素外置(分离式结构):表里只存储链接
107 |
108 | ## 表中存储的两个信息
109 |
110 | 1.表中的元素集合
111 |
112 | 2.信息主要包括元素存储区的容量和当前表中已有的元素个数两项
113 |
114 | ## 增加元素
115 |
116 | a. 尾端加入元素,时间复杂度为O(1)
117 |
118 | b. 中间插入,时间复杂度为O(n)
119 |
120 | ## 删除元素
121 |
122 | a. 删除表尾元素,时间复杂度为O(1)
123 |
124 | b. 中间元素删除,时间复杂度为O(n)
125 |
126 | ## Python中的顺序表
127 |
128 | list和tuple
129 |
130 | tuple是不可变类型,即不变的顺序表,因此不支持改变其内部状态的任何操作,而其他方面,则与list的性质类似。
131 |
132 | list就是一种采用分离式技术实现的动态顺序表。
133 |
134 |
135 | ## 链表与顺序表的对比
136 |
137 | 链表失去了顺序表随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大,但对存储空间的使用要相对灵活。
138 |
139 | 链表与顺序表的各种操作复杂度如下所示:
140 |
141 | 
142 |
143 | 链表的主要耗时操作是**遍历查找**
144 |
145 | 顺序表查找很快,主要耗时的操作是**拷贝覆盖**
146 |
147 |
148 | # 3.栈
149 |
150 | [代码](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/3.%E6%A0%88.py)
151 |
152 | # 4.队列
153 | 代码:
154 |
155 | [队列](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/4.1%E9%98%9F%E5%88%97.py)
156 |
157 | [双端队列](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/4.2%E5%8F%8C%E7%AB%AF%E9%98%9F%E5%88%97.py)
158 |
159 | # [5.排序算法](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/5.0.%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md)
160 |
161 | 排序算法(Sorting Algorithm):是一种能将一串数据按照特定顺序进行排列的一种算法。
162 |
163 | ## 稳定性
164 |
165 | 稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
166 |
167 | 当相等的元素是无法分辨的,比如像是整数,稳定性并不是一个问题。然而,假设以下的数对将要以他们的第一个数字来排序。
168 |
169 | (4, 1) (3, 1) (3, 7)(5, 6)
170 |
171 | 在这个状况下,有可能产生两种不同的结果,一个是让相等键值的纪录维持相对的次序,而另外一个则没有:
172 |
173 | (3, 1) (3, 7) (4, 1) (5, 6) (维持次序)
174 |
175 | (3, 7) (3, 1) (4, 1) (5, 6) (次序被改变)
176 |
177 | 不稳定排序算法可能会在相等的键值中改变纪录的相对次序,但是稳定排序算法从来不会如此。不稳定排序算法可以被特别地实现为稳定。作这件事情的一个方式是人工扩充键值的比较,如此在其他方面相同键值的两个对象间之比较,(比如上面的比较中加入第二个标准:第二个键值的大小)就会被决定使用在原先数据次序中的条目,当作一个同分决赛。然而,要记住这种次序通常牵涉到额外的空间负担。
178 |
179 | ## 常见排序算法效率比较
180 |
181 | 
182 |
183 | 代码:
184 |
185 | [冒泡排序](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/5.1%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F.py)
186 |
187 | [选择排序](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/5.2%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F.py)
188 |
189 | [插入排序](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/5.3%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F.py)
190 |
191 | [快速排序](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/5.4%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F.py)
192 |
193 | [希尔排序](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/5.5%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F.py)
194 |
195 | [归并排序](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/5.6.%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F.py)
196 |
197 | ## [希尔排序原理](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/5.5%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F.md)
198 |
199 |
200 | 希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
201 |
202 | ## 希尔排序过程
203 |
204 | 希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。
205 |
206 | 例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样(竖着的元素是步长组成):
207 | ```
208 | 13 14 94 33 82
209 | 25 59 94 65 23
210 | 45 27 73 25 39
211 | 10
212 |
213 | ```
214 |
215 | 然后我们对每列进行排序:
216 |
217 | ```
218 | 10 14 73 25 23
219 | 13 27 94 33 39
220 | 25 59 94 65 82
221 | 45
222 |
223 | ```
224 | 将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。这时10已经移至正确位置了,然后再以3为步长进行排序:
225 | ```
226 | 10 14 73
227 | 25 23 13
228 | 27 94 33
229 | 39 25 59
230 | 94 65 82
231 | 45
232 | ```
233 | 排序之后变为:
234 | ```
235 | 10 14 13
236 | 25 23 33
237 | 27 25 59
238 | 39 65 73
239 | 45 94 82
240 | 94
241 |
242 | ```
243 |
244 | 最后以1步长进行排序(此时就是简单的插入排序了)
245 |
246 |
247 |
248 | # 6.二分查找
249 | [代码](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/6.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.py)
250 |
251 | # [7.树](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/7.0.%E6%A0%91.md)
252 |
253 | [代码](https://github.com/JackKuo666/Data_Structure_with_Python/blob/master/7.1%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E8%A1%A8%E7%A4%BA%E4%B8%8E%E9%81%8D%E5%8E%86.py)
254 |
255 | 树(英语:tree)是一种抽象数据类型(ADT)
256 | 
257 | ## 树的术语
258 |
259 | - 节点的度:一个节点含有的子树的个数称为该节点的度;
260 | - 树的度:一颗树中,最大的节点的度称为树的度;
261 | - 叶节点或终端节点:度为零的节点;
262 | - 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
263 | - 子节点:一个节点含有的子树的根节点称为该节点的子节点;
264 | - 兄弟节点:具有相同父节点的节点互相称为兄弟节点;
265 | - 节点的层次:从根节点开始定义起,根为第一层,根的子节点为第二层,以此类推;
266 | - 树的高度或深度:树中节点的最大层次;
267 | - 堂兄弟节点:父节点在同一层次的节点互为堂兄弟;
268 | - 节点的祖先:从根节点到该节点所经分支上的所有节点;
269 | - 子孙:以某一节点为根的子树中任一节点都称为该节点的子孙;
270 | - 森林:由m(m >= 0)颗互不相交的树的集合称为森林;
271 |
272 | ## 树的种类
273 | - 无序树:树中任意节点的子节点之间没有顺序关系
274 | - 有序树:树中任意节点的子节点之间有顺序关系
275 | - - 二叉树:每个节点最多含有两个子树
276 | - - 完全二叉树:除最底层最后一个外其他必须有子树
277 | - - 满二叉树:所有叶节点都在最底层的完全二叉树
278 | - - 平衡二叉树:当且仅当任何节点的两颗子树的高度差不大于1的二叉树
279 | - - 排序二叉树:二叉搜索树
280 | - 霍夫曼树(用于信息编码):带权路径最短的二叉树
281 | - B树:一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有杜宇两个子树
282 |
283 | ## 常见的一些树的应用场景
284 |
285 | 1.xml,html等,那么编写这些东西的解析器的时候,不可避免用到树
286 | 2.路由协议就是使用了树的算法
287 | 3.mysql数据库索引
288 | 4.文件系统的目录结构
289 | 5.所以很多经典的AI算法其实都是树搜索,此外机器学习中的decision tree也是树结构
290 |
291 |
292 |
293 | ## 二叉树的遍历
294 | ### 深度优先遍历和广度优先遍历
295 | 深度优先一般用递归,广度优先一般用队列。一般情况下能用递归实现的算法大部分也能用堆栈来实现。
296 |
297 | #### 深度优先遍历
298 |
299 |
300 | 先序遍历 在先序遍历中,我们先访问根节点,然后递归使用先序遍历访问左子树,再递归使用先序遍历访问右子树
301 | 根节点->左子树->右子树
302 |
303 | def preorder(self, root):
304 | """递归实现先序遍历"""
305 | if root == None:
306 | return
307 | print root.elem
308 | self.preorder(root.lchild)
309 | self.preorder(root.rchild)
310 |
311 | 中序遍历 在中序遍历中,我们递归使用中序遍历访问左子树,然后访问根节点,最后再递归使用中序遍历访问右子树
312 | 左子树->根节点->右子树
313 |
314 | def inorder(self, root):
315 | """递归实现中序遍历"""
316 | if root == None:
317 | return
318 | self.inorder(root.lchild)
319 | print root.elem
320 | self.inorder(root.rchild)
321 |
322 | 后序遍历 在后序遍历中,我们先递归使用后序遍历访问左子树和右子树,最后访问根节点
323 | 左子树->右子树->根节点
324 |
325 | def postorder(self, root):
326 | """递归实现后续遍历"""
327 | if root == None:
328 | return
329 | self.postorder(root.lchild)
330 | self.postorder(root.rchild)
331 | print root.elem
332 |
333 | 
334 |
335 | 前序和后序在本质上都是将父节点与子结点进行分离,但并没有指明左子树和右子树的能力,因此得到这两个序列只能明确父子关系,而不能确定一个二叉树。
336 |
337 | 由二叉树的中序和前序遍历序列可以唯一确定一棵二叉树 ,由前序和后序遍历则不能唯一确定一棵二叉树
338 |
339 | 由二叉树的中序和后序遍历序列可以唯一确定一棵二叉树,由前序和后序遍历则不能唯一确定一棵二叉树
340 |
--------------------------------------------------------------------------------
/image/0.0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackKuo666/Data_Structure_with_Python/0380488f6e7122c6c2c9a732a9147c59c088164e/image/0.0.png
--------------------------------------------------------------------------------
/image/0.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackKuo666/Data_Structure_with_Python/0380488f6e7122c6c2c9a732a9147c59c088164e/image/0.1.png
--------------------------------------------------------------------------------
/image/1.0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackKuo666/Data_Structure_with_Python/0380488f6e7122c6c2c9a732a9147c59c088164e/image/1.0.png
--------------------------------------------------------------------------------
/image/1.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackKuo666/Data_Structure_with_Python/0380488f6e7122c6c2c9a732a9147c59c088164e/image/1.1.png
--------------------------------------------------------------------------------
/image/2.0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackKuo666/Data_Structure_with_Python/0380488f6e7122c6c2c9a732a9147c59c088164e/image/2.0.png
--------------------------------------------------------------------------------
/image/5.0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackKuo666/Data_Structure_with_Python/0380488f6e7122c6c2c9a732a9147c59c088164e/image/5.0.png
--------------------------------------------------------------------------------
/image/7.0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackKuo666/Data_Structure_with_Python/0380488f6e7122c6c2c9a732a9147c59c088164e/image/7.0.png
--------------------------------------------------------------------------------
/image/7.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackKuo666/Data_Structure_with_Python/0380488f6e7122c6c2c9a732a9147c59c088164e/image/7.1.png
--------------------------------------------------------------------------------