python3优先队列解决合并K个排序链表所涉及的知识点

这里主要记录下做题过程中遇到的新知识点:

dataclasses模块中的dataclass、field,以及queue中的PriorityQueue

PriorityQueue的参考见链接:

https://docs.python.org/3/library/queue.html

先讲一下python中优先队列的实现原理:优先队列实际上是使用堆结构实现的,能够将搜索的时间复杂度从 O ( k ) O(k) O(k)降到 O ( l o g k ) O(logk) O(logk)。看一下官方文档中对优先队列的描述:

The lowest valued entries are retrieved first。

可以发现,这里的优先队列实际上就是小根堆。大根堆也能用优先队列实现(加个负号就搞定了)。当然我们知道python中有一个高级数据结构是heapq,是专门用来处理堆结构的。个人认为有些情况下可以用优先队列来实现heapq的功能,这样记忆负担小一些。

在介绍PriorityQueue时候,有这样一句话:

A typical pattern for entries is a tuple in the form:(priority_number, data). If the data elements are not comparable, the data can be wrapped in a class that ignores the data item and only compares the priority number:

然后给了一段示例代码:

from dataclasses import dataclass, field
from typing import Any

@dataclass(order=True)
class PrioritizedItem:
    priority: int
    item: Any=field(compare=False)

好了,这段简单的示例代码中涉及到了如下的知识点:

  1. dataclass
  2. field
  3. Any
  4. 类装饰器

现在我们依次解决这些知识点。

首先dataclass、field都是从dataclasses中导入的,关于dataclasses的介绍参考链接:

https://docs.python.org/zh-cn/3/library/dataclasses.html

简单来说,这个模块提供了一个装饰器和一些函数,用于自动添加生成的special methods,比如

__init__()和__repr__()

到用户定义的类。这些方法是自动添加的,不会像我们自己定义类的时候显示添加。

3.7之后的功能:

提供了模块级的装饰器、类和函数

首先看dataclass的定义:

dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)

这个函数是decorator,用于将生成的special method添加到类中。

dataclass()装饰器检查类以找到field,所谓的field定义为具有类型标注的类变量。(后面具体例子在详细说明)

所有生成的方法中的字段顺序是它们在类定义中出现的顺序。(同样后面详细说明)

这里主要解释下几个关键参数的意义:

repr: 生成的repr字符串将具有类名以及每个字段的名称和repr,按照它们在类中定义的顺序。不包括标记为从repr中排除的字段。(和field中的repr参数配合使用会很有趣,具体后面例子说明)

eq:此方法将类作为其字段的元组按顺序比较。(刚开始看的时候很懵,举个例子就发现很好理解了,具体后面详细说明)

接下来看看field的定义:

field(*, default=MISSING, default_factory=MISSING, repr=True, hash=None, init=True, compare=True, metadata=None)

有些数据类功能需要额外的每字段信息。为了满足这种对附加信息的需求,你可以通过调用提供的field()函数来替换默认字段值。(直接看这个会有点懵,同样后面举例详细说明)

记录下field的几个关键参数:

repr:该字段包含在生成的repr()方法返回的字符串中,也就是说这里设置成False,最终显示的字符串中没有这个字段的名称和值。

compare:为True,则该字段包含在生成的相等性和比较方法中。

讲到这,先举几个例子把上面的概念熟悉下。(不过还得先理解类装饰器的概念,参考链接:https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p12_using_decorators_to_patch_class_definitions.html,这里先不展开)

from dataclasses import dataclass, field
from queue import PriorityQueue

@dataclass(order=True)
class Item:
    num1: int#field,这就是field;类型标注:num1:int,int就是对类变量num1的类型标注
    num2: int
    str_: str

item1 = Item(5, 3, 'c')#自动调用__init__(),即self.num1=5;self.num2=3;...
item2 = Item(3, 4, 'b')
q = PriorityQueue()
q.put()
q.put()

首先看一看repr的含义:

>>>item1
Item(num1=5, num2=3, str_='c')#顺序是在类中添加的顺序,默认值都是要添加到repr字符串中的

修改一下item的定义:

@dataclass(order=True)
class Item:
    num1: int
    num2: int=field(repr=False)
    str_: str
item1 = Item(5,3,'c')

看看item1会变成什么?

