栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

数据结构与算法

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

数据结构与算法

队列和栈

本次讲解的队列与栈,如果你对之前的线性和链式结构顺利掌握了,那么下边的队列和栈就小菜一碟了。因为我们会用前两节讲到的东西来实现队列和栈。 之所以放到一起讲是因为这两个东西很类似,队列是先进先出结构(FIFO, first in first out), 栈是后进先出结构(LIFO, last in first out)。

生活中的数据结构:

  • 队列。没错就是咱平常排队,第一个来的第一个走

队列 Queue
class Queue():
    def __init__(self):
        self.item = DoubleLinkedList()

    def push(self, value):

        self.item.append(value)
    def pop(self):
        value = self.item.top_node().value
        self.item.remove(value)
        return value

用数组实现队列
class Queue:
    def __init__(self, maxsize=4):
        self.item = Array(maxsize)
        self.head = 0
        self.end = 0
        self.maxsize = maxsize

    def push(self, value):
        self.item[self.head % self.maxsize] = value
        self.head += 1

    def pop(self):
        value = self.item[self.end % self.maxsize]
        self.end +=1
        return value

双端队列 Double ended Queue

头尾都能进出

  • append
  • appendleft
  • headnode()
  • tailnode()
  • remove(node)

栈 Stack
from collections import deque


class Stack():
    def __init__(self):
        self.item = deque()

    def add(self,value):
        self.item.append(value)
    def pop(self):
        return self.item.pop()

哈希表

哈希表是种数据结构,它可以提供快速的插入操作和查找操作。第一次接触哈希表时,它的优点多得让人难以置信。不论哈希表中有多少数据,插入和删除(有时包括侧除)只需要接近常量的时间即0(1)的时间级。

哈希表简单的理解:在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。 哈希表最常见的例子是以学生学号为关键字的成绩表,1号学生的记录位置在第一条,10号学生的记录位置在第10条…

哈希表的工作原理

哈希表基于数组的,正因为数组创建后难于扩展某些哈希表被基本填满时,性能下降得非常严重,所以程序虽必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中,这是个费时的过程)

size = 13
h(765) = 765 % size = 11
h(431) = 431 % size = 2
h(96) = 96 % size = 5
h(142) = 142 % size = 12
h(579) = 579 % size = 7
h(226) = 226 % size = 5
h(903) = 903 % size = 6
h(388) = 388 % size = 11

 

哈希冲突 (collision)

这里到插入 226 这个元素的时候,不幸地发现 h(226) = h(96) = 5,不同的 key 通过我们的哈希函数计算后得到的下标一样

解决方法:链接法(chaining)

inserted_index_set = set()
M = 13

def h(key, M=13):
    return key % M

to_insert = [765, 431, 96, 142, 579, 226, 903, 388]
for number in to_insert:
    index = h(number)
    first_index = index
    i = 1
    while index in inserted_index_set:   # 如果计算发现已经占用,继续计算得到下一个可用槽的位置
        print('th({number}) = {number} % M = {index} collision'.format(number=number, index=index))
        index = (first_index +  i*i) % M   # 根据二次方探查的公式重新计算下一个需要插入的位置
        i += 1
    else:
        print('h({number}) = {number} % M = {index}'.format(number=number, index=index))
        inserted_index_set.add(index)

哈希函数

 hash 函数怎么选? 当然是散列得到的冲突越来越小就好啦,也就是说每个 key 都能尽量被等可能地散列到 m 个槽中的任何一个,并且与其他 key 被散列到哪个槽位无关

装载因子(load factor)

如果继续往我们的哈希表里塞东西会发生什么?空间不够用。这里我们定义一个负载因子的概念(load factor),其实很简单,就是已经使用的槽数比哈希表大小。 比如我们上边的例子插入了 8 个元素,哈希表总大小是 13, 它的 load factor 就是 $ 8/13 approx 0.62 $。当我们继续往哈希表插入数据的时候,很快就不够用了。 通常当负载因子开始超过 0.8 的时候,就要新开辟空间并且重新进行散列了。

HashTable ADT
lass Slot(object):
    """定义一个 hash 表 数组的槽
    注意,一个槽有三种状态,看你能否想明白
    1.从未使用 HashMap.UNUSED。此槽没有被使用和冲突过,查找时只要找到 UNUSED 就不用再继续探查了
    2.使用过但是 remove 了,此时是 HashMap.EMPTY,该探查点后边的元素扔可能是有key
    3.槽正在使用 Slot 节点
    """
    def __init__(self, key, value):
        self.key, self.value = key, value

class HashTable(object):
    pass

二叉树的遍历
  • 先(根)序遍历: 先处理根,之后是左子树,然后是右子树
  • 中(根)序遍历: 先处理左子树,之后是根,最后是右子树
  • 后(根)序遍历: 先处理左子树,之后是右子树,最后是根
def iter_node1(self, node):
        if node is not None:
            print(node.data)
            self.iter_node1(node.left)
            self.iter_node1(node.right)

二叉树层序遍历
def iter_node2(self, node):
        node_list = [node]

        for node in node_list:
            print(node.data)
            if node.left:
                node_list.append(node.left)
            if node.right:
                node_list.append(node.right)

还有一种方式就是使用一个队列,之前我们知道队列是一个先进先出结构,如果我们按照一层一层的顺序从左往右把节点放到一个队列里, 也可以实现层序遍历:

