一个快速排序写了快 10000 字?

本文详细介绍了快速排序的基本思想、实现方法及优化技巧,并对比了快速排序与归并排序的区别。

【优快云 编者按】之前一起看了归并排序和一些利用归并排序可以解决的经典题目,今天我们再来说一下另一个高频考点,快速排序。

作者 | 袁厨的算法小屋       责编 | 张红月

你或许已经掌握了快速排序,或者看过一些其他文章,不过我相信,读完这个文章肯定还会有所收获!

快速排序

   

今天我们来说一下快速排序,这个排序算法也是面试的高频考点,原理很简单,我们一起来扒一下他吧。

我们先来说一下快速排序的基本思想,很容易理解。

1.先从数组中找一个基准数

2.让其他比它大的元素移动到数列一边,比他小的元素移动到数列另一边,从而把数组拆解成两个部分。

3.再对左右区间重复第二步,直到各区间只有一个数。

见下图

快速排序

上图则为一次快排示意图,下面我们再利用递归,分别对左半边区间也就是 [3,1,2] 和右半区间 [7,6,5,8] 执行上诉过程,直至区间缩小为 1 也就是第三条,则此时所有的数据都有序。

简单来说就是我们利用基准数通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比基准数小,另一部分记录的关键字均比基准数大,然后分别对这两部分记录继续进行分割,进而达到有序。

我们现在应该了解了快速排序的思想,那么大家还记不记得我们之前说过的归并排序,他们两个用到的都是分治思想,那他们两个有什么不同呢?见下图

注:这里我们以区间的第一个元素作为基准数

对比

虽然归并排序和快速排序都用到了分治思想,但是归并排序是自下而上的,先处理子问题,然后再合并,将小集合合成大集合,最后实现排序。

快速排序是由上到下的,先分区,然后再处理子问题。归并排序虽然是稳定的、时间复杂度为 O(nlogn) 的排序算法,但是它是非原地排序算法。我们前面讲过,归并之所以是非原地排序算法,主要原因是合并函数需要利用辅助数组保存元素。

快速排序通过设计巧妙的原地分区函数,可以实现原地排序,解决了归并排序占用太多内存的问题。

我们根据思想可知,快速排序算法的核心就是如何利用基准数将记录分区,这里我们主要介绍两种容易理解的方法,一种是挖坑填数,另一种是利用双指针思想进行元素交换。

下面我们先来介绍下挖坑填数的分区方法

基本思想是我们首先以序列的第一个元素为基准数,然后将该位置挖坑,下面判断 nums[hight] 是否大于基准数,如果大于,则左移 hight 指针,直至找到一个小于基准数的元素,将其填入之前的坑中,则 hight 位置会出现一个新的坑,此时移动 low 指针,找到大于基准数的元素,填入新的坑中。不断迭代直至完成分区。

大家直接看我们的视频模拟吧,一目了然。

注:为了便于理解所以采取了挖坑的形式展示

是不是很容易就理解啦,下面我们直接看代码吧。

class Solution {
    public int[] sortArray(int[] nums) { 

        quickSort(nums,0,nums.length-1);
        return nums;

    }
    public void quickSort (int[] nums, int low, int hight) {

        if (low < hight) {
            int index = partition(nums,low,hight);
            quickSort(nums,low,index-1);
            quickSort(nums,index+1,hight);
        }    

    }
    public int partition (int[] nums, int low, int hight) {

            int pivot = nums[low];
            while (low < hight) {
                //移动hight指针
                while (low < hight && nums[hight] >= pivot) {
                    hight--;
                }
                //填坑
                if (low < hight) nums[low] = nums[hight];
                while (low < hight && nums[low] <= pivot) {
                    low++;
                }
                //填坑
                if (low < hight) nums[hight] = nums[low];
            }
            //基准数放到合适的位置
            nums[low] = pivot;
            return low;
    }   
}

下面我们来看一下第二种交换方法的思路,原理一致,实现也比较简单。

见下图。

快速排序

其实这种方法,算是对上面方法的挖坑填坑步骤进行合并,low 指针找到大于 pivot 的元素,hight 指针找到小于 pivot 的元素,然后两个元素交换位置,最后再将基准数归位。

