上篇文章讲到了:冒泡排序、插入排序、选择排序,这篇文章我们讲归并排序和快速排序。
归并排序
归并排序主要把数组从中间分成两部分,然后对前后两部分进行排序下,再将排好序的两部分合并在一起,这样整个数组就有序了。
代码实现:
public static void mergeSort(int[] arr) {
sort(arr, 0, arr.length - 1);
}
public static void sort(int[] arr, int L, int R) {
if(L == R) {
return;
}
int mid = L + ((R - L) >> 1);
sort(arr, L, mid);
sort(arr, mid + 1, R);
merge(arr, L, mid, R);
}
public static void merge(int[] arr, int L, int mid, int R) {
int[] temp = new int[R - L + 1];
int i = 0;
int left = L;
int right = mid + 1;
// 比较左右两部分的元素,哪个小,把那个元素填入temp中
while(left <= mid && right <= R) {
temp[i++] = arr[left] < arr[right] ? arr[left++] : arr[right++];
}
// 上面的循环退出后,把剩余的元素依次填入到temp中
// 以下两个while只有一个会执行
while(left <= mid) {
temp[i++] = arr[left++];
}
while(right <= R) {
temp[i++] = arr[right++];
}
// 把最终的排序的结果复制给原数组
for(i = 0; i < temp.length; i++) {
arr[L + i] = temp[i];
}
}
总结:归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。
快速排序
如果要排序数组中下标从p到r之间的一组数据,我们选择p到r之间的任意一个数据作为pivot(分区点)。
遍历p到r之间的数据,将小于pivot的放到左边,将大于pivot的放到右边,将pivot放到中间。经过这一步骤之后,数组p到r之间的数据就被分成了三个部分,前面p到q-1之间都是小于pivot的,中间是pivot,后面的q+1到r之间是大于pivot的。
可以用递归排序下标从p到q-1之间的数据和下标从q+1到r之间的数据,直到区间缩小为1,就说明所有的数据都有序了。
代码实现:
public void quickSort(int[] arr, int left,int right){
if (left >= right){
return;
}
int key = arr[left];
int i = left;
int j = right;
while (i < j){
//j向左移,直到遇到比key小的值
while (arr[j] >= key && i < j){
j--;
}
//i向右移,直到遇到比key大的值
while (arr[i] <= key && i < j){
i++;
}
//i和j指定的元素交换
if (i < j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
arr[left] = arr[i];
arr[i] = key;
quickSort(arr,left,i - 1);
quickSort(arr,i + 1,right);
}
总结:快排是一种原地、不稳定的排序算法,在大部分情况下的时间复杂度都可以做到O(nlogn),只有在极端情况下,才会退化到O(n2),即分区点pivot选在了最边缘。
归并排序和快速排序的区别
归并排序的处理过程是由下到上的,先处理子问题,然后再合并。而快排正好相反,它的处理过程是由上到下的,先分区,然后再处理子问题。归并排序虽然是稳定的、时间复杂度为O(nlogn)的排序算法,但是它是非原地排序算法。
喜欢本文的话,可以关注一下公众号,每天定时更新一篇学习日记,让我们一起成长!