Python 列表

本文详细介绍了Python列表的分类和接口,包括基于索引、内容和位置的操作。讲解了列表的AbstractList类、基于数组和链表的实现,以及如何实现列表迭代器。列表迭代器支持更强大的导航和修改功能,提供了first、last、next、previous等方法。文章还分析了不同实现的时间和空间复杂度,并探讨了列表迭代器的先验条件和实例化过程。

简要介绍

列表是三种主要的线性集合中的一种(另两种是栈和队列)。列表所支持的操作,比栈和队列还要广泛,因此其应用也更广,并且也更难实现。尽管Python包含了一个内建的列表操作,但是还有几种可能的实现。为了让列表的大量操作更有意义,我们可以将其分为3类:基于索引的操作、基于内容的操作、基于位置的操作

注意:尽管列表中的项在逻辑上总是连续的,但是它们在物理内存中不一定是连续的。列表的数组实现了使用物理位置来表示逻辑顺序,但是链表的实现并没有那么做。


列表的操作分类

基于索引的操作

基于索引的操作会操作列表中的指定索引的项,在基于数组实现的例子中,这些操作也提供了方便的随机访问。

列表基于索引的操作
列表方法作用
L.insert(i,item)在将各项向右移动一个位置之后,在索引i添加项item
L.pop(i = None)删除并返回位于索引i的项,如果没有参数i,删除并返回最后一项;先验条件:0 <= i <= len(L)
L.[i]返回位于索引i的项,先验条件:0 <= i <= len(L)
L.[i] = item用item替代位于索引i的项,先验条件:0 <= i <= len(L)

从这个视角来查看的时候,列表有时候叫做向量或序列,并且由于它们使用索引,会令人想起数组。然而,数组是一种具体的数据结构,拥有基于单个的物理内存块的一种特定的、不变的实现,而列表是一种数据类型,可以以各种方式表示,其中只有一种方式使用了数组。此外,列表所拥有的基本操作的范围,要比数组的大得多,即使合适的数组序列可以模拟所有的列表操作。

基于内容的操作

一个列表的基于内容的操作
列表方法作用
L.add(item)在列表的尾部添加一项
L.remove(item)从列表中删除一项。先验条件:该项位于列表之中
L.index(item)返回列表中的第一个实例的位置。先验条件:该项位于列表之中。
基于位置的操作

基于位置的操作是相对于当前建立的名为游标的位置而执行的。这个操作允许程序员通过移动游标在列表中导航。尽管在Python中列表已经支持迭代器了,它允许程序员使用for循环来访问列表的项,但列表迭代器要更为强大一些。与简单的迭代器不同,列表迭代器支持移动到之前的位置,直接移动到第一个位置以及直接移动到最后一个位置。除了这些导航操作以后,列表迭代器还支持从游标位置插入、替代和删除项。

这里,我们可以通过在列表上运行listIterator方法(列表类ArrayList中含有一个listIterator方法,这个方法调用了列表迭代器类ArrayListIterator)来创建列表迭代器,如下所示:

listIterator = aList.listIterator()

一开始,在列表迭代器第一次实例化的时候,其游标是未定义的。在将项插入到列表中之后,用户可以通过将游标移动到列表的开始或末尾,从而建立游标的位置。从这些位置开始,用户可以以某种方式导航到另一个位置。

列表迭代器的导航操作
操作目标
LI.hasNext()如果在游标之后有项,返回True;如果游标未定义或位于最后一项之后,返回False
LI.next()返回下一项并且将游标向右移动一个位置。先验条件:hasNext返回True,在最近一次next或previous操作之后,列表上没有干扰性修改
LI.hasPrevious()      如果在游标之前有项,返回True;如果游标未定义或位于第一项之前,返回False
LI.previous()返回之前的一项,并且将游标向右移动一个位置。先验条件:hasPrevious返回True,在最近一次next或previous操作之后,列表上没有干扰性修改
LI.first()将游标移动到第一项之前(如果有第一项的话)
LI.last()将游标移动到最后一项之后(如果有最后一项的话)
对一个列表迭代器的修改操作
操作作用
LI.insert(item)如果定义了游标的话,在其后插入项;否则,在列表的尾部插入项
LI.remove()删除最近的next或previous操作所返回的项。先验条件:在最近一次next或previous之后,列表上没有干扰性修改
LI.replace(item)替代最近一次next或previous操作调用所返回的项。先验条件:在最近一次next或previous之后,列表上没有干扰性修改

