第九章 优先级队的基本知识及Python实现(from Goodrich)

Date: 2019-07-11

在回顾优先级队列的知识之前,先总结一下自己对于数据结构和抽象数据类型ADT的理解,之前一直不太懂。

一、数据结构

1、定义:数据结构指的是数据之间的相互关系,即数据的组织形式.(数据的逻辑结构(线性/非线性)+数据的存储结构(顺序存储+链接存储+索引存储+关键字存储)+数据的运算(检索、插入、删除、更新、排序等运算))

2、4种基本数据结构:集合、线性结构、树形结构、图状结构

(1)集合结构:除了同属于一种类型外,别无其它关系

(2)线性结构:元素之间存在一对一关系常见类型,如: 数组、链表、队列(先进先出)、栈(先进后出) ->(统称线性表)

(3)树形结构:元素之间存在一对多关系,如:树

(4)图形结构:元素之间存在多对多关系,如:图

 

二、ADT(Abstract Data Type):抽象数据类型

1、定义:一个数学模型以及定义在该模型上的一组操作(ADT是指抽象数据的组织和与之相关的操作。可以看作是数据的逻辑结构及其在逻辑结构上定义的操作。)

2、表示法:三元组表示(D,S,P),其中D是数据对象,S是D上的关系集,P是对D的基本操作集

3、举例说明(以线性表为例):

若以(D,S,P)表示线性表,D、S、P分别表示:

   D:具有相同特性的一组数据元素的集合

          S:(数据元素之间的关系)

                a.集合中必存在唯一的一个“第一元素”

        b.集合中必存在唯一的一个 “最后元素”

               c.除最后一个元素之外,均有唯一的后继(后件)

               d.除第一个元素之外,均有唯一的前驱(前件)

        P:(对数据元素的一组操作)

               a.插入一个元素

               b.删除一个元素

            c.清除所有的元素

      d.查找并返回元素

目录:

9.1 优先级队列的抽象数据类型(9.1.1 优先级 9.1.2 优先级队列的抽象数据类型的实现 )

9.2 优先级队列的实现(9.2.1 组合设计模式 9.2.2 使用未排序列表实现优先级 9.2.3 使用排序列表实现优先级队列)

9.3 堆(9.3.1 堆的数据结构 9.3.2 使用堆实现优先级队列 9.3.3 基于数组的完全二叉树表示 9.3.4 python实现 9.3.5 基于堆的优先级队列分析  9.3.7 python的headpq模块)

9.4 使用优先级队列排序(9.4.1 选择排序和插入排序 9.4.2 堆排序)

9.5 适应性优先队列(定位器 适应性优先队列的实现)

9.1 优先级队列的抽象数据类型

在前面,我们知道了队列ADT是一个根据先进先出策略在队列中添加和移除数据的对象集合。在实际生活有很多使用队列的结构来管理需要顺序处理的对象,例如:呼叫按照呼叫中心接受的顺序来应答。但有时候仅仅根据先进先出的策略是不够的,有的时候需要对队列中的元素评定优先级。则就需要一个新的抽象数据类型:优先级队列---这是一个包含优先级元素的集合,该集合允许插入任意的元素,并允许删除拥有最高优先级的元素。当一个元素被插入优先级队列中时,用户可以通过提供一个关联键来为该元素赋予一定的优先级。键值最小的元素将会是下一个熊队列中移除的元素。

9.1.2 优先级队列的抽象数据类型的实现

我们形式化地将一个元素和它的优先级用一个key-value对进行建模。我们在优先级队列p上定义优先级队列ADT,以支持如下的方法:

p.add(k,v)向优先级队列p中插入一个拥有键k和值v的元组
p.min()返回一个优先级队列p中拥有最小键值的元组(k,v)(但是没有移除该元组);如果为空,则发生错误
p.remove_min()从优先级队列p中移除一个拥有最小键值的元组,并返回这个被移除的元组,(k,v)代表这个被移除的元组的键和值;如果为空,则发生错误
p.is_empty()如果优先级队列为空,则返回True
len(p)返回优先级队列中元组的数量

一个优先级队列中可能包含多个键值相等的条目,在这种情况下min和remove_min()方法可能从具有最小键值的元组中任选一个返回,值可以是任何对象的类型。

