NC+LC:数组排序
LC 912.排序数组:https://leetcode-cn.com/problems/sort-an-array/
使用Arrays工具类
这是最简单的方法,只需一行代码就可解决,但Arrays.sort()使用的并不是单一的排序,而是插入排序,快速排序,归并排序三种排序的组合,会在不同的情况使用不同的排序算法
class Solution { public int[] sortArray(int[] nums) { if(nums.length == 0){ return nums; } Arrays.sort(nums); return nums; } }
冒泡排序
- 设置了一个排序标记,可避免对已排序的数组进行重复比较
- 注意:冒泡排序的时间复杂度为
,在本道题中使用冒泡排序是会超出时间限制的,但我们还是需要掌握这种方法
import java.util.*; public class Solution { public int[] MySort (int[] arr) {//冒泡排序 // write code here boolean isSorted = true;//排序标记 for(int i = 0; i<arr.length - 1; i++){ int temp = 0; for(int j = 0; j<arr.length - i - 1; j++){ if(arr[j] > arr[j+1]){ temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; isSorted = false;//有交换说明还不是有序的 } } if(isSorted){//判断是否已有序,若已有序就可以直接退出循环,不用再进行下面的比较 break; } } return arr; } }优化
如数组{3,4,2,1,5,6,7,8}这种情况,前半段无序,后半段有序,可以在每一轮排序后,记录下来最后一次元素交换的位置,该位置即为无序数列的边界,再往后就是有序区了,以此减少无谓的比较。
class Solution { public int[] sortArray(int[] nums) { if(nums.length == 0){ return nums; } boolean isSorted = true; //记录当前数组是否有序 int lastExchangeIndex = 0; //最后一次交换的位置 int sortBorder = nums.length - 1; //无序边界,每次只需比较到这里 for(int i = 0; i < nums.length - 1; i++){ int temp = 0; for(int j = 0; j < sortBorder; j++){ if(nums[j] > nums[j+1]){ temp = nums[j]; nums[j] = nums[j+1]; nums[j+1] = temp; isSorted = false; //有交换说明还不是有序的 lastExchangeIndex = j; } } sortBorder = lastExchangeIndex; if(isSorted){ //判断是否已有序 break; } } return nums; } }复杂度分析
时间复杂度为
,空间复杂度为)
插入排序
思路
在给定数组上进行移动插入操作,先暂存当前元素,然后循环遍历数组前面的元素,注意边界
,若前面的元素大于当前元素,就往右移动(这时当前元素的位置就会被覆盖,前面自然就会空出一个位置),直到找到一个当前元素能够插入的位置
「插入排序」可以提前终止内层循环(体现在 nums[j - 1] > temp 不满足时),在数组「几乎有序」的前提下,「插入排序」的时间复杂度可以达到)
参考资料:https://leetcode-cn.com/problems/sort-an-array/solution/fu-xi-ji-chu-pai-xu-suan-fa-java-by-liweiwei1419/
实现代码
class Solution { public int[] sortArray(int[] nums) { //插入排序 int len = nums.length; for(int i = 1; i < len; i++){ int temp = nums[i]; //暂存当前元素 int j = i; //循环判断前面的元素,若大于当前元素就往右移,直到找到一个当前元素能够插入的位置 while(j > 0 && nums[j - 1] > temp){ nums[j] = nums[j - 1]; j--; } nums[j] = temp; } return nums; } }
复杂度分析
- 时间复杂度:
,这里
是数组的长度;
- 空间复杂度:
,使用到常数个临时变量。
快速排序
由冒泡排序演化而来,也是通过比较来交换元素实现排序,使用了分治法,重点是基准元素的选择和元素的交换
基准元素的选择:可以选择数组的第一个元素,或者随机选择一个元素再与第一个元素进行交换,都有可能选择到整个数组的最大值或最小值,无法达到分治的效果,所以最坏时间复杂度是
;
元素的交换:分为双边循环法和单边循环法
双边循环法
思路
- 设置两个指针
和
,初始分别指向数组头尾
- 从
所指元素开始,若大于或等于基准元素pivot,则指针
向左移动,若小于pivot则停止移动,转换到对
进行操作;
- 将指针
所指元素与pivot进行比较,若小于或等于则指针向右移动,若大于则停止移动,将
和
所指元素进行交换
- 以此类推,当
等于
时,将
所指元素与pivot进行交换,以此完成了一次分治。
实现代码
class Solution { public int[] sortArray(int[] nums) { if(nums.length == 0){ return nums; } quickSort(nums, 0, nums.length - 1); return nums; } public void quickSort(int[] nums, int start, int end){ //快速排序 if(start >= end){ return; } int i = new Random().nextInt(end - start + 1) + start; //随机选择一个位置 swap(nums, i, start); //将其与开始位置进行元素交换 int pivot = nums[start]; //确定基准元素 int left = start; int right = end; while(left != right){ while(left < right && nums[right] > pivot){ right--; } while(left < right && nums[left] <= pivot){ left++; } if(left < right){ swap(nums, left, right); } } nums[start] = nums[left]; nums[left] = pivot; int pivotIndex = left; //获得基准元素下标 quickSort(nums, start, pivotIndex-1); //对左边再进行分治 quickSort(nums, pivotIndex+1, end); //对有边再进行分治 } public void swap(int[] nums, int i, int j) { //交换元素 int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } }
单边循环法
思路
- 设置一个
指针指向数组起始位置,这个
指针代表小于基准元素的区域边界
- 遍历数组与基准元素pivot进行比较,若大于则继续遍历,若小于则需要做两件事:
- 第一,
指针右移1位,因为小于pivot的区域边界增大了1;
- 第二,让最新遍历到的元素和
指针所在位置的元素交换位置,因为最新遍历的元素归属于小于pivot的区域
- 以此类推直到遍历到最后一个元素,最后将
所指元素与pivot进行交换,以此完成了以此分治。
实现代码
//单边循环法 public int partition(int[] arr,int startIndex,int endIndex){//分治 int pivot = arr[startIndex]; int mark = startIndex; for(int i = startIndex+1; i<=endIndex; i++){ if(arr[i] < pivot){ mark++; int tmp = arr[i]; arr[i] = arr[mark]; arr[mark] = tmp; } } arr[startIndex] = arr[mark]; arr[mark] = pivot; return mark; }
复杂度分析
时间复杂度为归并排序
思路
两个核心操作是分治和将两个有序序列合并成一个有序序列(无论这个序列是顺序存储还是链式存储),即(递归+合并)
实现代码
class Solution { int[] tmp; //保存合并排序的结果 public int[] sortArray(int[] nums) { tmp = new int[nums.length]; mergeSort(nums, 0, nums.length - 1); return nums; } public void mergeSort(int[] nums, int l, int r){ //归并排序 //递归分治 if(l >= r){ return; } int mid = (l + r) / 2; mergeSort(nums, l, mid); mergeSort(nums, mid+1, r); //将左右两边进行有序合并 int left = l; int right = mid + 1; int index = 0; while(left <= mid && right <= r){ if(nums[left] < nums[right]){ tmp[index++] = nums[left++]; }else{ tmp[index++] = nums[right++]; } } while(left <= mid){ tmp[index++] = nums[left++]; } while(right <= r){ tmp[index++] = nums[right++]; } //将该区间的排序结果重新赋给nums for (int k = 0; k < r - l + 1; k++) { nums[k + l] = tmp[k]; } } }
复杂度分析
- 时间复杂度:
。
- 空间复杂度:
。