下表列出了在列表迭代器上的一系列操作:

列表上的列表迭代器操作的结果
操作操作之后的位置操作后的列表返回值说明
实例化未定义 一个新的列表迭代器
insert(a)未定义a 当游标未定义的时候,插入的每一项都位于列表的末尾
insert(b)未定义

ab

  
hasNext()未定义 False当游标未定义时,没有下一项或者前一项
first()0,ab 将游标放在第一项之前(如果有第一项的话)
hasNext()0 True在游标的右边有一个项
next()1a,ba返回a并且将游标向右移动
replace(c)1c,b 用c替换a,后者是next最近返回的项
next()2c b,b返回b并且游标向右移动
next()2c b,异常游标位于列表的末尾,因此,无法移动到下一项
hasNext()2c b,False游标位于列表末尾
hasPrevious()2c b,True游标左边有一个项
previous()1c,bb返回b并且将游标向左移动
insert(e)1c,eb 在游标位置的右边插入e
remove()1c,eb异常自最近的next或previous之后,发生了一次insert
previous()0,c ebc返回c并且将游标向左移动
remove()0, eb 删除c,这是最近的previous返回的项

注意:

(1)除非列表中至少有一项,并且已经运行过first或last方法,否则是没有当前位置的

(2)我们知道remove和replace在一次成功的next或previous操作所返回的最后一项运行,前提是这里没有干扰性的insert或remove操作

下面代码也展示了列表迭代器的用法。可以假设已经定义了ArrayList类,而该类支持前面介绍的操作。

from arraylist import ArrayList

def test(listType):
    """Expects a list type as an argument and runs some tests
    on objects of that type.""" 
    print("Create a list with 1-9")
    lyst = listType(range(1, 10))
    print("Length:", len(lyst))
    print("Items (first to last):", lyst)
    
    # Create and use a list iterator
    listIterator = lyst.listIterator()
    print("Forward traversal: ", end="")
    listIterator.first()
    while listIterator.hasNext(): 
            print(listIterator.next(), end = " ")

    print("\nBackward traversal: ", end="")
    listIterator.last()
    while listIterator.hasPrevious(): 
            print(listIterator.previous(), end=" ")

    print("\nInserting 10 before 3: ", end="")
    listIterator.first()
    for count in range(3):
            listIterator.next()
    listIterator.insert(10)
    print(lyst)
    print("Removing 2: ", end="")
    listIterator.first()
    for count in range(2): 
            listIterator.next()
    listIterator.remove()
    print(lyst)

    print("Removing all items")
    listIterator.first()
    while listIterator.hasNext():
            listIterator.next()
            listIterator.remove()
    print("Length:", len(lyst))
       
test(ArrayList)

代码输出:

Create a list with 1-9
Length: 9
Items (first to last): [1, 2, 3, 4, 5, 6, 7, 8, 9]
Forward traversal: 1 2 3 4 5 6 7 8 9
Backward traversal: 9 8 7 6 5 4 3 2 1
Inserting 10 before 3: [1, 2, 10, 3, 4, 5, 6, 7, 8, 9]
Removing 2: [1, 10, 3, 4, 5, 6, 7, 8, 9]
Removing all items
Length: 0


列表的接口

根据前面我们对列表操作的介绍,假设你要将这些操作划分到两个接口中。第一个接口包括了基于索引的操作和基于内容的操作,它们类似于Python的列表类中的那些操作。第二个接口包含了用于列表迭代器的操作。



列表的实现

AbstractList类

每个具体的列表类都是在一个名为AbstractList的抽象类之下进行子类化的,而不是从头开始。由于这个类是AbstractCollection的一个子类,列表类也继承了常用的集合方法以及self._size变量。上文列表的接口处的框图可以看出这点。

基于内容的方法index是针对给定的项的一次搜索。由于该搜索可以使用简单的for循环,你可以在AbstractList中定义该方法,此外,基于内容的方法remove和add,分别可以调用基于索引的方法pop和insert,以便在调用index找到位置之后,删除或添加一项。因此,也可以在AbstractList类中定义这两个基于内容的方法。

from abstractcollection import AbstractCollection