两种方法都很容易理解和实现,即使是完全没有学习过快速排序的同学,理解了思想之后也能自己动手实现,下面我们继续用视频模拟下这种方法的执行过程吧。

两种方法都很容易实现,对我们非常友好,大家可以自己去 AC 一下啊。

class Solution {
    public int[] sortArray (int[] nums) {   

        quickSort(nums,0,nums.length-1);
        return nums;

    }

    public void quickSort (int[] nums, int low, int hight) {

        if (low < hight) {
            int index = partition(nums,low,hight);
            quickSort(nums,low,index-1);
            quickSort(nums,index+1,hight);
        } 

    }

    public int partition (int[] nums, int low, int hight) {

            int pivot = nums[low];
            int start = low;

            while (low < hight) {
                while (low < hight && nums[hight] >= pivot) hight--;           
                while (low < hight && nums[low] <= pivot) low++;
                if (low >= hight) break;
                swap(nums, low, hight);  
            }
            //基准值归位
            swap(nums,start,low);
            return low;
    }  
    public void swap (int[] nums, int i, int j) {      
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
     } 
}

快速排序的时间复杂度分析

快排也是用递归来实现的。所以快速排序的时间性能取决于快速排序的递归树的深度。

如果每次分区操作,都能正好把数组分成大小接近相等的两个小区间,那么此时的递归树是平衡的,性能也较好,递归树的深度也就和之前归并排序求解方法一致。

我们每一次分区需要对数组扫描一遍,做 n 次比较,所以最优情况下,快排的时间复杂度是 O(nlogn)。

但是大多数情况下我们不能划分的很均匀,比如数组为正序或者逆序时,即 [1,2,3,4] 或 [4,3,2,1] 时,此时为最坏情况,那么此时我们则需要递归调用 n-1 次,此时的时间复杂度则退化到了 O(n^2)。

快速排序的空间复杂度分析

快速排序主要时递归造成的栈空间的使用,最好情况时其空间复杂度为O (logn),对应递归树的深度。最坏情况时则需要 n-1 次递归调用,此时空间复杂度为O(n)。

快速排序的稳定性分析

快速排序是一种不稳定的排序算法,因为其关键字的比较和交换是跳跃进行的,见下图。

稳定性

此时无论使用哪一种方法,第一次排序后,黄色的 1 都会跑到红色  1 的前面,所以快速排序是不稳定的排序算法。

性能分析

好啦,快速排序我们掌握的差不多了,下面我们一起来看看如何对其优化吧。

快速排序的迭代写法

该方法实现也是比较简单的,借助了栈来实现,很容易实现。主要利用了先进先出的特性,这里需要注意的就是入栈的顺序,这里算是一个小细节,需要注意一下,其中分区函数和上面一致。大家只需看入栈出栈情况即可。

class Solution {

     public int[] sortArray(int[] nums) {
        Stack<Integer> stack = new Stack<>();
        stack.push(nums.length - 1);
        stack.push(0);
        while (!stack.isEmpty()) {
            int low = stack.pop();
            int hight = stack.pop();

            if (low < hight) {
                int index = partition(nums, low, hight);
                stack.push(index - 1);
                stack.push(low);
                stack.push(hight);
                stack.push(index + 1);
            }
        }
        return nums;
    }

    public int partition (int[] nums, int low, int hight) {

            int pivot = nums[low];
            int start = low;
            while (low < hight) {

                while (low < hight && nums[hight] >= pivot) hight--;           
                while (low < hight && nums[low] <= pivot) low++;
                if (low >= hight) break;
                swap(nums, low, hight); 
            }
            swap(nums,start,low);
            return low;

    } 
    public void swap (int[] nums, int i, int j) {    
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }  
}

快速排序优化

三数取中法

我们在上面的例子中选取的都是 nums[low] 做为我们的基准值,那么当我们遇到特殊情况时呢?见下图

特殊举例

我们按上面的方法,选取第一个元素做为基准元素,那么我们执行一轮排序后,发现仅仅是交换了 2 和 7 的位置,这是因为 7 为序列的最大值。

