912. 排序数组

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

归并排序

public class Sort {
//归并排序
public static int[] MergeSort(int[] arr) {
int[] temp = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, temp);
return arr;
} private static void mergeSort(int[] arr, int left, int right, int[] temp) {
if (left >= right) {
return;
} int mid = (left + right) / 2;
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid + 1, right, temp); merge(arr, left, mid, right, temp);
} private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left, j = mid + 1;
int k = 0;
while(i <= mid && j <= right) {
if (arr[i] < arr[j]) { //改成 <= 就是稳定的
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
} //如果左边还有剩余
while(i <= mid) {
temp[k++] = arr[i++];
} //如果右边还有剩余
while(j <= right) {
temp[k++] = arr[j++];
} //将temp中的数据放回arr
k = 0;
for (int m = left; m <= right; m++) {
arr[m] = temp[k++];
}
}
}

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的。稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。推荐阅读:堆排序为什么不稳定_为什么要区分稳定和不稳定排序

时间复杂度:平均 O(nlogn) 最优 O(nlogn) 最差 O(nlogn)

空间复杂度:O(n) = max(O(logn)——递归栈深度, O(n)——临时数组)

是否稳定性算法: 是/否,取决于merge函数的实现。看merge是否会导致两个值相同的元素发生前后顺序的改变,现在的实现是不稳定的。(如:[ 1, 2, 3, 2] 。第一次合并:1和2、3和2合并有[1, 2, 2, 3];第二次合并:1, 2 和 2, 3合并得[1, 2, 2, 3],但已经交换了两个2的先后)

快速排序

public class Sort {
//快速排序
public static int[] QuickSort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
return arr;
} private static void quickSort(int[] arr, int left, int right) {
if (left >= right) {
return;
} int mid = partition(arr, left, right);
quickSort(arr, left, mid - 1);
quickSort(arr, mid + 1, right);
} private static Random random = new Random(47); private static int partition(int[] arr, int left, int right) {
int rand = left + random.nextInt(right - left + 1);
swap(arr, left, rand);
int pivot = arr[left]; while (left < right) {
// 右边找一个 小于等于 pivot 的元素
while (left < right && arr[right] > pivot) {
right--;
} // 找到了就将右边的元素放到左边
if (left < right) {
swap(arr, left, right);
left++;
} // 左边找一个 大于 pivot 的元素
while (left < right && arr[left] <= pivot) {
left++;
} if (left < right) {
swap(arr, left, right);
right--;
}
} //跳出循环时 left = right
arr[left] = pivot;
return left;
} private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
  • 归并排序是标准的分治法模板
  • 快速排序只有分没有治,因为在分之前就已经做好了要做的事

时间复杂度:平均 O(nlogn) 最优 O(nlogn) 最差O(n2)

空间复杂度:O(logn) —— 递归栈深度

是否稳定排序算法:否 (如:[ 4, 2, 3, 2] -> [2, 2, 3, 4])这里的 4 - 2 交换,导致原有的 2、2排序被打乱。

堆排序

堆:一种满足堆积性质的完全二叉树,需要满足如下性质:

  1. 堆中的某个节点的值总是大于等于或小于等于其父节点的值;

  2. 堆总是一颗完全二叉树;

public class Sort {
//堆排序
public static void HeapSort(int[] arr) {
buildMaxHeap(arr);
for (int i = arr.length - 1; i > 0; i--) {
swap(arr, 0, i); //将堆顶元素和最后一个叶子结点交换,最大值放到数组尾部
heapify(arr, 0, i); //对前i个元素构建新的大顶堆
}
} //构建大顶堆(从第一个非叶子结点从右至左,从下至上)
private static void buildMaxHeap(int[] arr) {
for (int i = arr.length / 2 - 1; i >= 0; i--) {
heapify(arr, i, arr.length);
}
} //以i为根的堆调整(大顶堆)
private static void heapify(int[]arr, int i, int length) {
int left = 2*i + 1;
int right = 2*i + 2; int largest = i; //假设根节点i为最大值 // 左子结点存在且大于根节点
if (left < length && arr[left] > arr[largest]) {
largest = left;
} // 右子结点存在且大于根节点
if (right < length && arr[right] > arr[largest]) {
largest = right;
} if (largest != i) { //说明最大值为其子结点
swap(arr, largest, i);
heapify(arr, largest, length); //交换过后再调整其子结点为根的堆
}
} private static void swap(int[]arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}

时间复杂度:平均 O(nlogn) 最优 O(nlogn) 最差 O(nlogn)

空间复杂度:O(logn) - 递归堆栈

是否稳定排序算法:否。比如:3 27 36 27(小顶堆),如果堆顶3先输出,则,第三层的27(最后一个27)跑到堆顶,然后堆稳定,继续输出堆顶,是刚才那个27,这样说明后面的27先于第二个位置的27输出,不稳定。

排序结果测试类

public class Test {
private static final int num = 8000000;
private static int[] arr = new int[num]; private static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH::mm:ss"); private static void generateArr() {
//给相同的种子使得每次生成的伪随机数序列相同
Random random = new Random(47);
for (int i = 0; i < num; i++) {
arr[i] = random.nextInt(num);
}
} private static boolean isOrdered(int[] arr) {
boolean order = true;
for (int i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i + 1]) {
order = false;
break;
}
}
return order;
} public static void test(SortI sort) {
generateArr();
Date startDate = new Date();
System.out.println("start : " + formatter.format(startDate)); sort.sort(arr); Date endDate = new Date();
System.out.println("end : " + formatter.format(endDate));
System.out.println("排序正确性:" + isOrdered(arr));
} public static void main(String[] args) {
Test.test(Sort::MergeSort);
Test.test(Sort::QuickSort);
Test.test(Sort::HeapSort);
} } interface SortI {
void sort(int[] arr);
}

