排序算法
重点掌握三个排序算法:
快速排序
思路:
假如给你一个数组{-9, 78,45, 23, -567, 89}
-
首先你先确定一个基数pivot (选取的规则一般是left + right >> 1),这里是优化,否则每次选左边或者右边在有序数组中的时间复杂度是O(n的平方)。
-
然后将选取的该基数放在最右边
-
然后运用双指针的操作,创建 指针i 和 指针j,然后两个指针都是指向最左边的那个元素,然后指针j开始遍历,指针i不动
-
直到指针j遇到比基数小的数时,就跟指针i的指向的数进行互换,此时,j++,i也需要++ 。以此反复。
-
直到指针j指向pivot时,再将pivot和指针i的指向的数进行互换。从而结束第一次的排序。此时pivot这个数就到了它正确的位置了。
-
这样以第一次的pivot为基点就切割为两个子数组。再对两个子数组分别重复进行 步骤1- 5的操作即可。
流程图如下:
代码如下:
private void quickSort(int[] nums, int left, int right) {
//递归退出条件
if (left >= right) {
return;
}
int mid = left + (right - left) / 2;
swap(nums, mid, right);
// 基数
int pivot = nums[right];
int i = left,j = left;
while (j <= right) {
if (nums[j] <= pivot) {
swap(nums, i, j);
j++;
i++;
} else {
j++;
}
}
// 递归条件中 为什么是 i - 2 以及 i 呢? 因为当 j 指针到达了 pivot 元素的时候,这时候 i 和 j 同时加加,而且又是左闭右闭区间,对于右区间来说,左区间的终点肯定是 i - 2的,而对于右区间来说,此时的 i 就是起点
quickSort(nums, left, i - 2);
quickSort(nums, i, right);
}
public void swap(int[] nums, int left, int right) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
}
/*
时间复杂度:最坏O(n * n) 最优O(n * log n) 平均O(n * log n)
空间复杂度:O(1)
*/
归并排序
思路:归并排序用到了分治的思想。所谓分治,对于我个人的理解来说,就是将数组拆分为个体(这里体现分的思想),然后进行操作,操作完了之后,就进行合并操作(这里体现治的思想)。
思路
假如给你一个数组{-9, 78,45, 23, -567, 89}
- 利用二分拆解的方法将原始数组为两个等份(一份区间为[left , mid],一份为[mid+1 , right])
- 原先的数组的[left,mid]再等份划分之后,原来的mid其实就是现在的新数组的right了。一直划分,直到将全部的数组分为单个元素为止
- 然后就需要进行 治 了,这时就需要一个临时数组(大小:[right - left + 1])来装这些治后的元素,也同样从最底下的子数组开始进行合并。(这里看文字也许无法理解,可以画个图,每个数组都有自己的子数组,我说的最底下的子数组是区间为2的时候的子数组了。)
- 完成治后,将临时数组的元素再拷贝覆盖到原来的数组即可得出结果。
流程图如下:
代码如下:
class Solution {
public static void main(String[] args) {
int[] ints = new Solution().sortArray(new int[]{9, 25, 7, 678, 19, 14});
System.out.println(Arrays.toString(ints));
}
public int[] sortArray(int[] nums) {
recursion(nums, 0, nums.length - 1);
return nums;
}
public void recursion(int[] nums, int i, int j) {
if (i >= j) {
return;
}
int mid = i + j >> 1;
// 左闭右闭
recursion(nums, i, mid);
recursion(nums, mid + 1, j);
merge(nums, i, mid, j);
}
public void merge(int[] nums1, int left, int mid, int right) {
int[] res = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
// 即使相等也不改变这个顺序,稳定性
if (nums1[i] <= nums1[j]) {
res[k] = nums1[i];
i++;
} else if (nums1[i] > nums1[j]) {
res[k] = nums1[j];
j++;
}
k++;
}
while (i <= mid) {
res[k++] = nums1[i];
i++;
}
while (j <= right) {
res[k++] = nums1[j];
j++;
}
// 将res数组的数 转移在 nums1中
for (int t = 0; t < res.length; t++) {
nums1[t + left] = res[t];
}
}
}
/*
时间复杂度:最坏O(n * n) 最优O(n * log n) 平均O(n * log n)
空间复杂度:O(1)
*/
堆排序
思路:
其实堆排序是利用了大顶堆的思想,但不是真的构建一个二叉树,真实的数据是在数组中操作的。
假如给你一个数组{-9, 78,45, 23, -567, 89}
- 将数组的数构建成一个大顶堆(大顶堆就是每个父节点肯定比其左右孩子节点的值要大);
- 先从倒数第1个非叶子节点的节点算起,先比较 该节点(就是父节点)的两个子节点的大小,然后将大的那个子节点和父节点进行比较,如果是子节点比父节点大的话,就互换位置,如果小的话,就不动;
- 按照 2 的思路,直到根节点符合大顶堆的逻辑,然后将根节点的值将最后一个节点的值互换,即可完成一次排序;
流程图如下:
代码如下:
public int[] sortArray(int[] nums) {
// 先对传入进来的数组进行堆的构建 []
int len = nums.length;
for (int i = (len - 1) / 2; i >= 0; i--) {
heapAdjust(nums, i, len - 1);
}
// 逐步取掉第一个元素,然后不断调整堆
int i = len - 1;
while (i >= 0) {
swap(nums, 0, i);
// 相当于删除最后一个元素
i--;
heapAdjust(nums, 0, i);
}
return nums;
}
/**
* 堆调整(按序调整)
*
* @param nums 数组
* @param index 根节点(父节点)索引
* @param len 堆操作的范围
*/
public void heapAdjust(int[] nums, int index, int len) {
int maxIndex;
while (2 * index + 1 <= len) {
int left = 2 * index + 1;
if (left + 1 <= len && nums[left] < nums[left + 1]) {
maxIndex = left + 1;
} else {
maxIndex = left;
}
if (nums[index] < nums[maxIndex]) {
swap(nums, index, maxIndex);
index = maxIndex;
} else {
break;
}
}
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
/*
时间复杂度:最坏O(n * n) 最优O(n * log n) 平均O(n * log n)
空间复杂度:O(1)
*/
这里附上一个大根堆的一个代码:
/**
* @author: hero生仔
* 大顶堆
*/
public class MaxHeap {
/**
* 堆的底层就是数组
*/
private final List<Integer> list;
public MaxHeap() {
list = new ArrayList<>();
}
/**
* 堆中添加元素
*/
public void push(int val) {
// 往集合中添加元素
list.add(val);
int len = list.size();
// 如果集合中头节点的元素 > 集合中该头节点的子节点的元素
while (list.get(len - 1) > list.get((len - 1) / 2)) {
// 78 跟 -9 互换 比如数组:[-9,78,45,23,-567,89]
swap(len - 1, (len - 1) / 2);
// 将指向父节点的指针 移向 指向子节点
len = (len - 1) / 2 + 1;
}
}
/**
* 堆中第一个最大的元素
*/
public void top() {
int len = list.size();
if (len == 0) {
System.out.println("堆为空~");
} else {
System.out.println("top的元素为:" + list.get(0));
}
}
/**
* 堆弹出元素
*/
public void pop() {
int len = list.size();
if (len == 0) {
System.out.println("堆为空~");
} else {
System.out.println("弹出来的元素为:" + list.get(0));
// 每次弹出元素,都需要将堆顶元素与堆底元素进行互换
list.set(0, list.get(len - 1));
// 然后删除堆底元素(也就是原来的堆顶元素)
list.remove(len - 1);
// 此时需要再次调整
heapAdjust(0, list.size() - 1);
}
}
private void heapAdjust(int index, int len) {
// 记录最大节点的元素的索引值
int maxIndex;
while (index * 2 + 1 <= len) {
// 子节点的索引值
int left = 2 * index + 1;
// 比较两个子节点的值
if (left + 1 <= len && list.get(left) < list.get(left + 1)) {
maxIndex = left + 1;
} else {
maxIndex = left;
}
// 如果子节点此时大于根(父)节点
if (list.get(index) < list.get(maxIndex)) {
// 交换位置
swap(index, maxIndex);
// 且需要将索引从原来指向根(父)节点变到子节点
index = maxIndex;
} else {
break;
}
}
}
/**
* 交换元素
*/
private void swap(int left, int right) {
int tmp = list.get(left);
list.set(left, list.get(right));
list.set(right, tmp);
}
}