~~~~~~ 排序算法作为一项需求,它足够简单,是学习基础算法思想(例如:分治算法、减治思想、递归写法)的很好的学习材料。
参考:leetcode一位很棒的题解大佬
链接: https://leetcode.cn/problems/sort-an-array/solution/fu-xi-ji-chu-pai-xu-suan-fa-java-by-liweiwei1419/
1、选择排序(了解)
思路:每一轮选取未排定的部分中最小的部分交换到未排定部分的最开头。
// 1 选择排序 : 每次找出"i~n"最小的元素,放到最前面
void select_sort(int arr[],int len)
{
for(int i=0;i<len;i++)
{
int min_th = i;
for(int j=i+1;j<len;j++)
{
if(arr[j]<arr[min_th])
min_th = j;
}
int temp = arr[i];
arr[i] = arr[min_th];
arr[min_th] = temp;
}
}
复杂度分析:
时间复杂度:O( N 2 N^2 N2),这里 N 是数组的长度;
空间复杂度:O(1),使用到常数个临时变量。
2、插入排序(熟悉)
思路:每次将一个数字插入一个有序的数组里,成为一个长度更长的有序数组,有限次操作以后,数组整体有序(打扑克理牌的原理)。
~~~~~~
由于「插入排序」在几乎有序的数组上表现良好,特别地,在短数组上的表现也很好。因为短数组的特点是:每个元素离它最终排定的位置都不会太远。为此,在小区间内执行排序任务的时候,可以转向使用「插入排序」
// 3 插入排序 : 打扑克理牌
void insert_sort(int arr[],int len)
{
for(int i=1;i<len;i++)
{
int value = arr[i]; //一定要设置变量储存arr[i]的初始值
int preIndex = i-1; //用于寻找value的插入位置
while(preIndex>=0 && arr[preIndex]>value)
{
arr[preIndex+1] = arr[preIndex];
preIndex--;
}
arr[preIndex+1] = value;
}
}
插入排序有两个关键变量:
~~~~
①value值为当前摸到的扑克牌
~~~~
②preIndex值为已排序的最后一个扑克牌的位置索引,即 i-1.
复杂度分析:
时间复杂度:O( N 2 N^2 N2),这里 N 是数组的长度;
空间复杂度:O(1),使用到常数个临时变量。
3、冒泡排序(了解)
// 2 冒泡排序 : 每次检查相邻两个元素,每次找出最大的数放在最后
void bubble_sort(int arr[],int len)
{
for(int i=0;i<len-1;i++)
{
for(int j=0;j<len-1-i;j++)
{
if(arr[j]>arr[j+1])
{
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
}
4、归并排序(重点)
思路:分治,借助额外空间,合并两个有序数组,得到更长的有序数组。(理解递归过程的非常好的学习材料,重点掌握)
归并排序是建立了额外的数组空间tmp,用来储存合并两个有序数组的结果。那么我们如何得到左右两个有序数组呢?是用递归的思想,递归的结束条件是左边界大于等于右边界,即分解到只有1个元素时结束。
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++];
}
// 将额外空间中的结果返回给nums
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(NlogN),这里 N是数组的长度;
空间复杂度:O(N),辅助数组与输入数组规模相当。
5、快速排序(重点)
思路:快速排序每一次都排定一个元素——基准数(这个元素呆在了它最终应该呆的位置),然后递归地去排它左边的部分和右边的部分,依次进行下去,直到数组有序。
具体实现过程(下图借用链接作者的图):参考 快速排序过程详解
// 6 快速排序
void quick_sort(int s[], int l, int r)
{
if(l>=r)
return ;
//Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 :以中间数作为基准数
int i = l, j = r, x = s[l];
while (i < j)
{
while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
j--;
while(i < j && s[i] <= x) // 从左向右找第一个大于x的数
i++;
if(i < j)
{
int temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
int temp = s[l]; //这里一定是s[l]而不是x
s[l] = s[i];
s[i] = temp;
quick_sort(s, l, j - 1); // 递归调用
quick_sort(s, j + 1, r);
}
注意:第二个交换中,交换的变量是s[l]而不是x。
复杂度分析:
时间复杂度:O(NlogN),这里 N 是数组的长度;
空间复杂度:O(logN),这里占用的空间主要来自递归函数的栈空间。
注:(针对特殊测试用例:顺序数组或者逆序数组)一定要随机化选择切分元素,否则在输入数组是有序数组或者是逆序数组的时候,快速排序会变得非常慢(等同于冒泡排序or选择排序)
6、堆排序(根据个人情况掌握)
后续再补充吧。