首页 > 其他分享 >排序数组

排序数组

时间:2023-10-09 15:12:56浏览次数:30  
标签:nums int 复杂度 mid 数组 textit 排序

 

 

 

排序数组  数组 C++ Java Python

前言

本题你可以选择直接调用库函数来对序列进行排序,但意义不大。由于排序算法有很多,本文只介绍三种常见的基于比较的复杂度较低的排序。

方法一:快速排序

思路和算法

快速排序的主要思想是通过划分将待排序的序列分成前后两部分,其中前一部分的数据都比后一部分的数据要小,然后再递归调用函数对两部分的序列分别进行快速排序,以此使整个序列达到有序。

我们定义函数 randomized_quicksort(nums, l, r) 为对 nums 数组里 [l,r][l,r][l,r] 的部分进行排序,每次先调用 randomized_partition 函数对 nums 数组里 [l,r][l,r][l,r] 的部分进行划分,并返回分界值的下标 pos,然后按上述将的递归调用 randomized_quicksort(nums, l, pos - 1) 和 randomized_quicksort(nums, pos + 1, r) 即可。

那么核心就是划分函数的实现了,划分函数一开始需要确定一个分界值(我们称之为主元 pivot),然后再进行划分。而主元的选取有很多种方式,这里我们采用随机的方式,对当前划分区间 [l,r][l,r][l,r] 里的数等概率随机一个作为我们的主元,再将主元放到区间末尾,进行划分。

整个划分函数 partition 主要涉及两个指针 iii 和 jjj,一开始 i = l - 1j = l。我们需要实时维护两个指针使得任意时候,对于任意数组下标 kkk,我们有如下条件成立:

  1. l≤k≤il\leq k\leq il≤k≤i 时,nums[k]≤pivot\textit{nums}[k]\leq \textit{pivot}nums[k]≤pivot。

  2. i+1≤k≤j−1i+1\leq k\leq j-1i+1≤k≤j−1 时,nums[k]>pivot\textit{nums}[k]> \textit{pivot}nums[k]>pivot。

  3. k==rk==rk==r 时,nums[k]=pivot\textit{nums}[k]=\textit{pivot}nums[k]=pivot。

我们每次移动指针 jjj ,如果 nums[j]>pivot\textit{nums}[j]> pivotnums[j]>pivot,我们只需要继续移动指针 jjj ,即能使上述三个条件成立,否则我们需要将指针 iii 加一,然后交换 nums[i]\textit{nums}[i]nums[i] 和 nums[j]\textit{nums}[j]nums[j],再移动指针 jjj 才能使得三个条件成立。

当 jjj 移动到 r−1r-1r−1 时结束循环,此时我们可以由上述三个条件知道 [l,i][l,i][l,i] 的数都小于等于主元 pivot,[i+1,r−1][i+1,r-1][i+1,r−1] 的数都大于主元 pivot,那么我们只要交换 nums[i+1]\textit{nums}[i+1]nums[i+1] 和 nums[r]\textit{nums}[r]nums[r] ,即能使得 [l,i+1][l,i+1][l,i+1] 区间的数都小于 [i+2,r][i+2,r][i+2,r] 区间的数,完成一次划分,且分界值下标为 i+1i+1i+1,返回即可。

如下的动图展示了一次划分的过程,刚开始随机选了 444 作为主元,与末尾元素交换后开始划分:

fig1

C++ Java
class Solution {
    int partition(vector<int>& nums, int l, int r) {
        int pivot = nums[r];
        int i = l - 1;
        for (int j = l; j <= r - 1; ++j) {
            if (nums[j] <= pivot) {
                i = i + 1;
                swap(nums[i], nums[j]);
            }
        }
        swap(nums[i + 1], nums[r]);
        return i + 1;
    }
    int randomized_partition(vector<int>& nums, int l, int r) {
        int i = rand() % (r - l + 1) + l; // 随机选一个作为我们的主元
        swap(nums[r], nums[i]);
        return partition(nums, l, r);
    }
    void randomized_quicksort(vector<int>& nums, int l, int r) {
        if (l < r) {
            int pos = randomized_partition(nums, l, r);
            randomized_quicksort(nums, l, pos - 1);
            randomized_quicksort(nums, pos + 1, r);
        }
    }
public:
    vector<int> sortArray(vector<int>& nums) {
        srand((unsigned)time(NULL));
        randomized_quicksort(nums, 0, (int)nums.size() - 1);
        return nums;
    }
};
 

