1. 冒泡排序
时间复杂度O(N^2)
额外空间复杂度O(1)
可实现稳定性
特点:
- 比较这个数和它的下一个数;
- 每一趟运行后的结果:本趟中最大的数排在了最末;
- (至多)走N-1趟。
例如:
代码:(生成一个随机数组并对其冒泡排序,输出每一趟的结果)
package sort;
public class BubbleSort {
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int end = arr.length - 1; end > 0; end--) {
for (int i = 0; i < end; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
System.out.print("第"+ (arr.length -end) +"趟的结果为:");
printArray(arr);
}
}
public static void swap(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; //数组长度随机
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); //每个元素均随机(两次随机,保证有正有负)
}
return arr;
}
public static void main(String[] args) {
int maxSize = 10;
int maxValue = 50;
int[] arr = generateRandomArray(maxSize, maxValue);
System.out.print("进行冒泡排序的数组:");
printArray(arr);
bubbleSort(arr);
System.out.print("排序结果:");
printArray(arr);
}
}
运行截图:
2. 选择排序
时间复杂度O(N^2)
额外空间复杂度O(1)
不具备稳定性
特点:(类似于冒泡排序)
- 比较这个数和它后面的每一个数;
- 每一趟运行后的结果:本趟中最小的数排在了最前;
- 走N-1趟。
思想:
赋初最小值为本趟第一个数,使其与它后面的每一个数进行比较,从而更新最小值,交换初最小值与最小值,本趟结束。即在每一趟中选择最小的数放在本趟最前的位置。
代码:(随机生成一个数组并对其选择排序,打印结果)
package sort;
public class SelectSort {
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(arr, i, minIndex);
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; //数组长度随机
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); //每个元素均随机(两次随机,保证有正有负)
}
return arr;
}
public static void main(String[] args) {
int maxSize = 10;
int maxValue = 50;
int[] arr = generateRandomArray(maxSize, maxValue);
System.out.print("进行选择排序的数组:");
printArray(arr);
selectionSort(arr);
System.out.print("排序结果:");
printArray(arr);
}
}
运行截图:
3. 插入排序
时间复杂度O(N^2) (最好时O(N))
额外空间复杂度O(1)
可实现稳定性
特点:总是试着将一个元素有序地插入到已经有序的数列中。
思想:(因为思路很像捋牌,故以抓扑克牌时整理牌从小到大的顺序为例)
-
i 控制手中牌的个数(已有序的牌数+1),从2张开始插入,因为仅有1张牌时必为有序,不需要考虑插入排序,即i (数组下标)的范围:[1,length-1];
-
j 控制是否交换,即从已有序的牌的最大牌开始依次往前与需插入的牌比较大小,若需插入到最前,则应从最后开始往前一个一个地交换。
代码:(随机生成一个数组并对其插入排序,打印结果)
package sort;
public class InsertSort {
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) {
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
public static void swap(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; //数组长度随机
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); //每个元素均随机(两次随机,保证有正有负)
}
return arr;
}
public static void main(String[] args) {
int maxSize = 10;
int maxValue = 50;
int[] arr = generateRandomArray(maxSize, maxValue);
System.out.print("进行插入排序的数组:");
printArray(arr);
insertionSort(arr);
System.out.print("排序结果:");
printArray(arr);
}
}
运行截图:
4. 快速排序(随机快速排序)
时间复杂度O(N*logN) 最好情况时间复杂度O(N^2)
额外空间复杂度O(logN)
不具备稳定性
思想:在数组中随机寻找一个数作为划分值,分出三个区域(小于、等于、大于),使用递归函数,在小于(大于)区域内再次划分三个区域,…
划分过程:
- 在数组中随机寻找一个元素作为划分值,将其与最后一个元素交换,(L下标指向数组最左的元素,R指向最右的元素)即arr[R]为划分值;
- 设置两个下标变量less(小于区域的最右元素)与more(大于区域的最左元素)并赋初值:less=L-1,此时小于区域为空,more=R,此时大于区域里有一个元素–划分值;
- L右移遍历数组的循环过程:(L==R时停止)
(1)当前数arr[L]若小于划分值arr[R],less的后一个元素与当前数arr[L]交换,小于区域向右扩一位,即less+1,L+1右移;
(2)当前数arr[L]若大于划分值arr[R],more的前一个元素与当前数arr[L]交换,大于区域向左扩一位,即more-1,L不动,留在原地;
(3)当前数若等于划分值,L+1右移。
代码:(生成随机数组对其快速排序,并打印结果)
package sort;
public class QuickSort {
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int l, int r) {
if (l < r) {
swap(arr, l + (int) (Math.random() * (r - l + 1)), r); //在数组中随机选择一个元素作为划分值,使其与最后一个元素交换,使得最后一个元素为划分值(随机快速排序)
int[] p = partition(arr, l, r);
quickSort(arr, l, p[0] - 1); //p[0]->等于区域的第一个元素
quickSort(arr, p[1] + 1, r); //p[1]->等于区域的最后一个元素
}
}
//以数组最后一个元素作为划分值,使得大于它的位于左边区域,等于它的位于中间区域,大于它的位于右边区域(“荷兰国旗问题”)
public static int[] partition(int[] arr, int l, int r) {
int less = l - 1; //less->左区域的最右位置,赋初值l-1保证左区域没有元素
int more = r; //more->右区域的最左位置,赋初值r使得最后一个元素(划分值)在右区域里
while (l < more) {
if (arr[l] < arr[r]) { //小于划分值
swap(arr, ++less, l++); //less右移一位,并与l交换,即左区域向右扩一位,l右移一位
} else if (arr[l] > arr[r]) { //大于划分值
swap(arr, --more, l); //more左移一位,并与l交换,即右区域向左扩一位,l不动留在原地
} else { //等于划分值,l右移一位
l++;
}
}
swap(arr, more, r); //右区域的最左元素与最右元素交换,因为划分值不应在最右区域中
return new int[] { less + 1, more }; //返回两个值:等于区域的最左及最右元素的下标
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int maxSize = 10;
int maxValue = 50;
int[] arr = generateRandomArray(maxSize, maxValue);
System.out.print("进行快速排序的数组:");
printArray(arr);
quickSort(arr);
System.out.print("排序结果:");
printArray(arr);
}
}
运行截图:
5. 归并排序
时间复杂度O(N×logN)
注: T(N)=T(N/2)+T(N/2)+O(N)=2T(N/2)+O(N)(左部分递归+右部分递归+合并)
使用Master公式:
将a=2,b=2,d=1代入,于是得到O(N×logN)
额外空间复杂度O(N)
可实现稳定性
思想:利用递归函数,使得数组左半部分有序,右半部分有序,最终合并。
- 使左(右)半部分有序:将该部分再次划分为左右两个部分分别有序,…
- Base case:左半部分(或右半部分仅有一个数,即L==R)。
- 合并:创建一长度为R-L的数组作为辅助空间,p1,p2分别指向左半部分数组和右半部分数组的第一个数,将p1,p2中小的数放入辅助空间,并++后移,重复比较、填入、后移的过程,直到p1,p2中有且仅有一个越界,此时无越界的数组元素可全部依次填入辅助空间。
注:中间位置的求法尽量不写为(L+R)/2,可能导致越界,可以写L+(R-L)/2 或L+((R-L)>>1).
代码:(生成随机数组对其归并排序,并打印结果)
package sort;
public class MergeSort {
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
mergeSort(arr, 0, arr.length - 1);
}
public static void mergeSort(int[] arr, int l, int r) {
if (l == r) {
return;
}
int mid = l + ((r - l) >> 1); //l与r中点的位置,防止越界写法
mergeSort(arr, l, mid); //左面部分排序
mergeSort(arr, mid + 1, r); //右面部分排序
merge(arr, l, mid, r); //合并(外排)
}
//合并过程
public static void merge(int[] arr, int l, int m, int r) {
int[] help = new int[r - l + 1]; //辅助空间
int i = 0;
int p1 = l;
int p2 = m + 1;
while (p1 <= m && p2 <= r) { //p1与p2没越界时
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; //p1与p2谁小谁就填入help数组,且填入后++后移
}
//以下的两个while必能且只能发生一个,因为p1与p2不能同时都越界
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) { //将辅助空间中的元素分别拷贝回去
arr[l + i] = help[i];
}
}
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
//生成随机数组
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; //数组长度随机
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); //每个元素均随机(两次随机,保证有正有负)
}
return arr;
}
public static void main(String[] args) {
int maxSize = 10;
int maxValue = 50;
int[] arr = generateRandomArray(maxSize, maxValue);
System.out.print("进行归并排序的数组:");
printArray(arr);
mergeSort(arr);
System.out.print("排序结果:");
printArray(arr);
}
}
运行截图:
拓展:求小数和问题
问题描述:求数组中所有元素的前面所有比它小的元素之和的总和。
例如:输入:2 1 3 0 5 4 2 6 1
输出:33(0+0+3(2+1)+0+6(2+1+3)+6(2+1+3)+1(1+0)+17(2+1+3+5+4+2)+0)
分析:若使用暴力方法,一个一个元素地遍历,则时间复杂度为O(N^2),尽量不采取此方法,于是想到结合归并排序算法实现,即在每一次外排合并(子函数、父函数)时若填入辅助空间help中的元素来自左半部分,则产生“小和”,使该元素乘以与其进行比较的右半部分中的元素及其后面的元素个数即可(外排时比该元素大的数有多少个)。利用此方法,本例中33=1×1+2×1+2×1+4×1+5×1+0×5+1×4+2×3+3×3. 时间复杂度为O(N×logN)。
代码:(使用对数器与暴力方法比较)
package sort;
public class SmallSum {
//结合归并排序
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return mergeSort(arr, 0, arr.length - 1);
}
public static int mergeSort(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
int mid = l + ((r - l) >> 1); //l与r中点的位置,防止越界写法
return mergeSort(arr, l, mid) + mergeSort(arr, mid + 1, r) + merge(arr, l, mid, r); //左部分产生的小和数累加+右部分产生的小和数累加+外排合并过程中产生的小和数累加
}
//合并过程中产生小和数
public static int merge(int[] arr, int l, int m, int r) {
int[] help = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = m + 1;
int res = 0;
while (p1 <= m && p2 <= r) {
res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0; //左部分小于右部分,产生小和数
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
return res;
}
// for test 暴力方法
public static int comparator(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int res = 0;
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < i; j++) {
res += arr[j] < arr[i] ? arr[j] : 0;
}
}
return res;
}
// for test 产生随机数组
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for test 拷贝数组
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test 两种不同的方法产生的结果进行比对
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// 打印数组
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 50;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
if (smallSum(arr1) != comparator(arr2)) {
succeed = false;
printArray(arr1);
printArray(arr2);
break;
}
}
System.out.println(succeed ? "Nice!" : "Fuck!");
int[] arr3 = generateRandomArray(maxSize, maxValue);
System.out.print("数组:");
printArray(arr3);
System.out.print("结果:"+smallSum(arr3));
}
}
运行截图: