python数据结构学习笔记(九)

  • Priority Queues

    • 9.1 ADT
    • 9.2 implementing a priority queue
      • 用无序的list来实现
      • 用有序的列表来实现优先队列
    • 9.3 heaps
      • heap数据结构
      • 使用堆来实现优先队列
      • 基于数组实现的完全二叉树
      • 使用最小优先队列来进行排序
    • adaptable priority queue
      • Locators

Priority Queues

9.1 ADT

  • P.add(k, v)
  • P.min(): return (k, v)
  • P.remove_min(): return (k,v)
  • P.is_empty()
  • len(P)

优先队列可能有几个元素有相同的key值并且都是最小,这时最小的值是随机选择的一个。

9.2 implementing a priority queue

class PriorityQueueBase:
    """abstract base class for a priority queue."""

    class _Item:
        """lightweight composite to store priority queue items."""
        __slots__ = ‘_key‘, ‘_value‘

        def __init__(self, k, v):
            self._key = k
            self._value = v

        def __lt__(self, other):
            return self._key < other._key

    def is_empty(self):
        return len(self) == 0

在优先队列的抽象基类中,定义一个简单的_Item类来实现(k, v)对的存储

用无序的list来实现

对于优先队列里面列表的存储,一种方法是选择前面的PositionalList,这种方法由于队列里面的元素不需要 保持有序,添加一个元素只需要O(1)的时间就可以完成,但是对于min, remove_min操作就需要遍历一次整个list, 找到最小的元素的定位,所以需要O(n)的时间。

class UnsortedPriorityQueue(PriorityQueueBase):
    def _find_min(self):
        """return Position of item with min key"""
        if self.is_empty():
            raise Empty(‘priority queue is empty‘)
        small = self._data.first()
        walk = self._data.after(small)
        while walf is not None:
            if walf.element() < small.element():
                small = walk
            walk = self._data.after(walk)
        return small

    def __init__(self):
        self._data = PositionalList()

    def __len__(self):
        return len(self._data)

    def add(self, key, value):
        self._data.add_last(self._Item(key, value))

    def min(self):
        p = self._find_min()
        item = p.element()
        return (item._key, item._value)

    def remove_min(self):
        """remove and return (k, v) tuple with min key."""
        p = self._find_min()
        item = self._data.delete(p)
        return (item._key, item._value)
用有序的列表来实现优先队列

使用有序的列表来实现,队列中第一个元素就是最小的元素。

class SortedPriorityQueue(PriorityQueueBase):
    """ a min priority queue implemented with a sorted list"""

    def __init__(self):
        self._data = PositionalList()

    def __len__(self):
        return len(self._data)

    def add(self, key, value):
        """ add a key-value pair"""
        newest = self._Item(key, value)
        walk = self._data.last()
        while walk is not None and newest < walk.element():
            walk = self._data.before(walk)
        if walk is None:  # new key is smallest
            self.= _data.add_first(newest)
        else:
            self._data.add_after(walk, newset)

    def min(self):
        """ return (k, v) tuple with min key"""
        if self.is_empty():
            raise Empty(‘priority queue is empty‘)
        p = self._data.first()
        item = p.element()
        return (item._key, item._value)

    def remove_min(self):
        """ remove and return (k, v) tuple with min key"""
        if self.is_empty():
            raise Empty(‘priority queue is empty‘)
        item = self._data.delete(self._data.first())
        return (item._key, item._value)

9.3 heaps

前面提到的使用有序和无序队列实现的优先队列各有各的优点。使用无序队列的在插入的时候可以达到 O(1)的时间,但是查找最小的元素就要O(n)的时间。有序队列实现的插入时要O(n)的时间,但是 查找最小的元素只需要O(1)的时间。

现在介绍一种新的数据结构,二叉堆,可以在对数的时间复杂的实现插入和查找的操作。

heap数据结构

二叉堆中的元素满足下面的性质:

  • 对于每个不是根的位置p,该位置存储的key大于等于它的父结点上存储的key,这样从根结点开始向下的路径中

的所有key都是不减的,所以根结点的key就是最小的。

  • 完全二叉树性质:一个高度为h的堆T如果在层数0到h-1都是满的,而在第h层可以不满,但是结点从左向右

紧密排列,及左边没有空隙, 那就可以说它是完全的。

  • 一个完全的二叉堆,当里面有n个元素时,它的高度h不超过logn
使用堆来实现优先队列

因为堆的高度不会超过n的对数,如果我们实现更新的操作这个高度,那么就可以实现不超过对数时间的复杂度。

对于使用堆的实现,我们需要关心的是插入一个新的元素该如何操作。为了保持树的完全性,这个新的结点 应该在最下一层的最后一个结点的右边,如果这层刚好满了则在新一层的第一个结点。这样做仅仅满足树的 完全性,但是还要考虑一个结点的key值不小于它的父结点的key的性质。 假设新结点为p,他的父结点为q,如果kp < kq,则需要将这两个结点做一个交换。交换之后p结点仍然可能 小于它新的父结点,则继续检查判断是否要交换,直到p结点被推到根结点或者已经满足性质为止。 因为插入操作最坏的情况是将新结点一直推到根结点的位置,所以时间复杂度是O(logn)

