文章目录
📕术语
时间复杂度
描述算法的运行时间
空间复杂度
描述算法运行过程中,临时占用存储空间大小的量度
排序方式
描述是否额外占用内存
In-place:不占用额外内存
Out-place:占用额外内存
🌳题目
请用下面的方法实现 912. 排序数组 - 力扣 这个题目。
📕冒泡排序
Bubble Sort
重复地访问要排序的元素组,依次比较元素组里面两个相邻的元素。当升序排序时,如果后一个元素小于前一个元素(数值从小到大,字符串从A到Z)那么就交换这两个元素,重复这个操作直至元素组里面所有元素按升序进行排列。
💡代码实现
冒泡排序做这个题目 912. 排序数组 - 力扣 会引起超出时间限制的错误。
class Solution {
public int[] sortArray(int[] nums) {
int temp;
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < nums.length; j++) {
if(nums[i]<nums[j]){
temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
}
return nums;
}
}
📕选择排序
Selection Sort
每次从未排序的数据表中选出最小的元素,存放在序列的前面(从小到大)或后面(从大到小),对整个数据表重复上面操作直至未排序的数据表的元素为零。
选择排序和冒泡排序算法的差异性在于:
- 选择排序:选择未排序的数据表最小值
- 冒泡排序:比较数据表中相邻数据的值
💡代码实现
选择排序做这个题目 912. 排序数组 - 力扣 可以正常通过。
class Solution {
public int[] sortArray(int[] nums) {
int temp;
int index;
for(int i=0; i<nums.length;i++){
// 无法删除数组的元素
// 因此将最小元素的索引进行更新
temp = nums[i];
index = i;
for(int j=i+1;j<nums.length;j++){
if(temp>nums[j]){
temp = nums[j];
index = j;
}
}
temp = nums[i];
nums[i] = nums[index];
nums[index] = temp;
}
return nums;
}
}
📕插入排序
Insertion Sort
在已排序的数据表里面插入新的数据,生成一个新的、索引增1的有序表。
前提条件
- 数据表的数据已经排好了顺序
💡代码实现
插入排序做这个题目 912. 排序数组 - 力扣 可以正常通过。
class Solution {
public int[] sortArray(int[] nums) {
/** 前提工作
* 将数据表nums的最后一位提取出来
* 将其他没有排序的元素进行排序
* 在已排序的数据表里面插入一开始提取的元素
*/
int insertion = nums[nums.length-1];// 待插入的数据
List<Integer> list1 = new ArrayList<Integer>();
for(int i=0;i<nums.length-1;i++){
list1.add(nums[i]);
}
Collections.sort(list1);
nums = list1.stream().mapToInt(Integer::valueOf).toArray();
list1 = null;
/** 插入排序
* 首先要知道insertion插入到list的索引位置
* 然后按顺序往后移
*/
int index = 0;
int[] list = new int[nums.length+1];
for(int i=0;i<nums.length;i++){
if(insertion<=nums[i]){
index = i;
break;
}else list[i] = nums[i];
if(i==nums.length-1&&insertion>nums[i])index = i+1;
}
list[index] = insertion;
for(int i=index+1;i<list.length;i++)list[i] = nums[i-1];
return list;
}
}
可能你觉得代码量比较多,那是因为需要对无序数据表做排序,才能使用插入排序
算法。
实际上代码只有下面那么多,其他代码都是对无序数据表做有序排列的操作。
int index = 0;
int[] list = new int[nums.length+1];
for(int i=0;i<nums.length;i++){
if(insertion<=nums[i]){
index = i;
break;
}else list[i] = nums[i];
if(i==nums.length-1&&insertion>nums[i])index = i+1;
}
list[index] = insertion;
for(int i=index+1;i<list.length;i++)list[i] = nums[i-1];
return list;
📕希尔排序
Shell‘s Sort
缩小增量排序(Diminishing Increment Sort)
插入排序的一种更高效的改进版本
个人感觉更像冒泡排序的一种
根据设置的增量对元素进行分组,再在分组里面使用直接插入排序对分组里面的元素进行排序,通过不断缩小增量重复上面操作直至所有元素排序成功。
例如:
原始数组 a:[8,9,1,7,2,3,0]
初始增量 g:a.length / 2 = 4
第一次分组:[8,2]、[9,3]、[1,0]、[7]
第一次排序:[2,8]、[3,9]、[0,1]、[7]
提取每个分组的第一位数,再去提取每个分组的第二位数,总元素个数为奇数时,最后一个分组的元素紧接着第一次提取完的后面。
第一次排序后的数组:[2,3,0,7,8,9,1]
第二次增量 g:g / 2 = 2
第二次分组:[2,0,8]、[3,7,9]、[1]
第二次排序:[0,2,8]、[3,7,9]、[1]
第二次排序后的数组:[0,3,1,2,7,8,9]
第三次增量 g:g / 2 = 1
第二次分组:[0,3,1,2,7,8,9]
第三次排序后的数组:[0,1,2,3,7,8,9]
💡代码实现
希尔排序做这个题目 912. 排序数组 - 力扣 超出时间限制。
class Solution {
public int[] sortArray(int[] nums) {
int temp = 0;
int len = nums.length;
// 分组次数
for(int g=len/2;g>0;g/=2){
// 分组数量
for(int i=0;i<g;i++){
// 分组元素数量
for(int j=0;j<len/g-1;j++){
for(int k=j+1;k<len/g;k++){
if(nums[j*g+i]>nums[k*g+i]){
temp = nums[k*g+i];
nums[k*g+i] = nums[j*g+i];
nums[j*g+i] = temp;
}
}
}
}
}
return nums;
}
}
在网上找到一个方法可以使希尔排序通过这个题目 912. 排序数组 - 力扣(LeetCode) (leetcode-cn.com) 。
class Solution {
public int[] sortArray(int[] nums) {
// 分组次数
for(int gap=nums.length/2;gap>0;gap/=2){
//从第gap个元素,逐个对其所在组进行直接插入排序操作
for(int i=gap;i<nums.length;i++){
int j = i;
int temp = nums[j];
if(nums[j]<nums[j-gap]){
while(j-gap>=0 && temp<nums[j-gap]){
//移动法
nums[j] = nums[j-gap];
j-=gap;
}
nums[j] = temp;
}
}
}
return nums;
}
}
📕归并排序
Merge Sort
采用分治法
利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略。
【分治策略】:将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案整合在一起,即分而治之。
💡代码实现
归并排序做这个题目912. 排序数组 - 力扣。
还没看透
📕快速排序
Quick Sort
采用分治法
对冒泡排序的改进
说什么来什么又是分治法头疼
💡代码实现
快速排序做这个题目912. 排序数组 - 力扣
📕堆排序
Heap Sort
利用堆这种数据结构而设计的一种排序算法,堆是一个近似完全二叉树的结构,并同时满足堆积的性质:子结点的键值或索引总是小于或大于它的父节点。
原始数组:【8,9,1,7,2,3,0】
通过上面的树图,我们可以用 2 的次幂来计算每一层对应的元素个数。
2
0
2^0
20:代表第一层有一个元素
2
1
2^1
21:代表第二层有两个元素
2
2
2^2
22:代表第三层有四个元素
总元素的个数公式为:
f
(
n
)
=
2
n
−
1
n
>
0
f(n)=2^{n}-1 \qquad \text {$n>0$}
f(n)=2n−1n>0
例如:n = 3 代表有三层,那么用上面的公式可以计算出最多元素容量为 f ( 3 ) = 2 3 − 1 = 7 f(3)=2^{3}-1=7 f(3)=23−1=7。
那么如何通过索引来交换树图上面的元素(这里指对应结点下一层子结点的元素交换)呢?
以当前结点的元素 1 与当前结点下一层子结点的元素 3 进行交换,它们在数组里面对应的索引为 2 和 5。
这里关键一点是如何通过当前结点的索引去获取下一层的结点的索引?
其实这个问题很好解决,只要你仔细观察就会发现,当前结点的子左结点是当前索引的 2 倍加上 1,那么很自然地就知道子右节点的索引是当前索引的 2 倍加上 2。
f ( n ) = 2 ∗ ( n + 1 ) − k k = [ 0 ∣ 1 ] f(n)= 2*(n+1) - k \qquad \text {$k=[0|1]$} f(n)=2∗(n+1)−kk=[0∣1]
当 k 等于 0 时,代表获取子右结点的索引;当 k 等于 1 时,代表获取子左结点的索引。
当你理解上面的知识点,那么下面介绍推排序的算法就会比较容易理解。
💡大顶堆(升序)
经过排序最大值会冒泡到最顶层,然后将最大值与末尾元素进行交换脱离当前大顶堆,如上图所示。
如果你仔细观察会发现父结点大于子结点(不需要考虑子左结点和子右结点的大小关系),这就是大顶堆的特性。
通过大顶堆的特性只需要将大顶堆最顶层的元素与大顶堆最后一个元素进行交换,即可得到一个升序的数组。
后续步骤如图所示:
小顶堆的特性:父结点小于子结点(不需要考虑子左结点和子右结点的大小关系)。
💡代码实现
堆排序做这个题目912. 排序数组 - 力扣
📕计数排序
Counting Sort
用数组来统计待排序数组里面每种数字出现的次数,然后按照大小顺序将其依次放回原数组。
💡代码实现
计数排序做这个题目912. 排序数组 - 力扣显然是没法通过的,因为它不支持负数和小数。
class Solution {
public int[] sortArray(int[] nums) {
// 计数排序
int max = -50000;
for(int i=0;i<nums.length;i++){
if(nums[i]>max){
max = nums[i];
}
}
int[] arr = new int[max+1];
for(int i=0;i<nums.length;i++){
++arr[nums[i]];
}
int count=0;
for(int i=0;i<arr.length;i++){
if(arr[i]>0){
while(arr[i]-->0){
nums[count]=i;
++count;
}
}
}
return nums;
}
}
📕桶排序
Bucket Sort
将数组放到对应的桶子里,每个桶子里面的元素再个别排序(也可以再分)。
💡代码实现
桶排序做这个题目912. 排序数组 - 力扣
📕基数排序
Radix Sort
💡代码实现
基数排序做这个题目912. 排序数组 - 力扣
📕算法时间和空间复杂度表
排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 排序方式 | 稳定性 |
---|---|---|---|---|---|---|
冒泡排序 | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | In-place | 稳定 |
选择排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | In-place | 不稳定 |
插入排序 | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | In-place | 稳定 |
希尔排序 | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g 2 n ) O(n log^2 n) O(nlog2n) | O ( n l o g 2 n ) O(n log^2 n) O(nlog2n) | O ( 1 ) O(1) O(1) | In-place | 不稳定 |
归并排序 | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g n ) O(n log n) O(nlogn) | O ( n ) O(n) O(n) | Out-place | 稳定 |
快速排序 | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g n ) O(n log n) O(nlogn) | O ( n 2 ) O(n^2) O(n2) | O ( l o g n ) O(log n) O(logn) | In-place | 不稳定 |
堆排序 | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g n ) O(n log n) O(nlogn) | O ( n l o g n ) O(n log n) O(nlogn) | O ( 1 ) O(1) O(1) | In-place | 不稳定 |
计数排序 | O ( n + k ) O(n + k) O(n+k) | O ( n + k ) O(n + k) O(n+k) | O ( n + k ) O(n + k) O(n+k) | O ( k ) O(k) O(k) | Out-place | 稳定 |
桶排序 | O ( n + k ) O(n + k) O(n+k) | O ( n + k ) O(n + k) O(n+k) | O ( n 2 ) O(n^2) O(n2) | O ( n + k ) O(n + k) O(n+k) | Out-place | 稳定 |
基数排序 | O ( n ∗ k ) O(n * k) O(n∗k) | O ( n ∗ k ) O(n * k) O(n∗k) | O ( n ∗ k ) O(n * k) O(n∗k) | O ( n + k ) O(n + k) O(n+k) | Out-place | 稳定 |
注:log 基数为2
如果你是无意刷到这篇文章并看到这里,希望你给我的文章来一个赞赞👍👍。如果你不同意其中的内容或有什么问题都可以在下方评论区留下你的想法或疑惑,谢谢你的支持!!😀😀