排序
排序是一个很重有的功能,平时也会经常用到排序。这篇文章会介绍三种常用的排序算法。还有其他的排序方法,比如堆排序,归并排序等等,这些排序会在后面的内容讲述。
以下排序的对象是一个数组 a
,有 n
个元素,需要从小到大进行排序。
选择排序
选择排序就是 n 次选择, 每次从剩下的元素中选择一个最小的出来。
即第 i 次选择就是从 i ~ n
这些位置中的元素里面找到一个最小的元素,然后和 i 这个位置进行交换。做完 n 次选择之后,数组就会变得有序。
第1
次选择找到第1
小的元素放在了0
这个位置
第2
次选择找到第2
小的元素放在了1
这个位置
第i
次选择找到第i
小的元素放在了 i-1
这个位置
一共需要 n 次选择, 每次选择需要从剩下的元素中遍历,所以总的时间复杂度为 O ( n 2 ) O(n^2) O(n2)
public void selectSort(int[] a){
int n = a.length;
for (int i = 0; i < n; ++i){
int mn = a[i], pos = i; // 初始化最小的元素为 mn, 位置为 i。
for (int j = i; j < n; ++j){ // 在剩下的元素中找一个最小的出来。
if (a[j] < mn){ // 如果当前的元素比之前记录的小,那么就要更新。
mn = a[j]; // 更新当前值为最小值。
pos = j; // 更新当前的位置。
}
}
int tmp; // 交换两个位置的元素。
tmp = a[i]; a[i] = a[pos]; a[pos] = tmp;
}
}
冒泡排序
冒泡排序是 n 次循环,每次循环找到一个最大的元素,然后放到数组的后面。
第 1
次找到第1大的元素放在n-1
的位置,
第2
次找到第2
大的元素,放到n-2
的位置,
第i
次找到第i
大的元素放到n-i
的位置。
具体的找法就和选择排序不一样了。
从前往后遍历,每次比较相邻的元素,如果大的元素在前,就交换两个元素的位置,否则不做修改,遍历结束,大的元素就跑到数组后面了。
一共是n次循环, 每次循环也要遍历整个数组,所以总的事件复杂度为 O ( n 2 ) O(n^2) O(n2)
public void bubbleSort(int[] a){
int n = a.length,tmp;
for (int i = 0; i < n; ++i){ // n 次循环
for (int j = 1; j < n-i; ++j){ // 每次循环,遍历一下数组。
if (a[j-1] > a[j]){ // 比较相邻的元素。 如果前面的元素大于后面的元素,那么就交换。
tmp = a[j-1];
a[j-1] = a[j];
a[j] = tmp;
}
}
}
}
第 2 重 for 循环 j
的结束位置 < n - i
, 是因为 n-i ~ n-1
这些位置已经有序了,没有必要再遍历了。
快速排序
快速排序应该是我们最常用的算法了,因为它的时间复杂度十分优秀,是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) ,要很多接口可以调用快速排序,但是我们还是要知道快速排序的原理,并且可以手写快排,在面试的过程中, 经常会有面试官让我们实现一个快速排序算法。
快速排序的大概原理:
1、对区间 [l, r] 进行排序, 先找一个基准数 pivot = a[l]
,定义两个指针 i = l
, j = r
2、先右指针j
不断向左移动,找到第一个小于 pivot
的位置,然后左指针i
不断向右移动,找到第一个大于 pivot
的位置。
3、如果 i < j
则交换 i
j
位置的元素,也就是把大的元素放在右边,小的元素放在左边。然后继续 步骤2
;
4、如果 i==j
代表指针相遇了。由于先移动右指针,所以j
这个位置必然小于等于 pivot
,所以要把 j
这个位置的元素和 l
位置的元素交换一下,也就是把 pivot
换到 i
这个位置上,此时 pivot
已经在数组中找到了自己的位置,以后也不会再变了。
5、当前区间 [l,r]
划分成了两个小区间 [l, i-1]
, [j+1,r]
,左区间的所有值都是小于等于 pivot
, 右区间的值都是大于等于 pivot
,
6、对两个子区间分别进行步骤一
下图是快速排序的一个图解:
public void quickSort(int l, int r, int[] a){ // 对区间 [l,r]进行排序
if (l >= r) return; // 区间只有一个数,直接退出。
int i = l, j = r,pivot = a[l],tmp; // 初始化
while(i < j){ // 两个指针 i j 如果相遇就退出,否则继续执行。
while((i < j) && (a[j] >= pivot)) j--; // 右指针找到第一个小于 pivot 的位置
while((i < j) && (a[i] <= pivot)) i++; // 左指针找到第一个大于 pivot 的位置
if (i < j){ // 交换左右指针位置元素的值
tmp = a[i]; a[i] = a[j]; a[j] = tmp;
}
}
a[l] = a[i]; //把 i 位置的值换到 l 位置
a[i] = pivot; // pivot 的值换到 i 这个位置。
quickSort(l,i-1,a); // 处理左区间
quickSort(i+1,r,a); // 处理右区间
}