复杂度分析

  • 时间复杂度:基于随机选取主元的快速排序时间复杂度为期望 O(nlog⁡n)O(n\log n)O(nlogn),其中 nnn 为数组的长度。详细证明过程可以见《算法导论》第七章,这里不再大篇幅赘述。

  • 空间复杂度:O(h)O(h)O(h),其中 hhh 为快速排序递归调用的层数。我们需要额外的 O(h)O(h)O(h) 的递归调用的栈空间,由于划分的结果不同导致了快速排序递归调用的层数也会不同,最坏情况下需 O(n)O(n)O(n) 的空间,最优情况下每次都平衡,此时整个递归树高度为 log⁡n\log nlogn,空间复杂度为 O(log⁡n)O(\log n)O(logn)。

方法二:堆排序

预备知识

思路和算法

堆排序的思想就是先将待排序的序列建成大根堆,使得每个父节点的元素大于等于它的子节点。此时整个序列最大值即为堆顶元素,我们将其与末尾元素交换,使末尾元素为最大值,然后再调整堆顶元素使得剩下的 n−1n-1n−1 个元素仍为大根堆,再重复执行以上操作我们即能得到一个有序的序列。

如下两个动图展示了对 [4, 6, 8, 5, 9] 这个数组堆排序的过程:

fig2

fig3

C++ Java Python3
class Solution {
    void maxHeapify(vector<int>& nums, int i, int len) {
        for (; (i << 1) + 1 <= len;) {
            int lson = (i << 1) + 1;
            int rson = (i << 1) + 2;
            int large;
            if (lson <= len && nums[lson] > nums[i]) {
                large = lson;
            } else {
                large = i;
            }
            if (rson <= len && nums[rson] > nums[large]) {
                large = rson;
            }
            if (large != i) {
                swap(nums[i], nums[large]);
                i = large;
            } else {
                break;
            }
        }
    }
    void buildMaxHeap(vector<int>& nums, int len) {
        for (int i = len / 2; i >= 0; --i) {
            maxHeapify(nums, i, len);
        }
    }
    void heapSort(vector<int>& nums) {
        int len = (int)nums.size() - 1;
        buildMaxHeap(nums, len);
        for (int i = len; i >= 1; --i) {
            swap(nums[i], nums[0]);
            len -= 1;
            maxHeapify(nums, 0, len);
        }
    }
public:
    vector<int> sortArray(vector<int>& nums) {
        heapSort(nums);
        return nums;
    }
};
 

复杂度分析

  • 时间复杂度:O(nlog⁡n)O(n\log n)O(nlogn)。初始化建堆的时间复杂度为 O(n)O(n)O(n),建完堆以后需要进行 n−1n-1n−1 次调整,一次调整(即 maxHeapify) 的时间复杂度为 O(log⁡n)O(\log n)O(logn),那么 n−1n-1n−1 次调整即需要 O(nlog⁡n)O(n\log n)O(nlogn) 的时间复杂度。因此,总时间复杂度为 O(n+nlog⁡n)=O(nlog⁡n)O(n+n\log n)=O(n\log n)O(n+nlogn)=O(nlogn)。

  • 空间复杂度:O(1)O(1)O(1)。只需要常数的空间存放若干变量。

方法三:归并排序

思路

归并排序利用了分治的思想来对序列进行排序。对一个长为 nnn 的待排序的序列,我们将其分解成两个长度为 n2\frac{n}{2}2n​ 的子序列。每次先递归调用函数使两个子序列有序,然后我们再线性合并两个有序的子序列使整个序列有序。

算法

定义 mergeSort(nums, l, r) 函数表示对 nums 数组里 [l,r][l,r][l,r] 的部分进行排序,整个函数流程如下:

  1. 递归调用函数 mergeSort(nums, l, mid) 对 nums 数组里 [l,mid][l,\textit{mid}][l,mid] 部分进行排序。

  2. 递归调用函数 mergeSort(nums, mid + 1, r) 对 nums 数组里 [mid+1,r][\textit{mid}+1,r][mid+1,r] 部分进行排序。

  3. 此时 nums 数组里 [l,mid][l,\textit{mid}][l,mid] 和 [mid+1,r][\textit{mid}+1,r][mid+1,r] 两个区间已经有序,我们对两个有序区间线性归并即可使 nums 数组里 [l,r][l,r][l,r] 的部分有序。

    线性归并的过程并不难理解,由于两个区间均有序,所以我们维护两个指针 iii 和 jjj 表示当前考虑到 [l,mid][l,\textit{mid}][l,mid] 里的第 iii 个位置和 [mid+1,r][\textit{mid}+1,r][mid+1,r] 的第 jjj 个位置。

    如果 nums[i] <= nums[j] ,那么我们就将 nums[i]\textit{nums}[i]nums[i] 放入临时数组 tmp 中并让 i += 1 ,即指针往后移。否则我们就将 nums[j]\textit{nums}[j]nums[j] 放入临时数组 tmp 中并让 j += 1 。如果有一个指针已经移到了区间的末尾,那么就把另一个区间里的数按顺序加入 tmp 数组中即可。

    这样能保证我们每次都是让两个区间中较小的数加入临时数组里,那么整个归并过程结束后 [l,r][l,r][l,r] 即为有序的。