在优先级队列的模型中,一个元素一旦被加入优先级队列,他的键值将保持不变。

9.2 优先级队列的实现

9.2.1 组合设计模式

定义了一个_Item类,这个类保证了每个元素与关联计数保持配对。

class Empty(Exception):
    pass

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

  #------------------------------ nested _Item class ------------------------------
  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    # compare items based on their keys

    def __repr__(self):
      return '({0},{1})'.format(self._key, self._value)

  #------------------------------ public behaviors ------------------------------
  def is_empty(self):                  # concrete method assuming abstract len
    """Return True if the priority queue is empty."""
    return len(self) == 0

  def __len__(self):
    """Return the number of items in the priority queue."""
    raise NotImplementedError('must be implemented by subclass')

  def add(self, key, value):
    """Add a key-value pair."""
    raise NotImplementedError('must be implemented by subclass')

  def min(self):
    """Return but do not remove (k,v) tuple with minimum key.

    Raise Empty exception if empty.
    """
    raise NotImplementedError('must be implemented by subclass')

  def remove_min(self):
    """Remove and return (k,v) tuple with minimum key.

    Raise Empty exception if empty.
    """
    raise NotImplementedError('must be implemented by subclass')
# 后面的四个函数的具体实现需要根据子类的情况具体实现。

9.2.2 使用未排序列表实现优先级队列

# 继承之前定义的一些类,如果需要实现这个,需要前往之前的博客查看父类是什么
from .priority_queue_base import PriorityQueueBase
from ..ch07.positional_list import PositionalList
from ..exceptions import Empty

class UnsortedPriorityQueue(PriorityQueueBase): # base class defines _Item
  """A min-oriented priority queue implemented with an unsorted list."""

  #----------------------------- nonpublic behavior -----------------------------
  def _find_min(self):
    """Return Position of item with minimum key."""
    if self.is_empty():               # is_empty inherited from base class
      raise Empty('Priority queue is empty')
    small = self._data.first()
    walk = self._data.after(small)
    while walk is not None:
      if walk.element() < small.element():
        small = walk    # 入当前walk小于small,则small=walk.然后再循环下一个walk
      walk = self._data.after(walk)
    return small

  #------------------------------ public behaviors ------------------------------
  def __init__(self):
    """Create a new empty Priority Queue."""
    self._data = PositionalList()

  def __len__(self):
    """Return the number of items in the priority queue."""
    return len(self._data)

  def add(self, key, value):
    """Add a key-value pair."""
    self._data.add_last(self._Item(key, value))

  def min(self):
    """Return but do not remove (k,v) tuple with minimum key.

    Raise Empty exception if empty.
    """
    p = self._find_min()
    item = p.element()
    return (item._key, item._value)

  def remove_min(self):
    """Remove and return (k,v) tuple with minimum key.

    Raise Empty exception if empty.
    """
    p = self._find_min()
    item = self._data.delete(p)
    return (item._key, item._value)

9.2.3 使用排序列表实现优先队列

from .priority_queue_base import PriorityQueueBase
from ..ch07.positional_list import PositionalList
from ..exceptions import Empty