class AbstractList(AbstractCollection):
    """Represents an abstract list."""

    def __init__(self, sourceCollection):
        """Maintains a count of modifications to the list."""
        self._modCount = 0
        AbstractCollection.__init__(self, sourceCollection)

    def getModCount(self):
        """Returns the count of modifications to the list."""
        return self._modCount

    def incModCount(self):
        """Increments the count of modifications to the list."""
        self._modCount += 1

    def index(self, item):
        """Precondition: item is in the list.
        Returns the position of item.
        Raises: ValueError if the item is not in the list."""
        position = 0
        for data in self:
            if data == item:
                return position
            else:
                position += 1
        if position == len(self):
            raise ValueError(str(item) + " not in list.")

    def remove(self, item):
        """Precondition: item is in self.
        Raises: ValueError if item in not in self.
        Postcondition: item is removed from self."""
        position = self.index(item)
        self.pop(position)

    def add(self, item):
        """Adds the item to the end of the list."""
        self.insert(len(self), item)

    def append(self, item):
        """Adds the item to the end of the list."""
        self.add(item)
基于数组的实现

列表接口的基于数组的实现是一个名为ArrayList的类。ArrayList将其数据项保存在Array类的一个实例中。

from arrays import Array
from abstractlist import AbstractList
from arraylistiterator import ArrayListIterator

class ArrayList(AbstractList):
    """An array-based list implementation."""

    DEFAULT_CAPACITY = 10

    def __init__(self, sourceCollection = None):
        """Sets the initial state of self, which includes the contents of sourceCollection, if it's present."""
        self._items = Array(ArrayList.DEFAULT_CAPACITY)
        AbstractList.__init__(self, sourceCollection)

    def _resize(self, array, logicalSize):
        """If the array needs resizing, resizes and returns the new array.  Otherwise, returns the olf array."""
        temp = None
        # If array is full
        if len(array) == logicalSize:
            temp = Array(2 * len(array))
        # If array is wasting space
        elif logicalSize <= .25 * len(array) and \
             len(array) >= ArrayList.DEFAULT_CAPACITY:
            temp = Array(round(.5 * len(array)))
        # None of the above
        else:
            return array
        # Copy items to new array
        for i in range(logicalSize):
            temp[i] = array[i]
        return temp


    # Accessor methods
    def __iter__(self):
        """Supports iteration over a view of self."""
        cursor = 0
        while cursor < len(self):
            yield self._items[cursor]
            cursor += 1

    def __getitem__(self, i):
        """Precondition: 0 <= i < len(self)
        Returns the item at position i.
        Raises: IndexError."""
        if i < 0 or i >= len(self):
            raise IndexError("List index out of range")
        return self._items[i]

    # Mutator methods
    def clear(self):
        """Makes self become empty."""
        self._size = 0
        self._items = Array(ArrayList.DEFAULT_CAPACITY)

    def __setitem__(self, i, item):
        """Precondition: 0 <= i < len(self)
        Replaces the item at position i.
        Raises: IndexError."""
        if i < 0 or i >= len(self):
            raise IndexError("List index out of range")
        self._items[i] = item

    def insert(self, i, item):
        """Inserts the item at position i."""
        self._items = self._resize(self._items, len(self))
        if i < 0: i = 0
        elif i > len(self): i = len(self)
        if i < len(self):
            for j in range(len(self), i, -1):
                 self._items[j] = self._items[j - 1]
        self._items[i] = item
        self._size += 1
        self.incModCount()

    def pop(self, i = None):
        """Precondition: 0 <= i < len(self).
        Removes and returns the item at position i.
        If i is None, i is given a default of len(self) - 1.
        Raises: IndexError."""
        if i == None: i = len(self) - 1
        if i < 0 or i >= len(self):
            raise IndexError("List index out of range")
        item = self._items[i]
        for j in range(i, len(self) - 1):
            self._items[j] = self._items[j + 1]
        self._size -= 1
        self.incModCount()
        self._items = self._resize(self._items, len(self))
        return item

    def listIterator(self):
        """Returns a list iterator."""
        return ArrayListIterator(self)

我们注意到,这个列表类里面包含了listIterator方法,这个方法返回了一个列表迭代器。


基于链表的实现

