diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/MyStack001.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/MyStack001.py" new file mode 100644 index 0000000000000000000000000000000000000000..083b1866c3797c99b0930680f056da1711062776 --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/MyStack001.py" @@ -0,0 +1,64 @@ +class MyStack: + """ + 栈有固定长度, 需要考虑栈溢出和空栈的情况 + top属性用来指示栈顶的索引值 + """ + def __init__(self, _size=10): + self.stack = [] + self.top = -1 + self.size = _size + + def is_full(self): + return self.top == self.size - 1 + + def push(self, item): + """ + 首先判断当前栈是否已满 + :param item: + :return: + """ + if self.is_full():#栈满了,抛出异常 + raise Exception("StackOverflow") + self.stack.append(item) + self.top += 1 + + def is_empty(self): + return self.top == -1 + + def pop(self): + """ + 首先要判断当前栈是不是空栈 + :return: + """ + if self.is_empty(): + raise Exception("StackUnderflow") + self.top -= 1 + return self.stack.pop() + +if __name__ == "__main__": + # 1 + 2 * 3 + print('1 + 2 * 3') + my_stack = MyStack() + my_stack.push(1) + my_stack.push("+") + my_stack.push(2) + my_stack.push("*") + my_stack.push(3) + + # 简单示例运算过程, 实际情况执行要复杂的多, 背后的指令也更优雅 + # 栈只有一个元素的时候, 运算正式结束 + while my_stack.top > 0: + item_1 = my_stack.pop() + operator = my_stack.pop() + item_2 = my_stack.pop() + if operator == "*": + my_stack.push(item_1 * item_2) + elif operator == "+": + my_stack.push(item_1 + item_2) + + # 结果就在栈底 + print(my_stack.pop()) + +# jeiguo结果 +# 1 + 2 * 3 +# 7 \ No newline at end of file diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/hashmap.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/hashmap.py" new file mode 100644 index 0000000000000000000000000000000000000000..5f8b0755485853a0d1fda80c429e7de22950cd8c --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/hashmap.py" @@ -0,0 +1,15 @@ +# print(hash('tser')) +my_dict={} + +# // 当前的函数是线程安全的 +def foo(a): + my_dict.update({"a": a}) + + +# // 通过dis库查看的字节码, 如果关键代码只有一条操作指令, 那就是线程安全的 +import dis +print(dis.dis(foo)) + +# // 当前的函数不是线程安全的 +def foo(a): + my_dict['a'] += 1 diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/hashmap02.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/hashmap02.py" new file mode 100644 index 0000000000000000000000000000000000000000..e323824ff1e7f346e74badd99c5f722f7e288bf9 --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/hashmap02.py" @@ -0,0 +1,20 @@ +from threading import Thread + +my_dict = {"a": 0} + + +def foo(a): + for i in range(10**6): + my_dict['a'] += a + + +if __name__ == "__main__": + thread_1 = Thread(target=foo, args=(1, )) + thread_2 = Thread(target=foo, args=(-1, )) + thread_1.start() + thread_2.start() + + thread_1.join() + thread_2.join() + + print(my_dict) \ No newline at end of file diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/linked_list.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/linked_list.py" new file mode 100644 index 0000000000000000000000000000000000000000..af389bb9b109598e46873bda3cc03c6d2d32c8ae --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/linked_list.py" @@ -0,0 +1,143 @@ +class Node: + def __init__(self,data): + self.data=data + self.next=None + + def __str__(self): + return f"" + +class LinkedList: + def __init__(self): + self.head = None + self.end = self.head + + def append(self, node): + """ + 向链表尾部添加一个节点 + 1. 尾部添加: end.next -> node, end -> node + 2. 当前头部没有节点: head -> node + :param node: + :return: + """ + if not self.head: + self.head = node + else: + self.end.next = node + self.end = node + + def insert(self, index, node): + """ + 向index值插入一个节点 + 1. 插入的是中间节点: 找到index值的节点, cur.next -> node node->next + 2. 遍历的过程当中, 结果index值超过了当前链表的长度, 我们抛出异常 + 3. 在头部插入节点: head -> node, node.next = head + 4. 在尾部插入节点: 跟中间节点是一样的, 但是end -> node + :param index: + :param node: + :return: + """ + # 在原index值元素的左边插入 -> 在原index-1值对应元素的右边插入 + cur = self.head + if index == 0: + node.next = self.head + self.head = node + return + + for i in range(index-1): + cur = cur.next + if cur is None: + raise IndexError("LinkedList insert node exceed max length") + + node.next, cur.next = cur.next, node + + if node.next is None: + self.end = node + + def remove(self, node): + """ + 通过遍历删除给定的节点 + 1. 移除的是中间节点: prev.next -> cur.next, cur.next -> None, + 2. 移除的是头节点: head -> cur.next, cur.next -> None + 3. 移除的是尾节点: cur.next本身指向的就是None,和 1一致, end -> prev + :param node: + :return: + """ + cur = self.head + prev = None + while cur: + if cur.data == node.data: #cur就是要删除的那个节点 + if prev is None: + self.head = cur.next + else: + prev.next = cur.next + cur.next = None + + if prev and prev.next is None: + self.end = prev + return + + prev = cur #prev往后移动一个 + cur = cur.next #cur往后移动一个 + + def reverse(self): + """ + 翻转当前链表 + 1. 中间节点: cur.next -> prev + 2. 头节点: cur.next -> prev + 3. 尾节点: cur.next -> prev + 4. 处理原本的head和end + :return: + """ + # 能被翻转说明链表长度 > 1 + if self.head and self.head.next: + cur = self.head.next + prev = self.head + + # 原本头节点的next需要断开 + self.head.next = None + # 原本头节点就变成了尾节点 + self.end = prev + while cur: + # 这里设计到next,cur, prev三个节点, 所以引入中间变量next + next = cur.next + cur.next = prev + + prev = cur + cur = next + + # 翻转后,头节点指向了原本的尾节点 + self.head = prev + else: + return + + def __str__(self): + """ + 通过遍历的方式打印当前链表 + 初始节点为Head, 如果当前指针指向的是NULL, 说明我们到达了结尾 + :return: + """ + cur = self.head + result = "" + while cur: + result += str(cur) + "\t" + cur = cur.next + return result + + +if __name__ == "__main__": + node_1 = Node(1) + node_2 = Node(2) + node_3 = Node(3) + node_4 = Node(4) + + linked_list = LinkedList() + linked_list.append(node_1) + linked_list.append(node_2) + linked_list.append(node_3) + # linked_list.insert(3, Node(1.5)) + linked_list.insert(3, Node(3.5)) + linked_list.append(node_4) + # linked_list.remove(Node(1.5)) + linked_list.reverse() + print(linked_list) + diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/shuang_list.py" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/shuang_list.py" new file mode 100644 index 0000000000000000000000000000000000000000..9b04126814c540a340aa1bdcb5d54d26804fd335 --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/shuang_list.py" @@ -0,0 +1,179 @@ +class Node(object): + # 双向链表节点 + def __init__(self, item): + self.item = item + self.next = None + self.prev = None + + +class DLinkList(object): + # 双向链表 + def __init__(self): + self._head = None + + def is_empty(self): + # 判断链表是否为空 + return self._head == None + + def get_length(self): + # 返回链表的长度 + cur = self._head + count = 0 + while cur != None: + count = count+1 + cur = cur.next + return count + + def print_list(self): + # 遍历链表 + items_list = [] + cur = self._head + while cur != None: + items_list.append(cur.item) + # print(cur.item) + cur = cur.next + + print(items_list) + # print("") + + def add(self, item): + # 头部插入元素 + node = Node(item) + if self.is_empty(): + # 如果是空链表,将 node 赋值给 _head + self._head = node + else: + # 将 node 的 next 属性指向头节点 _head + node.next = self._head + # 将头节点 _head 的 prev 属性指向 node + self._head.prev = node + # 将 node 赋值给 _head + self._head = node + + def append(self, item): + # 尾部插入元素 + node = Node(item) + if self.is_empty(): + # 如果是空链表,将 node 赋值给 _head + self._head = node + else: + # 循环移动到链表尾部结点的位置 + cur = self._head + while cur.next != None: + cur = cur.next + # 将尾结点 cur 的 next 属性指向 node + cur.next = node + # 将 node 的 prev 属性指向 cur + node.prev = cur + + def search(self, item): + # 查找元素是否存在 + cur = self._head + while cur != None: + if cur.item == item: + return True + cur = cur.next + return False + + def insert(self, pos, item): + # 在指定位置添加节点 + if pos <= 0: + self.add(item) + elif pos > (self.get_length()-1): + self.append(item) + else: + node = Node(item) + cur = self._head + count = 0 + # 移动到指定位置的前一个位置 + while count < (pos-1): + count += 1 + cur = cur.next + # 将 node 的 prev 属性指向 cur + node.prev = cur + # 将 node 的 next 属性指向 cur 的下一个节点 + node.next = cur.next + # 将 cur 的下一个节点的 prev 属性指向 node + cur.next.prev = node + # 将 cur 的 next 指向 node + cur.next = node + + def remove(self, item): + # 删除元素 + if self.is_empty(): + return + else: + cur = self._head + if cur.item == item: + # 如果首节点的元素即是要删除的元素 + if cur.next == None: + # 如果链表只有这一个节点 + self._head = None + else: + # 将第二个节点的 prev 属性设置为 None + cur.next.prev = None + # 将 _head 指向第二个节点 + self._head = cur.next + return + while cur != None: + if cur.item == item: + # 将 cur 的前一个节点的 next 指向 cur 的后一个节点 + cur.prev.next = cur.next + # 将 cur 的后一个节点的 prev 指向 cur 的前一个节点 + cur.next.prev = cur.prev + break + cur = cur.next + + def reverse(self): + """ + 将链表头尾反转 + :return: + """ + prev = None + current = self._head # 将头节点保存在current中 + + # 当链表为非空的时候,需要执行相应反转的操作 + # 分别将相邻的两个节点的前驱后继关系进行反转 + while current: + next_node = current.next # 将下一个节点保存在next_node中 + current.next = prev # 由于反转链表,因此头节点反转后,成为尾节点,应该指向None + current.prev = next_node # 尾节点的前驱应指向原本的后继 + + prev = current # 更新prev,向后移动 + current = next_node # 更新current,向后移动 + + # 到达链表尾部时,需要特殊处理 + self._head = prev + + def init_list(self, list):#列表元素依次插入链表 + for item in list: + self.append(item) + +if __name__=="__main__": + dlist = DLinkList() + + dlist.append("yun") + dlist.append("zhu") + list = [1, 2, 'A','B'] + dlist.init_list(list) + dlist.print_list() + + print(dlist.get_length()) + print(dlist.search("B")) + dlist.insert(2, "C") + dlist.print_list() + + dlist.reverse() + dlist.print_list() + + dlist2 = DLinkList() + dlist2.init_list(list) + dlist2.print_list() + +# 结果 +# ['yun', 'zhu', 1, 2, 'A', 'B'] +# 6 +# True +# ['yun', 'zhu', 'C', 1, 2, 'A', 'B'] +# ['B', 'A', 2, 1, 'C', 'zhu', 'yun'] +# [1, 2, 'A', 'B'] \ No newline at end of file diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250\343\200\201\346\240\210\345\222\214\351\230\237\345\210\227\343\200\201\346\225\260\344\270\216\345\240\206-22-1noteyun.md" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250\343\200\201\346\240\210\345\222\214\351\230\237\345\210\227\343\200\201\346\225\260\344\270\216\345\240\206-22-1noteyun.md" new file mode 100644 index 0000000000000000000000000000000000000000..b0fa40d4c9ed72b16a4ef7d5665665f5e072bf01 --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/\346\225\260\347\273\204\345\222\214\351\223\276\350\241\250\343\200\201\346\240\210\345\222\214\351\230\237\345\210\227\343\200\201\346\225\260\344\270\216\345\240\206-22-1noteyun.md" @@ -0,0 +1,132 @@ +22-1note-yun + +链表数组、栈与队列、数和堆-22-1noteyun + +[![2MhQrq.png](https://z3.ax1x.com/2021/06/02/2MhQrq.png)](https://imgtu.com/i/2MhQrq) + + + + + +# 链表和数组 + +- 数组 + + > Python的list是由数组来实现的 + + 有序的元素序列, 在内存中表现为一块连续的内存区域; + +- 链表 + + 通过**指针**将无序的列表链接起来, 每个节点都存储着**当前节点的值**和**下一个节点的内存地址** + + ![wwFny.png](https://i.im5i.com/2021/04/06/wwFny.png) + +- 链表和数组有什么区别? + + - 实现有序的方式是不一样的, 数组是连续的内存. 链表通过持有下一个节点的内存地址来达到有序的目的; + - 基于上述的特性, 数组在进行增删改查的时候钥耗费大量的系统资源来移动元素, 而链表只需要修改保存的地址即可. + +# 栈 + +栈的特点是后入先出`LIFO last in first out` + +> 可以将栈想象为一个有底的玻璃瓶, 那我们存取东西都必须遵守后入先出. + +**![wGeMl.png](https://i.im5i.com/2021/04/11/wGeMl.png)** + +# 队列 + +队列的特点是先入后出`FIFO first in first out` + +> 可以将队列想象为一个没有封口的玻璃管, 但是该玻璃管只有一个口可以添加元素, 另一个口吐出元素. 那么队列获取元素必然遵守先入后出. + +![wGLnn.png](https://i.im5i.com/2021/04/11/wGLnn.png) + +# 散列表 + +> python中的dict本质就是散列表 +> +> 详细见22-3 + +散列表也叫`hashmap`. 通过将`key`值映射到数组中的一个位置来访问. 这个映射函数就叫做散列函数, 存放记录的数组也叫散列表 + +![wqqUG.png](https://i.im5i.com/2021/04/10/wqqUG.png) + +# 树和堆 + +树是一种特殊的链表结构, 每个节点下有若干个子节点 + +![wG0V7.png](https://i.im5i.com/2021/04/11/wG0V7.png) + +- 树的分类 + + ![CZN2O.png](https://i.im5i.com/2021/05/30/CZN2O.png) + +- 二叉树 + + 每个节点下最多只有两个节点 + + ![wGDW2.png](https://i.im5i.com/2021/04/11/wGDW2.png) + + - 平衡二叉树 + + 二叉树节点下可以只有一个子节点, 如果二叉树中节点`1-> 2 -> 3 -> 4 -> 5`, 那么当前的树结构退化成了链表, 为了解决这么一个情况, 就有了平衡二叉树. + + > 平衡二叉树的任意节点的左子树的高度与右子树的高度差不可以超过1. **<=1;** + + **![wGa0P.png](https://i.im5i.com/2021/04/11/wGa0P.png)** + + - 红黑树 + + 因为平衡二叉树要严格保证左右子树的高度不超过1, 在实际场景中, 平衡二叉树需要频繁地进行调整. + + 性能有问题;红黑树,:左旋、右旋和变色。 + +- 二叉堆 (左右子节点 小于根 左右子节点大于根 最小堆、最大堆) + + 二叉堆是一个完全二叉树, 满足当前任意节点要`<=`或者`>=`左右子节点, 一般使用数组来实现. + + - 最大堆 + + 当前任意节点要`>=`左右子节点 + + ![wGMxS.png](https://i.im5i.com/2021/04/11/wGMxS.png) + + - 最小堆 + + 略 + +- B树 + + - B树解决了什么问题? + + B树的目的是在搜索树的基础上优化了磁盘获取的效率 + + > 大部分数据查询的瓶颈在磁盘IO上, 从磁盘中读取1kb数据和1b数据消耗的时间基本是一样的, 在平衡二叉树的基础上, 每个节点尽可能多地存储数据 + + ![wGpND.png](https://i.im5i.com/2021/04/11/wGpND.png) + +- B+树解决了什么问题? 为了优化B树的查找速度, B树的每一个节点都是数据, 而B+树非子节点存储的是数据的地址(索引值), 子节点存储的是数据, 而且子节点会指向相邻的子节点, 都成一个有序链表. + + > B树适合作文件系统. B+树适合作遍历和查找. + + ![wGkhL.png](https://i.im5i.com/2021/04/11/wGkhL.png) + +## B树运用的理念 + +- 保持键值有序,以顺序遍历 +- 使用层次化的索引来最小化磁盘读取 +- 使用不完全填充的块来加速插入和删除 +- 通过优雅的遍历算法来保持索引平衡 + +另外,B树通过保证内部节点至少半满来最小化空间浪费。一棵B树可以处理任意数目的插入和删除。 + + + + + +**课后作业** + +- 了解并复述每一种数据结构. + diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/\351\223\276\350\241\250\343\200\201\351\223\276\350\241\250\347\247\215\347\261\273\343\200\201\345\215\225\351\223\276\350\241\250\347\232\204\345\256\236\347\216\260-22-2noteyun.md" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/\351\223\276\350\241\250\343\200\201\351\223\276\350\241\250\347\247\215\347\261\273\343\200\201\345\215\225\351\223\276\350\241\250\347\232\204\345\256\236\347\216\260-22-2noteyun.md" new file mode 100644 index 0000000000000000000000000000000000000000..c1e26243d696b257e9f3bda70a142a06d14242ca --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/\351\223\276\350\241\250\343\200\201\351\223\276\350\241\250\347\247\215\347\261\273\343\200\201\345\215\225\351\223\276\350\241\250\347\232\204\345\256\236\347\216\260-22-2noteyun.md" @@ -0,0 +1,404 @@ +22-2-noteyun + +链表、链表种类、单链表的实现 + + + +# 链表 + +通过指针将**无序**的列表链接起来. 每个节点都存储着**当前节点的值**和**下一个节点的地址** + +- 链表的缺点 + + 链表的查找是从头节点逐个节点遍历, 查找效率低. + +- 链表的应用 + + - 系统的文件系统 + + 我们存放在磁盘上的文件并不是连续的, 我们通过链表来对文件进行目录归类. + + - git的提交节点 + + - 其他数据结构的基础, 比如树结构 + +# 链表的种类 + +- 单链表 + + ![wwFny.png](https://i.im5i.com/2021/04/06/wwFny.png) + +- 双链表 + + ![ww1B3.png](https://i.im5i.com/2021/04/06/ww1B3.png) + +- 环形链表 + + ![whkhQ.png](https://i.im5i.com/2021/04/06/whkhQ.png) + +# 单链表的实现 + +- 声明Node类(自定义一个数据结构Node) + + ```python + class Node: + def __init__(self, data): + self.data = data + self.next = None + + def __str__(self): + return f"" + + + if __name__=="__main__": + node1=Node(1) + node2=Node(2) + print(node1.next) + ``` + +- 实现LinkedList数据结构 + + ```python + class Node: + def __init__(self,data): + self.data=data + self.next=None + + def __str__(self): + return f"" + + class LinkedList: + def __init__(self): + self.head = None + self.end = self.head + + def append(self, node): + """ + 向链表尾部添加一个节点 + 1. 尾部添加: end.next -> node, end -> node + 2. 当前头部没有节点: head -> node + :param node: + :return: + """ + if not self.head: + self.head = node + else: + self.end.next = node + self.end = node + + def insert(self, index, node): + """ + 向index值插入一个节点 + 1. 插入的是中间节点: 找到index值的节点, cur.next -> node node->next + 2. 遍历的过程当中, 结果index值超过了当前链表的长度, 我们抛出异常 + 3. 在头部插入节点: head -> node, node.next = head + 4. 在尾部插入节点: 跟中间节点是一样的, 但是end -> node + :param index: + :param node: + :return: + """ + # 在原index值元素的左边插入 -> 在原index-1值对应元素的右边插入 + cur = self.head + if index == 0: + node.next = self.head + self.head = node + return + + for i in range(index-1): + cur = cur.next + if cur is None: + raise IndexError("LinkedList insert node exceed max length") + + node.next, cur.next = cur.next, node + + if node.next is None: + self.end = node + + def remove(self, node): + """ + 通过遍历删除给定的节点 + 1. 移除的是中间节点: prev.next -> cur.next, cur.next -> None, + 2. 移除的是头节点: head -> cur.next, cur.next -> None + 3. 移除的是尾节点: cur.next本身指向的就是None,和 1一致, end -> prev + :param node: + :return: + """ + cur = self.head + prev = None + while cur: + if cur.data == node.data: #cur就是要删除的那个节点 + if prev is None: + self.head = cur.next + else: + prev.next = cur.next + cur.next = None + + if prev and prev.next is None: + self.end = prev + return + + prev = cur #prev往后移动一个 + cur = cur.next #cur往后移动一个 + + def reverse(self): + """ + 翻转当前链表 + 1. 中间节点: cur.next -> prev + 2. 头节点: cur.next -> prev + 3. 尾节点: cur.next -> prev + 4. 处理原本的head和end + :return: + """ + # 能被翻转说明链表长度 > 1 + if self.head and self.head.next: + cur = self.head.next + prev = self.head + + # 原本头节点的next需要断开 + self.head.next = None + # 原本头节点就变成了尾节点 + self.end = prev + while cur: + # 这里设计到next,cur, prev三个节点, 所以引入中间变量next + next = cur.next + cur.next = prev + + prev = cur + cur = next + + # 翻转后,头节点指向了原本的尾节点 + self.head = prev + else: + return + + def __str__(self): + """ + 通过遍历的方式打印当前链表 + 初始节点为Head, 如果当前指针指向的是NULL, 说明我们到达了结尾 + :return: + """ + cur = self.head + result = "" + while cur: + result += str(cur) + "\t" + cur = cur.next + return result + + + if __name__ == "__main__": + node_1 = Node(1) + node_2 = Node(2) + node_3 = Node(3) + node_4 = Node(4) + + linked_list = LinkedList() + linked_list.append(node_1) + linked_list.append(node_2) + linked_list.append(node_3) + # linked_list.insert(3, Node(1.5)) + linked_list.insert(3, Node(3.5)) + linked_list.append(node_4) + # linked_list.remove(Node(1.5)) + linked_list.reverse() + print(linked_list) + + + ``` + + + + + +**课后作业** + +- 了解链表的结构和优缺点 +- 了解链表的种类 +- 实现单链表的各种接口 +- 扩展: 实现双链表的各种接口 + + + +# 双链表 + +```python +class Node(object): + # 双向链表节点 + def __init__(self, item): + self.item = item + self.next = None + self.prev = None + + +class DLinkList(object): + # 双向链表 + def __init__(self): + self._head = None + + def is_empty(self): + # 判断链表是否为空 + return self._head == None + + def get_length(self): + # 返回链表的长度 + cur = self._head + count = 0 + while cur != None: + count = count+1 + cur = cur.next + return count + + def print_list(self): + # 遍历链表 + items_list = [] + cur = self._head + while cur != None: + items_list.append(cur.item) + # print(cur.item) + cur = cur.next + + print(items_list) + # print("") + + def add(self, item): + # 头部插入元素 + node = Node(item) + if self.is_empty(): + # 如果是空链表,将 node 赋值给 _head + self._head = node + else: + # 将 node 的 next 属性指向头节点 _head + node.next = self._head + # 将头节点 _head 的 prev 属性指向 node + self._head.prev = node + # 将 node 赋值给 _head + self._head = node + + def append(self, item): + # 尾部插入元素 + node = Node(item) + if self.is_empty(): + # 如果是空链表,将 node 赋值给 _head + self._head = node + else: + # 循环移动到链表尾部结点的位置 + cur = self._head + while cur.next != None: + cur = cur.next + # 将尾结点 cur 的 next 属性指向 node + cur.next = node + # 将 node 的 prev 属性指向 cur + node.prev = cur + + def search(self, item): + # 查找元素是否存在 + cur = self._head + while cur != None: + if cur.item == item: + return True + cur = cur.next + return False + + def insert(self, pos, item): + # 在指定位置添加节点 + if pos <= 0: + self.add(item) + elif pos > (self.get_length()-1): + self.append(item) + else: + node = Node(item) + cur = self._head + count = 0 + # 移动到指定位置的前一个位置 + while count < (pos-1): + count += 1 + cur = cur.next + # 将 node 的 prev 属性指向 cur + node.prev = cur + # 将 node 的 next 属性指向 cur 的下一个节点 + node.next = cur.next + # 将 cur 的下一个节点的 prev 属性指向 node + cur.next.prev = node + # 将 cur 的 next 指向 node + cur.next = node + + def remove(self, item): + # 删除元素 + if self.is_empty(): + return + else: + cur = self._head + if cur.item == item: + # 如果首节点的元素即是要删除的元素 + if cur.next == None: + # 如果链表只有这一个节点 + self._head = None + else: + # 将第二个节点的 prev 属性设置为 None + cur.next.prev = None + # 将 _head 指向第二个节点 + self._head = cur.next + return + while cur != None: + if cur.item == item: + # 将 cur 的前一个节点的 next 指向 cur 的后一个节点 + cur.prev.next = cur.next + # 将 cur 的后一个节点的 prev 指向 cur 的前一个节点 + cur.next.prev = cur.prev + break + cur = cur.next + + def reverse(self): + """ + 将链表头尾反转 + :return: + """ + prev = None + current = self._head # 将头节点保存在current中 + + # 当链表为非空的时候,需要执行相应反转的操作 + # 分别将相邻的两个节点的前驱后继关系进行反转 + while current: + next_node = current.next # 将下一个节点保存在next_node中 + current.next = prev # 由于反转链表,因此头节点反转后,成为尾节点,应该指向None + current.prev = next_node # 尾节点的前驱应指向原本的后继 + + prev = current # 更新prev,向后移动 + current = next_node # 更新current,向后移动 + + # 到达链表尾部时,需要特殊处理 + self._head = prev + + def init_list(self, list):#列表元素依次插入链表 + for item in list: + self.append(item) + +if __name__=="__main__": + dlist = DLinkList() + + dlist.append("yun") + dlist.append("zhu") + list = [1, 2, 'A','B'] + dlist.init_list(list) + dlist.print_list() + + print(dlist.get_length()) + print(dlist.search("B")) + dlist.insert(2, "C") + dlist.print_list() + + dlist.reverse() + dlist.print_list() + + dlist2 = DLinkList() + dlist2.init_list(list) + dlist2.print_list() + +# 结果 +# ['yun', 'zhu', 1, 2, 'A', 'B'] +# 6 +# True +# ['yun', 'zhu', 'C', 1, 2, 'A', 'B'] +# ['B', 'A', 2, 1, 'C', 'zhu', 'yun'] +# [1, 2, 'A', 'B'] +``` + diff --git "a/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/\351\223\276\350\241\250\345\222\214\346\225\260\347\273\204\343\200\201\346\240\210\343\200\201\351\230\237\345\210\227\343\200\201\346\225\243\345\210\227\350\241\250\343\200\201\346\240\221\345\222\214\345\240\206-22-3-note-yun.md" "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/\351\223\276\350\241\250\345\222\214\346\225\260\347\273\204\343\200\201\346\240\210\343\200\201\351\230\237\345\210\227\343\200\201\346\225\243\345\210\227\350\241\250\343\200\201\346\240\221\345\222\214\345\240\206-22-3-note-yun.md" new file mode 100644 index 0000000000000000000000000000000000000000..a7c677b4014c12cdc85b52e6615d09a730eb0da7 --- /dev/null +++ "b/\347\254\254\344\272\214\346\234\237\350\256\255\347\273\203\350\220\245/5\347\217\255/5\347\217\255_\344\272\221/week22/\351\223\276\350\241\250\345\222\214\346\225\260\347\273\204\343\200\201\346\240\210\343\200\201\351\230\237\345\210\227\343\200\201\346\225\243\345\210\227\350\241\250\343\200\201\346\240\221\345\222\214\345\240\206-22-3-note-yun.md" @@ -0,0 +1,258 @@ +链表和数组、栈、队列、散列表、树和堆-22-3-note-yun + + + +[![2wtIGF.png](https://z3.ax1x.com/2021/06/07/2wtIGF.png)](https://imgtu.com/i/2wtIGF) + +## 链表和数组 + +见22-1 + +## 栈 + +栈的特点是**后入先出**`LIFO last in first out` + +> 可以将栈想象为一个有底的玻璃瓶, 那我们存取东西都必须遵守**后入先出.** + +**![wGeMl.png](https://i.im5i.com/2021/04/11/wGeMl.png)** + +- 用`list`来实现一个栈 栈有长度 + + ```python + class MyStack: + """ + 栈有固定长度, 需要考虑栈溢出和空栈的情况 + top属性用来指示栈顶的索引值 + """ + def __init__(self, _size=10): + self.stack = [] + self.top = -1 + self.size = _size + + def is_full(self): + return self.top == self.size - 1 + + def push(self, item): + """ + 首先判断当前栈是否已满 + :param item: + :return: + """ + if self.is_full():#栈满了,抛出异常 + raise Exception("StackOverflow") + self.stack.append(item) + self.top += 1 + + def is_empty(self): + return self.top == -1 + + def pop(self): + """ + 首先要判断当前栈是不是空栈 + :return: + """ + if self.is_empty(): + raise Exception("StackUnderflow") + self.top -= 1 + return self.stack.pop() + + if __name__ == "__main__": + # 1 + 2 * 3 + print('1 + 2 * 3') + my_stack = MyStack() + my_stack.push(1) + my_stack.push("+") + my_stack.push(2) + my_stack.push("*") + my_stack.push(3) + + # 简单示例运算过程, 实际情况执行要复杂的多, 背后的指令也更优雅 + # 栈只有一个元素的时候, 运算正式结束 + while my_stack.top > 0: + item_1 = my_stack.pop() + operator = my_stack.pop() + item_2 = my_stack.pop() + if operator == "*": + my_stack.push(item_1 * item_2) + elif operator == "+": + my_stack.push(item_1 + item_2) + + # 结果就在栈底 + print(my_stack.pop()) + + # jeiguo结果 + # 1 + 2 * 3 + # 7 + ``` + +## 队列 + +队列的特点是先入先出`FIFO first in first out` + +> 可以将队列想象为一个没有封口的玻璃管, 但是该玻璃管只有一个口可以添加元素, 一个口吐出元素. 那么队列获取元素必然遵守先入后出. + +![wGLnn.png](https://i.im5i.com/2021/04/11/wGLnn.png) + +**22-1补充** 22-3 + +## 散列表 + +> python中的dict本质就是散列表 + +散列表也叫`hashmap`. 通过将`key`值映射到数组中的一个位置来访问. 这个映射函数就叫做散列函数, 存放记录的数组也叫散列表 + +散列冲突:两个key产生同一个结果; + + + +![wqqUG.png](https://i.im5i.com/2021/04/10/wqqUG.png) + +- 散列函数有哪些? + + https://blog.csdn.net/qq_33182756/article/details/80688417 + + https://blog.csdn.net/liu17234050/article/details/104270709 + + + + 一个好得散列函数要满足以下条件: 1. 均匀铺满散列表, 节约内存空间; 2. 散列冲突概率低 + + - 直接定址法 + + 适合`key`是连续得或者当前表较小的情况, 否则会有巨大的空间浪费. + + ``` + f(n) = a*n + b + + f(1) = a*1 + b + f(10000) = a* 10000 + b + ``` + + - 数字分析法 + + 找出`key`值的规律, 构建冲突比较低的散列函数 + + > 比如姓名, 显然姓很容易冲突, 所以根据名来定散列函数. + + - 平方取中法 + + 取关键字平方后的中间做为散列地址 + + - 折叠法 + + 将关键字分割成位数相同的几个部分, 然后取这几个部分的叠加和做为散列函数 + + - 随机数法 + + 选择一个随机函数, 取关键字的随机值做为散列地址, 通过用于关键字长度不同的场合 + + - 除留余数法 + + 取关键字被某个大于散列表长度的数`P`除后得到余数做为散列地址. + +- **Python用的是哪种散列函数呢**? + + 具体要看数据类型, 使用的散列函数大多也是混合方法. + + ```python + print(hash('test')) + ``` + + + +- 什么是散列冲突? + + 不同的`key`理应得到不同的散列地址, 散列冲突就是不同`key`得到了同一个散列地址. + +- 如果解决散列冲突? + + - 开放寻址法(Python) + + 线性地扫描散列表, 直到找到一个空单元. + + - 链表法(Java) + + 所有散列值相同的元素都放到相同位置的链表中 + + ![wqCvW.png](https://i.im5i.com/2021/04/10/wqCvW.png) + +- `hashmap`是线程安全的吗? + + - 在`jdk1.8`中, 内部使用的是数组+链表+红黑树, 当进行散列冲突的时候, 注定会有一个数据丢失. + + - 在`python`中, 由于`GIL`内置的数据结构都是线程安全的. 但是对于实际应用中, 我们讲线程安全都是 **针对的操作.** GIL全局解释器锁 + + ```python + // 当前的函数是线程安全的 + def foo(a): + my_dict.update({"a": a}) + + + // 通过dis库查看的字节码, 如果关键代码只有一条操作指令, 那就是线程安全的 + import dis + print(dis.dis(foo)) + + // 当前的函数不是线程安全的 + def foo(a): + my_dict['a'] += 1 + ``` + + - 例 + + ```python + from threading import Thread + + my_dict = {"a": 0} + + + def foo(a): + for i in range(10**6): + my_dict['a'] += a + + + if __name__ == "__main__": + thread_1 = Thread(target=foo, args=(1, )) + thread_2 = Thread(target=foo, args=(-1, )) + thread_1.start() + thread_2.start() + + thread_1.join() + thread_2.join() + + print(my_dict) + ``` + +- 什么是线程安全? + + 实际应用的角度来说, 加锁的就是线程安全, 不加锁的就是线程不安全. + +- 为什么在有`GIL`的情况, 线程仍然是不安全的? + + 全局解释器锁只保证了同一个时刻一个线程在运行, 但是不能保证切换到下一个线程还原的现场还有效. + +- `dict`扩容过程以及安全问题? + + `PyDict_SetItem`会计算`key`的散列值, 然后把需要的信息传递给`insertdict`. 在插入之前根据`ma_table`剩余空间的大小来判断是否扩容, 一般超过2/3就会进行扩容. + + > 2/3开始扩容原因就是要给散列函数足够的空间. + +[![2wYQpQ.png](https://z3.ax1x.com/2021/06/07/2wYQpQ.png)](https://imgtu.com/i/2wYQpQ) + +[![2wYllj.png](https://z3.ax1x.com/2021/06/07/2wYllj.png)](https://imgtu.com/i/2wYllj) + + + + + + + +## 树和堆 + +见22-1 + + + +**课后作业** + +- 了解和复述hashmap +- 用`list`来实现栈 \ No newline at end of file