class SortedPriorityQueue(PriorityQueueBase): # base class defines _Item
  """A min-oriented priority queue implemented with a sorted list."""

  #------------------------------ public behaviors ------------------------------
  def __init__(self):
    """Create a new empty Priority Queue."""
    self._data = PositionalList()

  def __len__(self):
    """Return the number of items in the priority queue."""
    return len(self._data)

  def add(self, key, value):
    """Add a key-value pair."""
    newest = self._Item(key, value)             # make new item instance
    walk = self._data.last()       # walk backward looking for smaller key
    while walk is not None and newest < walk.element():
      walk = self._data.before(walk)
    if walk is None:
      self._data.add_first(newest)              # new key is smallest
    else:
      self._data.add_after(walk, newest)        # newest goes after walk

  def min(self):
    """Return but do not remove (k,v) tuple with minimum key.

    Raise Empty exception if empty.
    """
    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 minimum key.

    Raise Empty exception if empty.
    """
    if self.is_empty():
      raise Empty('Priority queue is empty.')
    item = self._data.delete(self._data.first())
    return (item._key, item._value)

接下来比较两种基于列表的优先队列的实现的运行时间:

操作未排序列表排序列表
lenO(1)O(1)
is_emptyO(1)O(1)
addO(1)O(n)  添加元素,需要寻找属于它的特定的位置
minO(n)寻求最小值,需要遍历到最后去确定最小值O(1)
remove_minO(n)寻求最小值,需要遍历到最后去确定最小值O(1)

9.3 堆

本节将使用一个称为二进制堆的数据结构来给出一个更加有效的优先级队列的实现。这个数据结构允许我们以对数时间复杂度来实现插入和删除的操作。

堆是一个完全二叉树,具有Heap-order属性:

在堆T中,对于除了根节点的每个位置p,存储在p中的键值大于或等于存储在p的父节点的键值。(自上而下按照非递减的顺序排列)

命题:堆T有n个元组,则它的高度h = \left \lfloor logn \right \rfloor

9.3.2 使用堆实现优先级队列

上面的命题告诉我们,如果能以与堆的高度成比例的时间执行更新操作,那么这些操作将在对数级的时间内完成。

len和is_empty和基类一样,min方法直接返回树的根部(其是最小的元素)。但add和remove_min两个的实现方法很有趣:

1.插入 add方法:

1)首先在堆中增加一个元素,放在树的最后一层的最右端。

2)插入这个元素之后,将其进行堆向上冒泡:将该元素不断与相应的父节点的优先级进行比较,如果父节点优先级key值大于该节点的优先级,则将该节点和父节点进行swap交换,不断向上冒泡直到结束。

交换既解决了破坏heap-order属性的问题,又将元组在堆中向上移动了一层。最坏情况向上交换到树的根节点结束,交换了输的高度次h = \left \lfloor logn \right \rfloor

2. 删除键值最小的元素

1) 首先在堆中移除键值最小的根部元素,然后将最底部的最右边的元素用作根节点的替换值。

2) 交换最底右的元素之后,将该元素不断进行堆向下冒泡:不断将该元素与其两个孩子中较大的那个孩子进行比较,如果该元素大于较大孩子的优先级,则交换两者swap,不断进行直到不再违反heap-order性质;如果该元素均小于两个孩子的优先级key,则不进行交换,停止继续向下冒泡。

9.3.3 基于数组的完全二叉树表示

基于数组的完全二叉树的表示是需要对树进行编号的:

1)如果p是T的根节点,则f(p)=0

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

3)如果p是位置q的右孩子,则f(p)=2f(q)+2

用基于数组表示的堆来实现优先级队列使我们避免了基于节点树结构的一些复杂性。尤其是优先队列的add方法和remove_min操作都依靠定位大小为n的堆的最后一个索引位置。但是,如果事先不知道优先级队列的大小,基于数组的堆偶尔会动态重新设置数组的大小。

 

9.3.4 python的堆实现

基于数组的堆实现优先级队列,基于优先级队列基类

from .priority_queue_base import PriorityQueueBase
from ..exceptions import Empty

class HeapPriorityQueue(PriorityQueueBase): # base class defines _Item
  """A min-oriented priority queue implemented with a binary heap."""

  #------------------------------ nonpublic behaviors ------------------------------
  def _parent(self, j):
    return (j-1) // 2

  def _left(self, j):
    return 2*j + 1    # j的左孩子
    
  def _right(self, j):
    return 2*j + 2   # j的右孩子

  def _has_left(self, j):
    return self._left(j) < len(self._data)     # index beyond end of list?
  
  def _has_right(self, j):
    return self._right(j) < len(self._data)    # index beyond end of list?
  
  def _swap(self, i, j):  # 交换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):  # 堆向上冒泡(add方法中使用)
    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):   # 堆向下冒泡(remove_min方法中使用)
    if self._has_left(j):
      left = self._left(j)
      small_child = left               # although right may be smaller
      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)    # recur at position of small child

  #------------------------------ public behaviors ------------------------------
  def __init__(self):
    """Create a new empty Priority Queue."""
    self._data = []

  def __len__(self):    # 1 计算长度
    """Return the number of items in the priority queue."""
    return len(self._data)

  def add(self, key, value):   # 3 增加元素
    """Add a key-value pair to the priority queue."""
    self._data.append(self._Item(key, value))
    self._upheap(len(self._data) - 1)            # upheap newly added position
  
  def min(self):         # 4 返回最小值,最'强'优先级的元素(根部元素)
    """Return but do not remove (k,v) tuple with minimum key.

    Raise Empty exception if empty.
    """
    if self.is_empty():
      raise Empty('Priority queue is empty.')
    item = self._data[0]
    return (item._key, item._value)

  def remove_min(self):   # 5 删除最小值,根部节点元素
    """Remove and return (k,v) tuple with minimum key.

    Raise Empty exception if empty.
    """
    if self.is_empty():
      raise Empty('Priority queue is empty.')
    self._swap(0, len(self._data) - 1)           # put minimum item at the end
    item = self._data.pop()                      # and remove it from the list;
    self._downheap(0)                            # then fix new root
    return (item._key, item._value)

9.3.5 基于堆的优先队列的分析

操作运行时间
len(p), p.is_empty()O(1)
p.min()O(1)
p.add()O(logn)(摊销时间)
p.remove_min()O(logn)(摊销时间)

结论:无论堆使用链表结构还是数组结构实现,堆数据结构都是优先队列ADT非常有效的实现方式。

9.3.7 python的heapq模块

python的标准发行版中包含一个heapq模块,该模块提供基于堆的优先队列的支持。该模块不提供任何优先级队列类,而是提供一些函数,这些函数把标准python列表作为堆进行管理。heapq模块支持以下函数:

1. heappush(L,e)

2.heappop(L)

3.heappushpop(L,e)

4.heapreplace(L,e)

5.heapify(L)

6.nlargest(k,iterable)

 

9.4使用优先级队列排序

# 函数pq_sort的实现,这里假设已经有一个合适的priorityQueue类的实现,这里输入列表C的每个元素都充当了其在优先队列P的键
def pq_sort(C):
    n = len(C)
    P = PriorityQueue()
    for j in range(n):
        element = C.delelte(C.first())
        P.add(element,element)
    for j in range(n):
        (k,v) = P.remove_min()
        C.add_last(v)

选择排序

插入排序

堆排序

 9.5 适应性优先队列

不太理解这部分的内容,还是贴上代码:
一个可适应性优先队列的实现:


from .heap_priority_queue import HeapPriorityQueue

class AdaptableHeapPriorityQueue(HeapPriorityQueue):
  """A locator-based priority queue implemented with a binary heap."""

  #------------------------------ nested Locator class ------------------------------
  class Locator(HeapPriorityQueue._Item):
    """Token for locating an entry of the priority queue."""
    __slots__ = '_index'                 # add index as additional field

    def __init__(self, k, v, j):
      super().__init__(k,v)
      self._index = j

  #------------------------------ nonpublic behaviors ------------------------------
  # override swap to record new indices
  def _swap(self, i, j):
    super()._swap(i,j)                   # perform the swap
    self._data[i]._index = i             # reset locator index (post-swap)
    self._data[j]._index = j             # reset locator index (post-swap)

  def _bubble(self, j):
    if j > 0 and self._data[j] < self._data[self._parent(j)]:
      self._upheap(j)
    else:
      self._downheap(j)

  #------------------------------ public behaviors ------------------------------
  def add(self, key, value):
    """Add a key-value pair."""
    token = self.Locator(key, value, len(self._data)) # initiaize locator index
    self._data.append(token)
    self._upheap(len(self._data) - 1)
    return token

  def update(self, loc, newkey, newval):
    """Update the key and value for the entry identified by Locator loc."""
    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 the (k,v) pair identified by Locator loc."""
    j = loc._index
    if not (0 <= j < len(self) and self._data[j] is loc):
      raise ValueError('Invalid locator')
    if j == len(self) - 1:                # item at last position
      self._data.pop()                    # just remove it
    else:
      self._swap(j, len(self)-1)          # swap item to the last position
      self._data.pop()                    # remove it from the list
      self._bubble(j)                     # fix item displaced by the swap
    return (loc._key, loc._value)             

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值