所以我们的 pivot 的选取尤为重要,选取时应尽量避免选取序列的最大或最小值做为基准值。则我们可以利用三数取中法来选取基准值。

也就是选取三个元素中的中间值放到 nums[low] 的位置,做为基准值。这样就避免了使用最大值或最小值做为基准值。

所以我们可以加上这几行代码实现三数取中法。

     int mid = low + ((hight-low) >> 1);
     if (nums[low] > nums[hight]) swap(nums,low,hight);
     if (nums[mid] > nums[hight]) swap(nums,mid,hight);
     if (nums[mid] > nums[low]) swap(nums,mid,low); 

其含义就是让我们将中间元素放到 nums[low] 位置做为基准值,最大值放到 nums[hight],最小值放到 nums[mid],即 [4,2,3] 经过上面代码处理后,则变成了 [3,2,4]。

此时我们选取 3 做为基准值,这样也就避免掉了选取最大或最小值做为基准值的情况。

三数取中法代码

class Solution {
    public int[] sortArray(int[] nums) {       
        quickSort(nums,0,nums.length-1);
        return nums;
    }
    public void quickSort (int[] nums, int low, int hight) {
        if (low < hight) {
            int index = partition(nums,low,hight);
            quickSort(nums,low,index-1);
            quickSort(nums,index+1,hight);
        }       
    }

    public int partition (int[] nums, int low, int hight) {
            //三数取中,大家也可以使用其他方法
            int mid = low + ((hight-low) >> 1);
            if (nums[low] > nums[hight]) swap(nums,low,hight);
            if (nums[mid] > nums[hight]) swap(nums,mid,hight);
            if (nums[mid] > nums[low]) swap(nums,mid,low); 
            //下面和之前一样,仅仅是多了上面几行代码
            int pivot = nums[low];
            int start = low;
            while (low < hight) {
                while (low < hight && nums[hight] >= pivot) hight--;           
                while (low < hight && nums[low] <= pivot) low++;
                if (low >= hight) break;
                swap(nums, low, hight); 
            }
            swap(nums,start,low);
            return low;
    }  
    public void swap (int[] nums, int i, int j) {     
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }  
}

和插入排序搭配使用

我们之前说过,插入排序在元素个数较少时效率是最高的,没有看过的同学可以去看一下之前的文章,所以当元素数量较少时,快速排序反而不如插入排序好用。

所以我们可以设定一个阈值,当元素个数大于阈值时使用快速排序,小于等于该阈值时则使用插入排序。我们设定阈值为 7 。

三数取中+插入排序

class Solution {
    private static final int INSERTION_SORT_MAX_LENGTH = 7;

    public int[] sortArray(int[] nums) {      
        quickSort(nums,0,nums.length-1);
        return nums;
    }

    public void quickSort (int[] nums, int low, int hight) {

            if (hight - low <= INSERTION_SORT_MAX_LENGTH) {
                insertSort(nums,low,hight);
                return;
            }               
            int index = partition(nums,low,hight);
            quickSort(nums,low,index-1);
            quickSort(nums,index+1,hight);         
    }

    public int partition (int[] nums, int low, int hight) {
            //三数取中,大家也可以使用其他方法
            int mid = low + ((hight-low) >> 1);
            if (nums[low] > nums[hight]) swap(nums,low,hight);
            if (nums[mid] > nums[hight]) swap(nums,mid,hight);
            if (nums[mid] > nums[low]) swap(nums,mid,low);   
            int pivot = nums[low];
            int start = low;
            while (low < hight) {
                while (low < hight && nums[hight] >= pivot) hight--;           
                while (low < hight && nums[low] <= pivot) low++;
                if (low >= hight) break;
                swap(nums, low, hight); 
            }
            swap(nums,start,low);
            return low;
    } 

    public void insertSort (int[] nums, int low, int hight) {

        for (int i = low+1; i <= hight; ++i) {
            int temp = nums[i];
            int j;
            for (j = i-1; j >= 0; --j) {
                if (temp < nums[j]) {
                    nums[j+1] = nums[j];
                    continue;
                } 
                break;
            }
            nums[j+1] = temp;
        }
    } 
    public void swap (int[] nums, int i, int j) {

        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    } 
}