如下的动图展示了两个有序数组线性归并的过程:

fig4

函数递归调用的入口为 mergeSort(nums, 0, nums.length - 1),递归结束当且仅当 l >= r

C++ Java Python3
class Solution {
    vector<int> tmp;
    void mergeSort(vector<int>& nums, int l, int r) {
        if (l >= r) return;
        int mid = (l + r) >> 1;
        mergeSort(nums, l, mid);
        mergeSort(nums, mid + 1, r);
        int i = l, j = mid + 1;
        int cnt = 0;
        while (i <= mid && j <= r) {
            if (nums[i] <= nums[j]) {
                tmp[cnt++] = nums[i++];
            }
            else {
                tmp[cnt++] = nums[j++];
            }
        }
        while (i <= mid) {
            tmp[cnt++] = nums[i++];
        }
        while (j <= r) {
            tmp[cnt++] = nums[j++];
        }
        for (int i = 0; i < r - l + 1; ++i) {
            nums[i + l] = tmp[i];
        }
    }
public:
    vector<int> sortArray(vector<int>& nums) {
        tmp.resize((int)nums.size(), 0);
        mergeSort(nums, 0, (int)nums.size() - 1);
        return nums;
    }
};
 

复杂度分析

  • 时间复杂度:O(nlog⁡n)O(n\log n)O(nlogn)。由于归并排序每次都将当前待排序的序列折半成两个子序列递归调用,然后再合并两个有序的子序列,而每次合并两个有序的子序列需要 O(n)O(n)O(n) 的时间复杂度,所以我们可以列出归并排序运行时间 T(n)T(n)T(n) 的递归表达式:
T(n)=2T(n2)+O(n)T(n)=2T(\frac{n}{2})+O(n)T(n)=2T(2n​)+O(n)

​ 根据主定理我们可以得出归并排序的时间复杂度为 O(nlog⁡n)O(n\log n)O(nlogn)。

  • 空间复杂度:O(n)O(n)O(n)。我们需要额外 O(n)O(n)O(n) 空间的 tmp\textit{tmp}tmp 数组,
  • 且归并排序递归调用的层数最深为 log⁡2n\log_2 nlog2​n,所以我们还需要额外的 O(log⁡n)O(\log n )O(logn) 的栈空间,
  • 所需的空间复杂度即为 O(n+log⁡n)=O(n)O(n+\log n) = O(n)O(n+logn)=O(n)。

 

class Solution:
    def merge_sort(self, nums, l, r):
        if l == r:
            return
        mid = (l + r) // 2
        self.merge_sort(nums, l, mid)
        self.merge_sort(nums, mid + 1, r)
        tmp = []
        i, j = l, mid + 1
        while i <= mid or j <= r:
            if i > mid or (j <= r and nums[j] < nums[i]):
                tmp.append(nums[j])
                j += 1
            else:
                tmp.append(nums[i])
                i += 1
        nums[l: r + 1] = tmp

    def sortArray(self, nums: List[int]) -> List[int]:
        self.merge_sort(nums, 0, len(nums) - 1)
        return nums

 
链接:https://leetcode.cn/problems/sort-an-array/solutions/178305/pai-xu-shu-zu-by-leetcode-solution/ 

  

class Solution:
    def max_heapify(self, heap, root, heap_len):
        p = root
        while p * 2 + 1 < heap_len:
            l, r = p * 2 + 1, p * 2 + 2
            if heap_len <= r or heap[r] < heap[l]:
                nex = l
            else:
                nex = r
            if heap[p] < heap[nex]:
                heap[p], heap[nex] = heap[nex], heap[p]
                p = nex
            else:
                break
        
    def build_heap(self, heap):
        for i in range(len(heap) - 1, -1, -1):
            self.max_heapify(heap, i, len(heap))

    def heap_sort(self, nums):
        self.build_heap(nums)
        for i in range(len(nums) - 1, -1, -1):
            nums[i], nums[0] = nums[0], nums[i]
            self.max_heapify(nums, 0, i)
            
    def sortArray(self, nums: List[int]) -> List[int]:
        self.heap_sort(nums)
        return nums

 