def layer_trav_use_queue(self, subtree):
  q = Queue()
  q.append(subtree)
  while not q.empty():
    cur_node = q.pop()
    print(cur_node.data)
    if cur_node.left:
      q.append(cur_node.left)
      if cur_node.right:
        q.append(cur_node.right)


from collections import deque
class Queue(object):  # 借助内置的 deque 我们可以迅速实现一个 Queue
    def __init__(self):
        self._items = deque()

    def append(self, value):
        return self._items.append(value)

    def pop(self):
        return self._items.popleft()

    def empty(self):
        return len(self._items) == 0

反转二叉树
def reverse(self, subtree):
        if subtree is not None:
            subtree.left, subtree.right = subtree.right, subtree.left
            self.reverse(subtree.left)
            self.reverse(subtree.right)

实现一个最大堆
class Heap:
    def __init__(self):
        self.items = Array(8)
        self.count = 0

    def add(self, value):
        # 根据count来决定将值暂时赋值到哪块空间
        self.items[self.count] = value
        # 为了保证下一个数据添加时,不替换上一个添加所在的索引的值
        self.count += 1
        # 将本次添加的数据的索引传到 提权函数中
        self.sift_up(self.count - 1)

    def sift_up(self, index):
        if index <= 0:
            return
        else:
            parent = (index - 1) / 2
            if self.items[parent] <= self.items[index]:
                self.items[parent], self.items[index] = self.items[index], self.items[parent]
                self.sift_up(parent)

    def remove(self):
        value = self.items[0]
        self.count -= 1
        self.items[0] = self.items[self.count]
        self.items[self.count] = None
        self.sift_down(0)
        return value

    def sift_down(self, index):
        left = index * 2 + 1
        right = index * 2 + 2

       largest = index
        if right < self.count:
            if self.item[right] > self.item[largest] and self.item[right]> self.item[left]:
                largest = right
            elif self.item[right] >self.item[largest] and self.item[right] < self.item[left]:
                largest = left
            elif self.item[right] < self.item[largest] and self.item[left] >self.item[largest]:
                largest = left
        elif left < self.count:
            if self.item[left] > self.item[largest]:
                largest = left
        if largest != index:
            self.item[index],self.item[largest] = self.item[largest],self.item[index]
            self.siftdown(largest)
    def __iter__(self):
        for i in self.items:
            if i is not None:
                yield i

二叉查找树(BST)

二叉树的一种应用就是来实现堆,我们再看看用二叉查找树(Binary Search Tree, BST)。 前面有章节说到了查找操作,包括线性查找、二分查找、哈希查找等,线性查找效率比较低,二分又要求必须是有序的序列, 为了维持有序插入的代价比较高、哈希查找效率很高但是浪费空间。能不能有一种插入和查找都比较快的数据结构呢?二叉查找树就是这样一种结构,可以高效地插入和查询节点。

BST 定义

二叉查找树是这样一种二叉树结构,它的每个节点的左子节点小于该节点,每个节点的右子节点小于该节点

构造一个 BST
class BST(object):
    def __init__(self, root=None):
        self.root = root

    @classmethod
    def build_from(cls, node_list):
        cls.size = 0
        key_to_node_dict = {}
        for node_dict in node_list:
            key = node_dict['key']
            key_to_node_dict[key] = BSTNode(key)

        for node_dict in node_list:
            key = node_dict['key']
            node = key_to_node_dict[key]
            if node_dict['is_root']:
                root = node
            node.left = key_to_node_dict.get(node_dict['left'])
            node.right = key_to_node_dict.get(node_dict['right'])
            cls.size += 1
        return cls(root)


NODE_LIST = [
    {'key': 60, 'left': 12, 'right': 90, 'is_root': True},
    {'key': 12, 'left': 4, 'right': 41, 'is_root': False},
    {'key': 4, 'left': 1, 'right': None, 'is_root': False},
    {'key': 1, 'left': None, 'right': None, 'is_root': False},
    {'key': 41, 'left': 29, 'right': None, 'is_root': False},
    {'key': 29, 'left': 23, 'right': 37, 'is_root': False},
    {'key': 23, 'left': None, 'right': None, 'is_root': False},
    {'key': 37, 'left': None, 'right': None, 'is_root': False},
    {'key': 90, 'left': 71, 'right': 100, 'is_root': False},
    {'key': 71, 'left': None, 'right': 84, 'is_root': False},
    {'key': 100, 'left': None, 'right': None, 'is_root': False},
    {'key': 84, 'left': None, 'right': None, 'is_root': False},
]
bst = BST.build_from(NODE_LIST)

BST 操作

查找

如何查找一个指定的节点呢,根据定义我们知道每个内部节点左子树的 key 都比它小,右子树的 key 都比它大,所以 对于带查找的节点 search_key,从根节点开始,如果 search_key 大于当前 key,就去右子树查找,否则去左子树查找。 一直到当前节点是 None 了说明没找到对应 key。

 

获取最大和最小 key 的节点

其实还按照其定义,最小值就一直向着左子树找,最大

def _bst_min_node(self, subtree):
        if subtree is None:
            return None
        elif subtree.left is None:   # 找到左子树的头
            return subtree
        else:
            return self._bst_min_node(subtree.left)

    def bst_min(self):
        node = self._bst_min_node(self.root)
        return node.value if node else None

值一直向右子树找,递归查找就行。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1026979.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号