好啦,我们的插入排序和快速排序的搭配使用就搞定啦。

我们继续看下面这种情况

我们对其执行一次排序后,则会变成上面这种情况,然后我们继续对蓝色基准值的左区间和右区间执行相同操作。也就是 [2,3,6,3,1,6] 和 [8,6] 。我们注意观察一次排序的结果数组中是含有很多重复元素的,我们之前的优化方式并不能很好的解决这种情况。

那么我们为什么不能将相同元素放到一起呢?这样就能大大减小递归调用时的区间大小,见下图。

三向切分

这样就减小了我们的左右区间大小,只需对区间 [3,1,2,4] 和 [8] 执行相同操作即可,我们将数组切分成了三部分,小于基准数的左区间,大于基准数的右区间,等于基准数的中间区间。

下面我们来看一下如何达到上面的情况,我们先来进行视频模拟,模拟之后应该就能明白啦。

我们来剖析一下视频,其实原理很简单,我们利用探路指针也就是  i,遇到比 pivot 大的元素,则和 right 指针进行交换,交换后 right 指向的元素肯定比 pivot 大,则 right--,但是,此时我们的 nums[i] 指向的元素并不知道情况,所以我们的 i 指针先不动,继续判断。

如果此时 nums[i] < pivot 则与 left 指针交换,注意此时我们的 left 指向的值肯定是等于 povit的,所以交换后我们要 left++,i++, nums[i] == pivot 时,仅需要 i++ 即可,继续判断下一个元素。我们也可以借助这个思想来解决经典的荷兰国旗问题。

好啦,我们下面直接看代码吧。

三数取中+三向切分+插入排序