现在到remove_min的实现。为了保证树的完全性,可以先将根结点与最下一层的最右边的结点进行交换,然后删除 最右下的那个结点。这样做有可能破坏子结点key不小于父结点的性质。维护的方法与插入类似,从根结点开始, 与它的子结点的key进行比较,注意它可能有2个子结点,所以需要与其中key较小那个进行比较。如果根的key值大于 子结点的key值,则进行交换,然后继续判断是否需要进一步交换。最坏情况也是O(logn)的复杂度。

基于数组实现的完全二叉树

f(p)是位置p在数组中存储的下标

  • 如果p是根,则f(p) = 0
  • 如果p是位置q的左孩子,则f(p) = 2f(q) + 1
  • 如果p是位置q的右孩子,则f(p) = 2f(q) + 2

使用数组实现有一个好处,就是在找最下层最右边结点的时候非常方便,就是下标n - 1存储的那个位置。

class HeapPriorityQueue(PriorityQueueBase):
    """a min-oriented priority queue implemented with a binary heap."""
    def _parent(self, j):
        return (j - 1) // 2

    def _left(self, j):
        return 2 * j + 1

    def _right(self, j):
        return 2 * j + 2

    def _has_left(self, j):
        return self._left(j) < len(self._data)

    def _has_right(self, j):
        return self._right(j) < len(self._data)

    def _swap(self, i, j):
        """swap the elements at indices i and j of array"""
        self._data[i], self._data[j] = self._data[j], self._data[i]

    def _upheap(self, j):
        parent = self._parent(j)
        if j > 0 and self._data[j] < self._data[parent]:
            self._swap(j, parent)
            self._upheap(parent)  # recur at position of parent

    def _downheap(self, j):
        if self._has_left(j):
            left = self._left(j)
            small_child = left
            if self._has_right(j):
                right = self._right(j)
                if self._data[right] < self._data[left]:
                    small_child = right
            if self._data[small_child] < self._data[j]:
                self._swap(j, small_child)
                self._downheap(small_child)

# ---------------- public methods --------------------
    def __init__(self):
        self._data = []

    def __len__(self):
        return len(self._data)

    def add(self, key, value):
        self._data.append(self._Item(key, value))
        self._upheap(len(self._data) - 1)

    def min(self):
        if self.is_empty():
            raise Empty(‘priority queue is empty‘)
        item = self._data[0]
        return (item._key, item._value)

    def remove_min(self):
        if self.is_empty():
            raise Empty(‘priority queue is empty‘)
        self._swap(0, len(self._data) - 1)
        item = self._data.pop()
        self._downheap(0)
        return (item._key, item._value)
使用最小优先队列来进行排序

可以先构造一个空的优先队列,然后将要排序的元素一个个插入到队列中,然后通过remove_min不断 从队列中取出元素就可以得到有序的队列。

adaptable priority queue

优先队列现在可以实现移除最小的一个元素,但是实际情况中可能还有一些问题需要处理,如一些在排队的 顾客中,中间的某个人突然说不想继续排了要离开,这时需要能将任意一个元素移除。还有比如排队的某个 客人本来优先级在后面,但是突然拿出一张vip卡,那么他的优先级突然变得很高,需要能将他的key修改为 新的值。

为了支持这些新的操作,我们需要定义一个新的ADT adaptable priority queue.

Locators

要修改队列中的任意一个元素,我们要能定位到里面的任意一个元素。现在增加定位器Locators, 在添加一个新元素到队列中的时候,返回一个locator给上级函数。

  • P.update(loc, k, v): 将loc指定位置的key, value更新。
  • P.remove(loc): 删除指定的元素,并返回(key, value)
class AdaptableHeapPriorityQueue(HeapPriorityQueue):
    """ a locator-based priority queue implemented with a binary heap."""
    class Locator(HeapPriorityQueue._Item):
        __slots__ = ‘_index‘

        def __init__(self, k, v, j):
            super().__init__(k, v)
            self._index = j
    # -------------- nonpublic methods -----------------
    # override swap to record new indices
    def _swap(self, i, j):
        super()._swap(i, j)
        self._data[i]._index = i
        self._data[j]._index = j

    def _bubble(self, j):
        """将某个元素上移或者下移到适当的位置"""
        if j > 0 and self._data[j] < self._data[self._parent(j)]:
            self._upheap(j)
        else:
            self._downheap(j)

    def add(self, key, value):
        """ add a key-value pair."""
        token = self.Locator(key, value, len(self._data))
        self._data.append(token)
        self._upheap(len(self._data) - 1)
        return token

    def update(self, loc, newkey, newval):
        j = loc._index
        if not (0 <= j < len(self) and self._data[j] is loc):
            raise ValueError(‘invalid locator‘)
        loc._key = newkey
        loc._value = newval
        self._bubble(j)

    def remove(self, loc):
        """ remove and return (key, value)"""
        j = loc._index
        if not (0 <= j < len(self) and self._data[j] is loc):
            raise ValueError(‘invalid locator‘)
        if j == len(self) - 1:  # remove the last position
            self._data.pop()
        else:
            self._swap(j, len(self) - 1)
            self._data.pop()
            self._bubble(j)
        return (loc._key, loc._value)