单链表结构对于列表迭代器的支持并不理想。列表迭代器允许游标在任何一个方向上移动,但是,单链表结构只支持朝着下一个节点移动,可以使用双链表结构来解决这个问题,其中,每个节点都有一个指针指向前一个节点,还有一个指针指向下一个节点

如果在该结构的头部添加一个额外的节点的话,实现双链表结构所需的代码可以得到简化。这个节点成为哨兵节点(sentinel node),它向前指向第一个数据节点,向后指向最后一个节点。头指针指向哨兵节点。需要注意的是,哨兵节点并不包括列表项,并且当列表为空的时候,仍然保留哨兵节点


一个双向链表结构的基本构建模块是带有两个指针的一个节点,next指针指向右边,而previous指针指向左边。节点的类型为TwoWayNode,这是Node类的一个子类(相比于Node类多了一个self.previous属性)。

from node import TwoWayNode
from abstractlist import AbstractList

class LinkedList(AbstractList):
    """A link-based list implementation."""

    def __init__(self, sourceCollection = None):
        """Sets the initial state of self, which includes the
        contents of sourceCollection, if it's present."""
        # Uses a circular linked structure with a dummy header node
        self._head = TwoWayNode()
        self._head.previous = self._head.next = self._head
        AbstractList.__init__(self, sourceCollection)

注意:__init__方法创建了一个没有数据的节点,这是一个哨兵节点。

然后就是开发基于索引的方法:__getitem__、__setitem__、insert和pop。这些方法都需要从头节点开始,直到到达第i个节点。由于搜索第i个节点是所有这4个方法都要执行的操作,我们构造了一个名为_getNode的辅助方法,由它来完成这一工作。这个方法接收目标节点的索引位置作为一个参数,并且返回指向第i个节点的一个指针。4个调用方法随后使用该指针来对链表结构进行相应的操作。

下面是辅助函数_getNode

    # Helper method returns node at position i
    def _getNode(self, i):
        """Helper method: returns a pointer to the node at position i."""
        if i == len(self):
            return self._head
        elif i == len(self) - 1:
            return self._head.previous
        probe = self._head.next
        while i > 0:
            probe = probe.next
            i -= 1
        return probe

下面4个方法

    def __getitem__(self, i):
        """Precondition: 0 <= i < len(self)
        Returns the item at position i.
        Raises: IndexError."""
        if i < 0 or i >= len(self):
            raise IndexError("List index out of range")
        return self._getNode(i).data

    def __setitem__(self, i, item):
        """Precondition: 0 <= i < len(self)
        Replaces the item at position i.
        Raises: IndexError."""
        if i < 0 or i >= len(self):
            raise IndexError("List index out of range")
        self._getNode(i).data = item

    def insert(self, i, item):
        """Inserts the item at position i."""
        if i < 0: i = 0
        elif i > len(self): i = len(self)
        theNode = self._getNode(i)
        newNode = TwoWayNode(item, theNode.previous, theNode)
        theNode.previous.next = newNode
        theNode.previous = newNode
        self._size += 1
        self.incModCount()

    def pop(self, i = None):
        """Precondition: 0 <= i < len(self).
        Removes and returns the item at position i.
        If i is None, i is given a default of len(self) - 1.
        Raises: IndexError."""
        if i == None: i = len(self) - 1
        if i < 0 or i >= len(self):
            raise IndexError("List index out of range")
        theNode = self._getNode(i)
        item = theNode.data
        theNode.previous.next = theNode.next
        theNode.next.previous = theNode.previous
        self._size -= 1
        self.incModCount()
        return item
两种实现的时间和空间分析

在访问方法和替换方法中,也就是__getitem__和__setitem__方法中,数组实现只需要直接以常数时间运行数组的下标操作就可以了,链表实现需要在链表结构中执行对第i个节点的线性搜索。

另外两个基于索引的方法,insert和pop,它们的运行时间在两种情况下都是线性的,数组实现中以常数时间找到目标项的位置,但是却需要线性时间移动项来完成该过程。相反,链表实现需要线性时间来找到目标项,但只需要常数时间就可以插入或删除一个节点

两种实现的基于内容的方法index的时间复杂度都是O(n),而基于内容的方法remove运行了index方法,然后是pop方法,因此时间复杂度也为O(n)。

这里需要注意的是,add方法是直接添加到末尾的,所以是常数时间复杂度