class Solution {
    private static final int INSERTION_SORT_MAX_LENGTH = 7;
    public int[] sortArray(int[] nums) {
        quickSort(nums,0,nums.length-1);
        return nums;

    }
    public void quickSort(int nums[], int low, int hight) {
        //插入排序
        if (hight - low <= INSERTION_SORT_MAX_LENGTH) {
            insertSort(nums,low,hight);
            return;
        }
        //三数取中
        int mid = low + ((hight-low) >> 1);
        if (nums[low] > nums[hight]) swap(nums,low,hight);
        if (nums[mid] > nums[hight]) swap(nums,mid,hight);
        if (nums[mid] > nums[low]) swap(nums,mid,low);
        //三向切分
        int left = low,  i = low + 1, right = hight;
        int pvoit = nums[low];
        while (i <= right) {
            if (pvoit < nums[i]) {
                swap(nums,i,right);
                right--;
            } else if (pvoit == nums[i]) {
                i++;
            } else {
                swap(nums,left,i);
                left++;
                i++;
            }
        }
        quickSort(nums,low,left-1);
        quickSort(nums,right+1,hight);
    }
     public void insertSort (int[] nums, int low, int hight) {

        for (int i = low+1; i <= hight; ++i) {
            int temp = nums[i];
            int j;
            for (j = i-1; j >= 0; --j) {
                if (temp < nums[j]) {
                    nums[j+1] = nums[j];
                    continue;
                } 
                break;
            }
            nums[j+1] = temp;
        }
    } 
    public  void swap (int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

好啦,一些常用的优化方法都整理出来啦,还有一些其他的优化算法九数取中,优化递归操作等因为不常用就不在这里进行描述啦。



☞Linux 之父家断电六天:内核更新延期;华为 EMUI 11 用户突破1亿;美国得州最大电力公司申请破产 | 极客头条☞千亿项目的弘芯团队解散,中国芯美梦何圆?☞平均 15189 元!2021 年 3 月程序员工资统计出炉☞为什么我们很难看到代码 5 分钟前的样子?
<think>首先,用户要求我提供使用Python对数int列表进行快速排序的方法。我需要基于系统级指令和用户查询结构化我的回答。 系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1] 用户查询:用户想查找使用Python对数int列表进行快速排序的方法。 从引用中: - 引用[1]、[2]、[3]、[4]提供了关于Python排序算法的信息,包括快速排序。 - 特别是引用[2]提到了快速排序的实现代码片段:它展示了快速排序的Python代码。 - 用户指定了数int列表,所以我需要专注于数值列表的排序。 我的回答结构: - 我需要清晰、逐步地解释如何实现快速排序。 - 包括代码示例。 - 使用中文。 - 生成相关问题。 - 确保LaTeX格式正确,但既然这是关于代码的,可能很少需要数学;如果有数学概念,如复杂度,可以使用公式。 - 例如,快速排序的平均时间复杂度是$O(n \log n)$,最好使用行内格式。 关键点: 1. **快速排序简介**:简要解释算法的工作原理。 2. **Python实现**:提供代码示例,基于引用[2]中的代码,但确保正确并针对int列表。 3. **步骤分解**:一步步解释代码逻辑。 4. **引用**:自然地添加引用。 5. **相关问题**:在最后生成相关问题。 引用处理: - 引用[2]提到了快速排序的实现,所以我应该在回答中引用它。 - 例如,结束时说:"参考快速排序的实现[^2]"或类似。 代码格式: - 在Markdown中使用代码块表示代码。 - 例如: ```python def quick_sort(arr): # 代码 ``` 具体实现: - 快速排序的基本原理:选择轴心,分区数组(小于轴心的在左,大于等于的在右),递归排序。 - 在这个例子中,用户指定了数int列表,所以不需要处理特殊类型。 - 标准实现:递归方法。 基于引用[2]: - 引用[2]提供了一个快速排序的Python代码片段: ```python # 四、快速排序 # (一)算法简介 # 然后在代码中有一个列表排序,但不是完整的快速排序代码;看起来是一个插入排序的代码。引用[2]说:"四、快速排序",但有插入排序的代码。让我检查。 引用[2]:"data = [i for i in range(1, n + 1)] data.sort(reverse=True) ... for i in range(1, n): value = data[i] ... " – 这看起来像插入排序,不是快速排序。 等一下,用户提供的引用[2]说:"四、快速排序 (一)算法简介",但代码片段是插入排序。这可能是个错误。在引用[2]中,它提到快速排序,但示例代码是插入排序。我应该忽略代码错误部分。 引用[2]的文本:"四、快速排序 (一)算法简介",但没有完整代码。我需要使用标准实现。 为了安全起见,我应该提供标准快速排序实现。 标准快速排序Python代码: ```python def quick_sort(arr): if len(arr) <= 1: return arr pivot = arr[0] left = [x for x in arr[1:] if x < pivot] right = [x for x in arr[1:] if x >= pivot] return quick_sort(left) + [pivot] + quick_sort(right) ``` - 这是一个简单的递归实现。 - 注意:使用列表解析效率较低,因为创建新列表;但简单易懂。 - 对于大列表,可能需要原地排序或其他优化,但用户要求基本方法。 复杂度: - 平均时间复杂度:$O(n \log n)$ - 最坏情况:$O(n^2)$(当轴心选择不当) 引用: - 引用[1]提到快速排序,所以我可以说:"快速排序是一种高效的排序算法[^1]"。 - 引用[2]讨论了快速排序,尽管代码混合了插入排序,我可以引用算法简介。 完整回答结构: 1. **介绍**:快速排序是什么,为什么高效。 2. **Python实现**:提供代码示例。 3. **解释说明**:简要解释代码逻辑。 4. **注意事项**:提到轴心选择和优化。 5. **相关问题生成**:在最后添加相关问题列表。 使用中文: - 整个回答用中文。 相关问题生成: - 基于系统指令:"生成相关问题" - 类似于示例:"§§相关问题§§" - 例如: 1. 快速排序的时间复杂度是多少? 2. 如何优化快速排序算法? 3. 快速排序在Python中与其他排序算法比较如何? LaTeX使用: - 如果在文本中提到时间复杂度,使用$O(n \log n)$。 - 独立公式可能不需要,因为主要是代码。 最终回答框架: - 标题:Python快速排序int列表的方法 - 正文: - 简要介绍快速排序。 - 提供代码块。 - 解释步骤。 - 引用。 - 相关问题。 确保回答真实可靠: - 提供标准实现。 - 提醒注意事项,如递归深度限制。 </think>### Python快速排序int列表的方法 快速排序是一种高效的排序算法,它通过选择一个轴心元素(pivot)将列表分成两部分:小于轴心的元素和大于等于轴心的元素,然后递归地对子列表排序。对于数int列表,这种方法特别高效,平均时间复杂度为$O(n \log n)$。下面我将逐步解释并提供一个完整的Python实现。 #### 1. 快速排序的基本原理 - **轴心选择**:通常选择列表的第一个元素作为轴心(简单但可能非最优)。 - **分区操作**:将列表划分为左子列表(所有元素 < 轴心)、轴心元素本身、右子列表(所有元素 ≥ 轴心)。这个过程可以通过列表解析或原地交换实现。 - **递归排序**:对左右子列表重复以上步骤,直到子列表长度 ≤ 1(此时已排序)。 - **合并结果**:将排序后的左子列表、轴心、右子列表连接起来。 数学上,快速排序的效率依赖于分区操作的平衡性。理想情况下,每次分区将列表均匀分成两份,时间复杂度为$O(n \log n)$;但在最坏情况下(如列表已排序且轴心选择不当),复杂度退化到$O(n^2)$[^1]。 #### 2. Python实现代码 以下是针对数int列表的标准递归实现。该代码简单易懂,适用于初学者理解算法逻辑。 ```python def quick_sort(arr): """对整数列表进行快速排序(递归实现)""" # 基线条件:列表长度为0或1时,直接返回 if len(arr) <= 1: return arr # 选择第一个元素作为轴心 pivot = arr[0] # 分区操作:使用列表解析创建左子列表(小于轴心)和右子列表(大于等于轴心) left = [x for x in arr[1:] if x < pivot] right = [x for x in arr[1:] if x >= pivot] # 递归排序并合并结果 return quick_sort(left) + [pivot] + quick_sort(right) # 示例使用 if __name__ == "__main__": # 创建一个随机整数列表(例如:[5, 3, 8, 1, 2]) num_list = [5, 3, 8, 1, 2] sorted_list = quick_sort(num_list) print("排序前:", num_list) print("排序后:", sorted_list) # 输出:[1, 2, 3, 5, 8] ``` #### 3. 代码解释与注意事项 - **参数说明**:`arr` 是输入的数int列表(例如 `[5, 3, 8, 1, 2]`)。 - **关键步骤**: - `pivot = arr[0]`:选择列表首个元素作为轴心。 - `left` 和 `right`:通过列表解析速分区,避免使用额外循环。 - 递归调用 `quick_sort(left)` 和 `quick_sort(right)`:处理子问题。 - 合并:使用 `+` 运算符连接排序后的子列表。 - **优点**: - 代码简洁,易于理解。 - 平均性能高,适合中等规模列表(n ≤ 10000)。 - **局限性**: - **递归深度限制**:对于大型列表(n > 10000),可能引发Python的递归深度错误(推荐改用迭代或优化算法)。 - **轴心选择优化**:如果列表已部分排序,选择首个元素可能导致性能下降(可改进为随机选择轴心或中位数法)。 - **空间开销**:每次递归创建一个新列表,空间复杂度为$O(n)$;原地排序版本更节省内存[^2]。 - **测试建议**:使用 `random.randint()` 生成随机整数列表测试边界情况(如空列表、重复元素)。 #### 4. 替代优化方案 如果需要处理大型数据集: - **原地排序实现**:减少内存使用(通过交换元素实现分区)。 - **随机轴心选择**:提升平均性能(例如 `pivot = arr[random.randint(0, len(arr)-1)]`)。 - **使用内置函数**:Python内置 `sorted()` 或 `list.sort()` 已在底层优化,推荐用于生产环境(它们基于Timsort,结合了快速排序和插入排序的优点)[^3]。 快速排序在Python中是基础排序算法之一,广泛应用于数据预处理和分析场景[^4]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值