LeetCode入门指南 之 排序的更多相关文章

  1. LeetCode入门指南 之 链表

    83. 删除排序链表中的重复元素 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 .返回同样按升序排列的结果链表. class Soluti ...

  2. LeetCode入门指南 之 回溯思想

    模板 result = {} void backtrack(选择列表, 路径) { if (满足结束条件) { result.add(路径) return } for 选择 in 选择列表 { 做选择 ...

  3. LeetCode入门指南 之 二分搜索

    上图表示常用的二分查找模板: 第一种是最基础的,查找区间左右都为闭区间,比较后若不等,剩余区间都不会再包含mid:一般在不需要确定目标值的边界时,用此法即可. 第二种查找区间为左闭右开,要确定targ ...

  4. LeetCode入门指南 之 二叉树

    二叉树的遍历 递归: void traverse (TreeNode root) { if (root == null) { return null; } //前序遍历位置 traverse(root ...

  5. LeetCode入门指南 之 栈和队列

    栈 155. 最小栈 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈. push(x) -- 将元素 x 推入栈中. pop() -- 删除栈顶的元素. top( ...

  6. LeetCode入门指南 之 动态规划思想

    推荐学习labuladong大佬的动态规划系列文章:先弄明白什么是动态规划即可,不必一次看完.接着尝试自己做,没有思路了再回过头看相应的文章. 动态规划一般可以由 递归 + 备忘录 一步步转换而来,不 ...

  7. 【翻译】Fluent NHibernate介绍和入门指南

    英文原文地址:https://github.com/jagregory/fluent-nhibernate/wiki/Getting-started 翻译原文地址:http://www.cnblogs ...

  8. 一起学微软Power BI系列-官方文档-入门指南(4)Power BI的可视化

    在前面的系列文章中,我们介绍了官方有关获取数据,以及建模的原始文档和基本介绍.今天继续给大家介绍官方文档中,有关可视化的内容.实际上获获取数据和建模更注重业务关系的处理,而可视化则关注对数据的解读.这 ...

  9. AngularJS快速入门指南20:快速参考

    thead>tr>th, table.reference>tbody>tr>th, table.reference>tfoot>tr>th, table ...

随机推荐

  1. 20204107 孙嘉临《Python程序设计》实验一报告

    课程:<python程序设计> 班级:2041 姓名:孙嘉临 学号:20204107 实验教师:王志强 实验日期:2021年4月12日 必修/选修:公选课 ##一.实验内容 1.熟悉Pyt ...

  2. mysql的主从复制延迟问题--看这一篇就够了

    ​ 在之前我们已经讲解了一主一从,双主双从的mysql集群搭建,在单机应用的时候看起来没有问题,但是在企业的生产环境中,在很多情况下都会有复制延迟的问题. ​ 主从复制的原理我们在此处就不再赘述了,之 ...

  3. Linux:Linux安装配置JDK1.8

    1  在/usr/local   文件夹下新建一个文件夹software ,将JDK放到此文件夹中 并在此文件夹下解压执行命令  tar  zxvf  jdk-8u144-linux-x64.tar. ...

  4. linux 退出状态码

    状态码 描述 0 命令成功结束 1 一般性未知错误 2 不适合的shell 命令 123 命令不可执行 127 没找到命令 128 无效退出参数 128+x 与linux信号x相关的严重错误 130 ...

  5. 在Intellij IDEA中新建Web项目

    1.点击左上角 文件(F) ,--> new  --> 项目 2.勾选下面的复选框,下一步就是给项目起名字和存放项目的位置 2.在Web文件下新建 clsses 和 lib文件夹:http ...

  6. buu pyre

    一.下载附件是是pyc的字节码文件,找个在线网站反编译一下 思路还是挺清晰: 先逆着求出code, 这里就是求余,有点麻烦,那个+128%128其实没啥用的,省略就好了 算法里面再处理一下细节,跑一下 ...

  7. Leetcode No.88 Merge Sorted Array(c++实现)

    1. 题目 1.1 英文题目 You are given two integer arrays nums1 and nums2, sorted in non-decreasing order, and ...

  8. 深入理解Java容器——HashMap

    目录 存储结构 初始化 put resize 树化 get 为什么equals和hashCode要同时重写? 为何HashMap的数组长度一定是2的次幂? 线程安全 参考 存储结构 JDK1.8前是数 ...

  9. 性能基准DevOps之如何提升脚本执行效率

    1.宝路说 宝路最近一直在自我思考:性能基准DevOps工作已经开展一段时间了,目前我们确实已经取得了一些成果,显然这还远远不够.趁闲暇之余跟组员进行了简单的头脑风暴!于是这就有了今天的主题,当然这仅 ...

  10. ZooKeeper 分布式锁 Curator 源码 03:可重入锁并发加锁

    前言 在了解了加锁和锁重入之后,最需要了解的还是在分布式场景下或者多线程并发加锁是如何处理的? 并发加锁 先来看结果,在多线程对 /locks/lock_01 加锁时,是在后面又创建了新的临时节点. ...