列表实现的空间分析,和之前栈、队列的类似。唯一不同之处在于这里的链表实现中,每个节点包含3个引用(next、previous、data),所以当数组实现的装载因子大于三分之一的时候,数组实现的内存使用比链表实现更为高效。


实现列表迭代器

列表迭代器是附加到列表上的一个对象,它提供在列表上的位置性的操作,这些操作前文就已经指出,它们允许程序员通过移动游标来查看和修改列表。

当我们在列表上运行listIteration方法的时候,这个方法会返回列表迭代器类的一个新的实例。列表迭代器对象依赖于相关联的列表,因为前者需要访问后者以定位项、替换项、插入项和删除项。因此,列表迭代器将保持对其列表或后备存储的一个引用,而这个列表是创建列表迭代器时所接收的。

列表迭代器除了支持其基本的操作,还必须遵守其先验条件,有3种类型的先验条件:

(1)如果hasNext或hasPrevious操作分别返回False的话,程序员不能运行next或previous操作

(2)程序员不能在列表迭代器上运行连续性的修改器方法。在每一次修改前,必须运行一个next或previous操作

(3)在列表上使用列表迭代器的时候,程序员不能使用列表的修改器方法在列表自身之上进行修改

为了帮助判断这些先验条件,列表迭代器有两个额外的变量。

第一个变量是自身的modCount变量,当创建列表迭代器的时候,这个变量设置为列表的modCount,因此列表和列表迭代器都有自己的modCount。无论何时,当运行列表自己的修改器的方法的时候,会递增列表的modCount以记录对列表的修改(前面的ArrayList代码中可以看出来)。每当列表迭代器上运行某个方法时(例如next和previous),列表迭代器会将自己的modCount和列表的modCount进行比较,如果两个值不同,说明已经有人在错误的环境中运行了列表的修改器方法,随后会引发异常。每当使用列表迭代器方法修改列表的时候,列表迭代器会增加自己的modCount,以便让两个modCount保持一致。

第二个变量记录了列表迭代器能够在列表上执行一次修改的位置。在基于数组的实现中,无论何时,当该位置还没有确立的时候,这个变量是-1,任何时候,当程序员在列表迭代器上成功地运行一个next或previous操作的时候,其值变为了列表中的一个索引。因此,列表迭代器中的修改器方法insert和remove能够通过检查这个变量以确保其先验条件,并且在它们成功修改了列表之后将其重置为-1。

设置和实例化一个列表迭代器类

数组列表的列表迭代器类名为ArrayListIterator。这个类包含了如下的实例变量:

self._backingStore——在其上打开迭代器的列表

self._modCount——modCount的迭代器表示;

self._cursor——迭代器的导航方法first、last、hasNext、next、hasPrevious和previous所维护的游标位置;

self._lastItemPos——迭代器的修改器方法insert、remove和replace所使用的游标位置。这个位置通过运行next或previous而建立,并且在运行了insert或remove之后是未定义的

ArrayList方法listIterator在其实例化的过程中,将后备存储(self)传递给列表迭代器。随后,列表迭代器可以在该对象上运行列表方法以操作它。

class ArrayListIterator(object):
    """Represents the list iterator for an array list."""

    def __init__(self, backingStore):
        self._backingStore = backingStore
        self._modCount = backingStore.getModCount()
        self.first()

    def first(self):
        """Returns the cursor to the beginning of the backing store."""
        self._cursor = 0
        self._lastItemPos = -1

列表迭代器中的导航方法

在向后移动游标并且从后备存储返回项之前,next方法必须检查两个先验条件。第一个是hasNext方法必须返回True。第二个是两个modCount(一个属于列表迭代器,另一个属于后备存储)必须相等。如果这两个先验条件都满足,方法next将self._lastItemPos设为self._cursor,将后者增加1,并且返回后备存储中位于self._lastItemPos的项。

    def hasNext(self):
        """Returns True if the iterator has a next item or False otherwise."""
        return self._cursor < len(self._backingStore)

    def next(self):
        """Preconditions: hasNext returns True
        The list has not been modified except by this iterator's mutators.
        Returns the current item and advances the cursor to the next item.
        Postcondition: lastItemPos is now defined.
        Raises: ValueError if no next item.
        AttributeError if illegal mutation of backing store."""
        if not self.hasNext():
            raise ValueError("No next item in list iterator")
        if self._modCount != self._backingStore.getModCount():
            raise AttributeError("Illegal modification of backing store")
        self._lastItemPos = self._cursor
        self._cursor += 1
        return self._backingStore[self._lastItemPos]