链接:https://leetcode.cn/problems/sort-an-array/solutions/178305/pai-xu-shu-zu-by-leetcode-solution/
 

 

 

 

给你一个整数数组 nums,请你将该数组升序排列。

 

示例 1:

输入:nums = [5,2,3,1]
输出:[1,2,3,5]

示例 2:

输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

 

提示:

  • 1 <= nums.length <= 5 * 104
  • -5 * 104 <= nums[i] <= 5 * 104

 

 

 

 

 

 

 

 

 

 

 

 

 

 

标签:nums,int,复杂度,mid,数组,textit,排序
From: https://www.cnblogs.com/flyingsir/p/17751777.html

相关文章

  • 数据重整:用Java实现精准Excel数据排序的实用策略
    摘要:本文由葡萄城技术团队原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。前言在数据处理或者数据分析的场景中,需要对已有的数据进行排序,在Excel中可以通过排序功能进行整理数据。而在Java中,则可以借助Excel表格插件对数......
  • Python生成随机整数数组的实用方法
    在编程中,生成随机整数数组是一项非常常见的任务。本文将介绍如何使用Python语言来生成随机整数数组,帮助读者掌握这一有用的编程技巧。通过实际的代码示例,我们将逐步指导读者完成生成随机整数数组的过程,并提供一些实际应用的建议。第一部分:了解随机数生成原理1.什么是随机数:-随机数......
  • Map根据value排序取topN
    publicstaticvoidmain(String[]args){Map<String,Integer>map=newHashMap<>();/*for(inti=0;i<1000000;i++){intnextInt=newRandom().nextInt();map.put("A"+i,i*nextInt......
  • vue中的循环遍历对象、数组和字符串
    vue循环遍历对象、数组和字符串1.循环遍历对象1.1vue在html里面循环遍历对象v-for="(val,key,i)indimItemMap":key="key" val-每一项key-key值i-第几个<el-table-columnprop="score"label="评分":show-overflow-tooltip="true"ali......
  • 寻找两个正序数组的中位数
    /* *@lcapp=leetcode.cnid=4lang=cpp *@lcprversion=21917 * *[4]寻找两个正序数组的中位数 *///@lccode=startclassSolution{public:  doublefindMedianSortedArrays(vector<int>&nums1,vector<int>&nums2){    if(nums1.size()......
  • 快慢指针用于数组的原地处理
    删除指定元素27.移除元素删除有序数组的重复项26.删除有序数组中的重复项删除有序数组重复项超过K次的部分80.删除有序数组中的重复项II整体来说,这类题目所用的方法都是快慢指针,只是其实现细节不尽相同而已。对我来说,做这种题目最好自己在纸上写写,不然很容......
  • C#归并排序算法
    前言归并排序是一种常见的排序算法,它采用分治法的思想,在排序过程中不断将待排序序列分割成更小的子序列,直到每个子序列中只剩下一个元素,然后将这些子序列两两合并并排序,最终得到一个有序的序列。归并排序实现原理将待排序序列分割成两个子序列,直到每个子序列中只有一个元素。......
  • js数组转字符串方法(转)
    JavaScript 允许数组与字符串之间相互转换。其中Array 方法对象定义了3 个方法,可以把数组转换为字符串,如表所示。数组方法说明toString()将数组转换成一个字符串toLocalString()把数组转换成本地约定的字符串join()将数组元素连接起来以构建一个字符串......
  • vue $refs 获取的结果有时候是数组
    在工作的时候要从接口读取数据,生成一个动态的表单首先做的就是绑定ref然后使用const{proxy}=getCurrentInstance();来读取ref,看了半天数据怎么不对,控制台打印后,发现是一个数组后来观察到只要是使用v-for生成的获取ref时,即使没有重复,结果也是数组,可能是作者在v-for中为了......
  • 紫书Unit3.字符数组
    charc语言中字符型关键字用char表示,实际储存的是字符的ascll码。字符串是以‘\0’结尾。同时,字符常量可以用单引号表示,'a',在语法上可以将字符当作int使用,`'a'+1会输出98; scanf输入charscanf("%s",s),遇到空字符会停下来。 //3.5TEX中的引号,将特定符号转化//输入"To......