时间: 2024-12-18 04:01:02

python数据结构学习笔记(九)的相关文章

python数据结构学习笔记(七)

7 Linked Lists 7.1 singly linked list 7.2 circular linked lists 7.4 the positional list ADT 7.5 sorting a positional list 7.6 链表与数组实现的序列比较 7 Linked Lists 7.1 singly linked list 单向链表,就是一系列的结点组成一个线性集合.里面每个结点存储有一个元素的引用,还会指向链表的下一个元素. 最后一个元素指向None. 链表的第一个

python数据结构学习笔记(五)

5 Array-Based Sequence 5.2.1 referential arrays 5.2.2 compact arrays in python array.array 5.3 dynamic arrays and amortization 5.3.1 implementing a dynamic array 5.4 efficiency of python's sequence types constant-time operation 字符串拼接 5.6 multidimensi

python学习笔记九——文件与目录

1.python进行文件读写的函数是open或file类 mode:r  只读 r+   读写 w  写入,先删除原文件,再重新写入,如果文件没有则创建 w+  读写,先删除原文件,再重新写入,如果文件没有则创建(可写入和输出) a  写入,在文件末尾追加新的内容,文件不存在则创建 a+  读写,在文件末尾追加新的内容,文件不存在则创建 b  打开二进制文件,可与r,w,a,+结合使用 U  支持所有的换行符号,"\r","\n","\r\n"

2. 蛤蟆Python脚本学习笔记二基本命令畅玩

2. 蛤蟆Python脚本学习笔记二基本命令畅玩 本篇名言:"成功源于发现细节,没有细节就没有机遇,留心细节意味着创造机遇.一件司空见惯的小事或许就可能是打开机遇宝库的钥匙!" 下班回家,咱先来看下一些常用的基本命令. 欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/48092873 1.  数字和表达式 看下图1一就能说明很多问题: 加法,整除,浮点除,取模,幂乘方等.是不是很直接也很粗暴. 关于上限,蛤蟆不太清楚

小猪的数据结构学习笔记(四)

小猪的数据结构学习笔记(四) 线性表之静态链表 --转载请注明出处:coder-pig 本章引言: 在二,三中中我们分别学习了顺序表中的线性表与单链表,线性表有点类似于 我们前面所学的数组,而单链表使用的最多的是指针,这里问个简单的问题, 如果是在以前没有指针的话,前辈先人们怎么实现单链表呢?大家思考下! 没有指针,那么用什么来代替呢?前辈先人们非常机智,想出了使用下标+游标的方式 来实现单链表的效果!也就是今天要讲的--静态链表! 当然你也可以直接跳过本章,因为有了单链表就没有必要用静态链表了

小猪的数据结构学习笔记(二)

小猪的数据结构学习笔记(二) 线性表中的顺序表 本节引言: 在上个章节中,我们对数据结构与算法的相关概念进行了了解,知道数据结构的 逻辑结构与物理结构的区别,算法的特性以及设计要求;还学了如何去衡量一个算法 的好坏,以及时间复杂度的计算!在本节中我们将接触第一个数据结构--线性表; 而线性表有两种表现形式,分别是顺序表和链表;学好这一章很重要,是学习后面的基石; 这一节我们会重点学习下顺序表,在这里给大家一个忠告,学编程切忌眼高手低,看懂不代表自己 写得出来,给出的实现代码,自己要理解思路,自己

Python Click 学习笔记(转)

原文链接:Python Click 学习笔记 Click 是 Flask 的团队 pallets 开发的优秀开源项目,它为命令行工具的开发封装了大量方法,使开发者只需要专注于功能实现.恰好我最近在开发的一个小工具需要在命令行环境下操作,就写个学习笔记. 国际惯例,先来一段 "Hello World" 程序(假定已经安装了 Click 包). # hello.py import click @click.command() @click.option('--count', default

APUE 学习笔记(九) 高级I/O

1. 非阻塞I/O 低速系统调用时可能会使进程永远阻塞的一类系统调用,包括以下调用: (1)某些文件类型你(网络socket套接字.终端设备.管道)暂无可使用数据,则读操作可能会使调用者永远阻塞 (2)如果数据不能立即被(1)中文件类型接受,则写操作会使调用者永远阻塞 (3)某些进程间通信函数 非阻塞I/O使我们可以调用open.read.write这样的I/O操作,并使这些操作不会永远阻塞,如果这种操作不能完成,则调用立即出错返回 对于一个给定的文件有两种方法对其指定非阻塞I/O: (1)调用

[简明python教程]学习笔记之编写简单备份脚本

[[email protected] 0503]# cat backup_ver3.py #!/usr/bin/python #filename:backup_ver3.py import os import time #source source=['/root/a.sh','/root/b.sh','/root/c.sh'] #source='/root/c.sh' #backup dir target_dir='/tmp/' today=target_dir+time.strftime('