last、hasPrevious和previous方法使用游标从基于数组的列表的末尾向其开头移动。previous方法检查和next方法相同的两个先验条件,然后,它将游标减小1,将self._lastItemPos设置为self._cursor,并且返回后备存储中位于self._lastItemPos的项。

注意:这里游标先减1和之前不冲突,因为修改器方法要修改的是next、previous返回的项(self._lastItemPos存储的就是能够执行修改的位置)!!

    def last(self):
        """Moves the cursor to the end of the backing store."""
        self._cursor = len(self._backingStore)
        self._lastItemPos = -1

    def hasPrevious(self):
        """Returns True if the iterator has a previous item or False otherwise."""
        return self._cursor > 0

    def previous(self):
        """Preconditions: hasPrevious returns True
        The list has not been modified except by this iterator's mutators.
        Returns the current item and moves the cursor to the previous item.
        Postcondition: lastItemPos is now defined.
        Raises: ValueError if no next item.
        AttributeError if illegal mutation of backing store."""
        if not self.hasPrevious():
            raise ValueError("No previous item in list iterator")
        if self._modCount != self._backingStore.getModCount():
            raise AttributeError("Illegal modification of backing store")
        self._cursor -= 1
        self._lastItemPos = self._cursor
        return self._backingStore[self._lastItemPos]

列表修改器中的修改器方法

修改器方法remove、replace必须检查两个先验条件。首先必须建立游标,这意味着变量self._lastItemPos必须不等于-1,其次,两个modCount必须相等。其中insert方法只用检查modCount这一个先验条件。

(1)replace方法的任务最简单。将后备存储中当前位置的项替换,并且将self._lastItemPos重新设置为-1,在替换操作过程中,列表迭代器的modCount不再递增

(2)如果游标已经定义了,insert方法在当前位置向后备存储中插入该项,并且将self._lastItemPos重新设置为-1。否则,将该项添加到后备存储的末尾。在两种情况下,列表迭代器的modCount都将递增。

(3)remove方法从后备存储的当前位置弹出该项,并且将列表迭代器的modCount递增1。如果self._lastItemPos小于self._cursor,这意味着remove将会在一个next操作之后运行,因此,游标会减1。最后,将self._lastItemPos重新设置为-1。

    def replace(self, item):
        """Preconditions: the current position is defined.
        The list has not been modified except by this iterator's mutators.
        Replaces the items at the current position with item.
        Raises: AttibuteError if position is not defined.
        AttributeError if illegal mutation of backing store."""
        if self._lastItemPos == -1:
            raise AttributeError("The current position is undefined.")
        if self._modCount != self._backingStore.getModCount():
            raise AttributeError("List has been modified illegally.")
        self._backingStore[self._lastItemPos] = item
        self._lastItemPos = -1

    def insert(self, item):         
        """Preconditions:
        The list has not been modified except by this iterator's mutators.
        Adds item to the end if the current position is undefined, or
        inserts it at that position.
        Raises: AttributeError if illegal mutation of backing store."""
        if self._modCount != self._backingStore.getModCount():
            raise AttributeError("List has been modified illegally.")
        if self._lastItemPos == -1:
            self._backingStore.add(item)
        else:
            self._backingStore.insert(self._lastItemPos, item)
            self._lastItemPos = -1
        self._modCount += 1

    def remove(self):         
        """Preconditions: the current position is defined.
        The list has not been modified except by this iterator's mutators.
        Pops the item at the current position.
        Raises: AttibuteError if position is not defined.
        AttributeError if illegal mutation of backing store."""
        if self._lastItemPos == -1:
            raise AttributeError("The current position is undefined.")
        if self._modCount != self._backingStore.getModCount():
            raise AttributeError("List has been modified illegally.")
        item = self._backingStore.pop(self._lastItemPos)
        # If the item removed was obtained via next, move cursor back
        if self._lastItemPos < self._cursor:
            self._cursor -= 1
        self._modCount += 1
        self._lastItemPos = -1


本文是数据结构(用Python实现)这本书的读书笔记!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值