1.绪论
排序是算法题中常见的操作,本文将介绍各种排序算法的思想和实现。
2. 选择排序
2.1 思想
选择排序其实就是从左往右遍历,假设当前处理的位置为i,则i左侧为已经拍好序的元素,i右侧为待排序的元素,每次从待排序中元素选择出最大值,与i位置进行交换。
可以看出选择排序每次处理,左边已经是最终排序的元素,右侧是待排的元素,只需要从待排元素中的找到最小值放到i处就可以了。
2.2 代码
java
private void selectedSortedAlgorithm(int nums[]){
//i为正在处理的元素,应该放入剩余元素最小值
for (int i = 0; i < nums.length - 1; i++) {
//寻找[i,nums.length)中的最小值
int minIndex = i;
for (int j = i; j < nums.length - 1; j++) {
if(nums[j] < nums[minIndex]) {
minIndex = j;
}
}
//交换元素
swap(nums, i, minIndex);
}
}
private void swap(int[] nums, int i, int minIndex) {
int temp = nums[minIndex];
nums[minIndex] = nums[i];
nums[i] = temp;
}
选择排序的时间复杂度为O(nlogn)。
3.插入排序
3.1 思想
插入拍序其实就是从左向右遍历,假设当前轮待处理元素为i,则[0,i)为局部有序的元素,[i,length - 1)为待处理元素。所以将i元素放到[0,i)的正确位置上即可。
可以看出,插入排序,i左侧[0,i)为局部有序的序列,他们的顺序并不是最终顺序。
3.2 代码
java
private void insertSortedAlgorithm(int nums[]){
for (int i = 1; i < nums.length - 1; i++) {
//i为当前处理的元素
for(int j = i; j > 0 ; j--) {
if(nums[j] < nums[j - 1]) {
swap(nums,j,j-1);
}
}
}
}
private void swap(int[] nums, int i, int j) {
int temp = nums[j];
nums[j] = nums[i];
nums[i] = temp;
}
插入排序的时间复杂度为O(nlogn)。
4. 归并排序
4.1 思想
归并排序其实是利用分治的思想,每次将区间平分为左右两个区间。只要左右区间为有序的区间,利用merge操作(即将两个有序数合并成一个有序数组),便能保证最后得到的区间是有序的。可以看出,利用分支的思想,最后得到的是合并两个数字,这两个数字本身就是有序的。
4.2 代码
java
public int[] sortArray(int[] nums) {
// 假设现在有两个数组
sortArrayIncr(nums, 0, nums.length - 1);
return nums;
}
// 将[l,r]的元素进行排序
private void sortArrayIncr(int[] nums, int l, int r) {
if (l >= r) {
return;
}
// 左边元素排序
int p = (l + r) / 2;
// 对[l,p]进行排序
sortArrayIncr(nums, l, p);
// 对[p+1,r]进行排序
sortArrayIncr(nums, p + 1, r);
// merge操作[l,p],[p+1,r]
merge(nums, l, p, r);
}
private void merge(int[] nums, int l, int p, int r) {
int arr[] = new int[r - l + 1];
for (int i = 0; i < arr.length; i++) {
arr[i] = nums[l + i];
}
// [0,p-l]和[p-l+1, r]
int m = 0;
int n = p - l + 1;
int k = l;
for (; k <= r; k++) {
if (m > p - l) {
nums[k] = arr[n++];
} else if (n > r - l) {
nums[k] = arr[m++];
} else if (arr[n] > arr[m]) {
nums[k] = arr[m++];
} else {
nums[k] = arr[n++];
}
}
}
5.快速排序
5.1 思想
快速排序其实也是采用分治的思想,首先获取一个元素e,然后采用partition操作,将整个数组中比元素e小的元素放在左边,比元素e大的元素放在右边。然后递归,左边执行该操作,右边也执行该操作,最后便能排好序。
那partition操作是如何进行的呢,如图所示
假设的[l+1,p)<=e,此时p表示下一个要处理的元素,[q,r]的元素都大于e。所以如果待处理元素arr[p]<=e的话,直接p++,如果arr[p]>e的话,直接交换arr[q-1]和arr[p]。
5.2 代码
java
class Solution {
public int[] sortArray(int[] nums) {
quickSort(nums, 0, nums.length - 1);
return nums;
}
private void quickSort(int[] nums, int left, int right) {
if (left >= right) {
return;
}
// 执行partition操作
int p = partition(nums, left, right);
// 左边排序
quickSort(nums, left, p - 1);
// 右边排序
quickSort(nums, p + 1, right);
}
private int partition(int[] nums, int left, int right) {
// 随机一个元素和第一个元素交换
Random random = new Random();
swap(nums, left, random.nextInt(right - left) + left);
// 取出待处理元素
int e = nums[left];
// [left+1,p) <=e,待处理元素为[p,q)
int p = left + 1;
// [q,right] > e
int q = right + 1;
// 无待处理元素
while (p < q) {
if (nums[p] <= e) {
p++;
} else {
// 交换p和q-1的元素
q--;
swap(nums, p, q);
}
}
// 其中p-1为最后一个小于e的元素
swap(nums, left, p - 1);
return p - 1;
}
private void swap(int[] nums, int p, int q) {
int num = nums[q];
nums[q] = nums[p];
nums[p] = num;
}
}
5.3 优化
5.3.1 如果数组本来就近乎有序
这个时候,由于递归树会失衡,可能退化为O(n*n)的时间复杂度。所以我们在获取元素e的时候可以不直接获取第一个元素,而是从待排序数组中随机取一个元素,作为元素e。
5.3.2 如果数组有大量重复元素
大量重复元素的话,可以考虑3路快排,即小于e的占一个区间,等于e的占一个区间,大于e的占一个区间。
6.排序思想的应用
快排的partition操作和归并排序的merge操作,是一种很好的思想,在算法题中右很多应用。
6.1 leetcode 215
6.1.1 题目描述
6.1.2 思路
可以利用的快排的partition操作,假设partition操作完成后,会找到第p大的元素,如果k小于p,便在[0,p]的范围内搜索第k大的元素,如果k>p,便在[p+1,nums.length-1]的范围内搜索第k大的元素。
6.1.3 代码
java
class Solution {
public int findKthLargest(int[] nums, int k) {
return findKthLargest(nums, 0, nums.length - 1, nums.length - k);
}
// 寻找[l,r]位置下标为k的元素
public int findKthLargest(int[] nums, int l, int r, int k) {
// partition
int p = partition(nums, l, r);
if (k == p) {
return nums[k];
}
// [l,p-1]寻找下标为k的元素
if (k < p) {
return findKthLargest(nums, l, p - 1, k);
} else {
// [p+1,r]寻找k-p-1大元素
return findKthLargest(nums, p + 1, r, k);
}
}
// 将[l,r]执行partition
private int partition(int[] nums, int l, int r) {
Random random = new Random();
swap(nums, l, random.nextInt(r - l + 1) + l);
int e = nums[l];
// [l+1,p) <= e
int p = l + 1;
// [q,r] > e
int q = r + 1;
while (p < q) {
if (nums[p] <= e) {
p++;
} else {
q--;
swap(nums, p, q);
}
}
swap(nums, p - 1, l);
return p - 1;
}
private void swap(int[] nums, int p, int q) {
int tmp = nums[p];
nums[p] = nums[q];
nums[q] = tmp;
}
}