1 冒泡排序 比较相邻元素,如果第一个比第二个大,则交换
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
// bubbleSort
int n = nums.size();
for (int i = 0; i < n - 1; ++i) {
bool flag = false;
for (int j = 0; j < n - 1 - i; ++j) {
if (nums[j] > nums[j + 1]) {
swap(nums[j], nums[j + 1]);
flag = true;//一旦进入循环,说明需要排序
}
}
if (flag == false) break; //无交换,代表当前序列已经最优
}
return nums;
}
};
2、选择排序(了解)
思路:每一轮选取未排定的部分中最小的部分交换到未排定部分的最开头,经过若干个步骤,就能排定整个数组。即:先选出最小的,再选出第 2 小的,以此类推
import java.util.Arrays;
public class Solution {
// 选择排序:每一轮选择最小元素交换到未排定部分的开头
public int[] sortArray(int[] nums) {
int len = nums.length;
// 循环不变量:[0, i) 有序,且该区间里所有元素就是最终排定的样子
for (int i = 0; i < len - 1; i++) {
// 选择区间 [i, len - 1] 里最小的元素的索引,交换到下标 i
int minIndex = i;
for (int j = i + 1; j < len; j++) {
if (nums[j] < nums[minIndex]) {
minIndex = j;
}
}
swap(nums, i, minIndex);
}
return nums;
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
public static void main(String[] args) {
int[] nums = {5, 2, 3, 1};
Solution solution = new Solution();
int[] res = solution.sortArray(nums);
System.out.println(Arrays.toString(res));
}
}
总结:
算法思想 1:贪心算法:每一次决策只看当前,当前最优,则全局最优。注意:这种思想不是任何时候都适用。
算法思想 2:减治思想:外层循环每一次都能排定一个元素,问题的规模逐渐减少,直到全部解决,即「大而化小,小而化了」。运用「减治思想」很典型的算法就是大名鼎鼎的「二分查找」。
优点:交换次数最少。
「选择排序」看起来好像最没有用,但是如果在交换成本较高的排序任务中,就可以使用「选择排序」
3、插入排序(熟悉)
思路:每次将一个数字插入一个有序的数组里,成为一个长度更长的有序数组,有限次操作以后,数组整体有序。
class Solution {
public int[] sortArray(int[] nums) {
//注意 i 的初始值为 1,也就是第二个元素开始
for (int i = 1; i < nums.length; ++i) {
//待排序的值
int temp = nums[i];
//需要注意
int j;
for (j = i-1; j >= 0; --j) {
//找到合适位置
if (temp < nums[j]) {
nums[j+1] = nums[j];
continue;
}
//跳出循环
break;
}
//插入到合适位置,这也就是我们没有在 for 循环内定义变量的原因
nums[j+1] = temp;
}
return nums;
}
}
直接插入排序时间复杂度分析
最好情况时,也就是有序的时候,我们不需要移动元素,每次只需要比较一次即可找到插入位置,那么最好情况时的时间复杂度为O(n)。
最坏情况时,即待排序表是逆序的情况,则此时需要比较2+3+....+n = (n+2)(n-1)/2,移动次数也达到了最大值,3 +4+5+....n+1 = (n+4)(n-1)/2,时间复杂度为O(n^2).
我们每次插入一个数据的时间复杂度为O(n),那么循环执行 n 次插入操作,平均时间复杂度为O(n^2)。
直接插入排序空间复杂度分析
根据动画可知,插入排序不需要额外的存储空间,所以其空间复杂度为O(1)
直接插入排序稳定性分析
我们根据代码可知,我们只会移动比 temp 值大的元素,所以我们排序后可以保证相同元素的相对位置不变。所以直接插入排序为稳定性排序算法。
4 快速排序
1随机选取一个数(x = rand() % len + startIndex)作为基准;
2.让其他比它大的元素移动到数列一边,比他小的元素移动到数列另一边,从而把数组拆解成两个部分。
3.再对左右区间重复第二步,直到各区间只有一个数。
上图则为一次快排示意图,下面我们再利用递归,分别对左半边区间也就是 [3,1,2] 和右半区间 [7,6,5,8] 执行上诉过程,直至区间缩小为 1 也就是第三条,则此时所有的数据都有序。
代码:
public static void quickSort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
/**
* @param arr 排序数组
* @param left 左哨兵
* @param right 右哨兵
*/
public static void quickSort(int[] arr, int left, int right) {
// 如果两个哨兵相遇,则结束循环
if (left < right) {
// 以最左边的数作为中心点(pivot)
int i = left, j = right, pivot = arr[left];
while (i < j) {
// 中心点在最左边,所以此处要从右开始,向左找第一个小于pivot的数
while (i < j && arr[j] >= pivot) {
j--;
}
if (i < j) {
// 退出循环有两种情况,i > j 或者 arr[j] < pivot
// 如果能够进入该if语句说明是arr[j] < pivot,可以交换
// 交换时,覆盖了原来的arr[left],不过它的值已经保存在pivot变量中了
arr[i++] = arr[j];
}
// 从左向右找第一个大于等于pivot的数
while (i < j && arr[i] < pivot) {
i++;
}
if (i < j) {
arr[j--] = arr[i];
}
}
arr[i] = pivot;
/*
至此完成了一次移位调换
int pivot = arr[i];
arr[i++] = arr[j];
arr[j--] = arr[i];
...
...
arr[i] = pivot;
*/
// 递归调用
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
}
5. 希尔排序
改进的插入排序(优化:原数组的一个元素距离正确位置很远的情况)
先让间隔 h 的元素有序,再使得间隔为 h / 2,一直缩小,一直到 h = 1(此时数组有序)。
时间复杂度介于nlogn和n^2之间,空间复杂度1
6、归并排序(重点)
基本思路:借助额外空间,合并两个有序数组,得到更长的有序数组。例如:「力扣」第 88 题:合并两个有序数组。
算法思想:分而治之(分治思想)。「分而治之」思想的形象理解是「曹冲称象」、MapReduce,在一定情况下可以并行化。
7 堆排序
8.基数排序
public void Radix() {
int arr[] = {53, 3, 542, 748, 14, 214};//处理负数时需要改进
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (max < arr[i])
max = arr[i];
}
int maxLength = (max + "").length();//得到最大数是几位数,妙!!!
int[][] bucket = new int[10][arr.length];
int[] bucketElementCounts = new int[10];//比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {//个十百...位
for (int j = 0; j < arr.length; j++) {
int digitOfElement = arr[j] / n % 10;//个十百...位
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
int index = 0;
for (int k = 0; k < bucketElementCounts.length; k++) {//遍历每一个桶
if (bucketElementCounts[k] != 0) {
for (int l = 0; l < bucketElementCounts[k]; l++) {//遍历k桶的数
arr[index++] = bucket[k][l];
}
}
bucketElementCounts[k] = 0;//第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
}
}
System.out.println(Arrays.toString(arr));
}