>>>item1
Item(num1=5, str_='c')

好,到这理解了repr参数的含义了。

接下来理解compare参数的含义;不过在理解这个参数含义之前,需要对优先队列理解下。

from dataclasses import dataclass, field
from queue import PriorityQueue

@dataclass(order=True)
class Item:
    num1: int
    num2: int
    str_: str

item1 = Item(5, 3, 'c')
item2 = Item(3, 4, 'b')
q = PriorityQueue()
q.put()
q.put()

优先队列有一个方法叫get(),即取出优先队列队首的entry,即最小entry。

前面介绍优先队列有这样一句话:

A typical pattern for entries is a tuple in the form:(priority_number, data).以及将类作为其字段的元组按顺序比较。

这个例子中的entry变成了(num1, num2, str_),首先按num1进行比较,num1相同的情况按num2进行比较,同理按str_进行比较。

看代码

>>>q,get()
Item(num1=3, num2=4, str_='b')

很好理解

接下来我们进行一些改动,

from dataclasses import dataclass, field
from queue import PriorityQueue

@dataclass(order=True)
class Item:
    num1: int
    num2: int=field(compare=False)#即这个类字段不参与比较
    str_: str

item1 = Item(3, 5, 'a')
item2 = Item(3, 4, 'b')
q = PriorityQueue()
q.put(item1)
q.put(item2)

我们先想一想,q.get()会是什么结果?简单来说item1和item2谁小?

由于item1和item2第一个元素相同,我们需要按顺序往后面比较,但是发现num2字段的compare为False,即不参与比较,于是我们比较第三个字段str_,发现item1小,但是如果num2字段的compare为True的话应该是item2小。

看看结果:

>>>q.get()
Item(num1=3, num2=5, str_='a')

OK,到这我们把dataclass、field的含义理解得差不多了。

简单记录下Any的含义:Any 可以是任何类型的数据;在python中,任意的对象都是Object的子类,但是反过来不成立;任何对象都是Any的subclass,同样,Any也是任何对象的subclass。

参考链接:https://docs.python.org/3/library/typing.html

接下来记录下leetcode上的一道题目,合并K个有序链表:

参考链接:https://leetcode-cn.com/problems/merge-k-sorted-lists/

官方视频解答中python3写的优先队列是有问题的。这里给出自己的正确答案:

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
from dataclasses import dataclass, field
from typing import Any
from queue import PriorityQueue
@dataclass(order=True)
class PriorityItem:
    priority_number: int
    item: Any=field(compare=False)

class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        head = p = ListNode(-1)
        q = PriorityQueue()
        for node in lists:
            if node:
                q.put(PriorityItem(node.val, node))
        while q.qsize()>=2:#不能用while q来判断,这样会显示死循环,而且即使q里面没有东西,也不是返回False
            temp = q.get()
            val, node = temp.priority_number, temp.item
            p.next = node
            p = p.next
            node = node.next
            if node:
                q.put(PriorityItem(node.val, node))
        if not q.empty():
            p.next = q.get().item
        return head.next

时间复杂度: O ( K n l o g K ) O(KnlogK) O(KnlogK)

空间复杂度: O ( K ) O(K) O(K)

官方给的解答:

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
from queue import PriorityQueue
class Solution:
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        head = p = ListNode(-1)
        q = PriorityQueue()
        for l in lists:
            if l:
                q.put((l.val, l))
        while not q.empty():
            val, node = q.get()
            listnode = ListNode(val)
            p.next = listnode
            p = p.next
            node = node.next
            if node:
                q.put((node.val, node))
        return head.next

会报错,报错用例:

[[1,4,5],[1,3,4],[2,6]]

报错原因:

TypeError: '<' not supported between instances of 'ListNode' and 'ListNode'
    heappush(self.queue, item)
Line 233 in _put (/usr/lib/python3.8/queue.py)
    self._put(item)
Line 149 in put (/usr/lib/python3.8/queue.py)
    q.put((l.val, l))
Line 13 in mergeKLists (Solution.py)
    ret = Solution().mergeKLists(param_1)
Line 48 in _driver (Solution.py)
    _driver()
Line 59 in <module> (Solution.py)

对比下官方的代码和我的代码,就能知道问题在哪了。

你可能感兴趣的:(学习记录,数据结构,python,算法,leetcode)