数组打印方式:
Arrays.toString() 打印一维数组
Arrays.deepToString()打印多维数组
public int[] initArray(int n){
int[] array = new int[n];
for (int i = 0; i < n; i++) {
array[i] = (int) (Math.random()*10);
}
return array;
}
public void printArray(String str, int[] arrays) {
System.out.println(str + " " + Arrays.toString(arrays));
}
排序算法的稳定性: 如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变,即是稳定的排序算法。
平均时间复杂度: 平均时间复杂度就是加权平均期望时间复杂度。
有序度: 有序度是数组中具有有序关系的元素对的个数,即有序元素对:a[i] <= a[j], 如果i < j
逆有序度: 逆有序度是数组中无序关系的元素对的个数,即逆序元素对:a[i] > a[j], 如果i < j
逆序度 = 满有序度 - 有序度
对于一个倒序排列的数组,比如 6,5,4,3,2,1,有序度是 0。
对于一个完全有序的数组,比如 1,2,3,4,5,6,有序度就是 n*(n-1)/2,我们把这种完全有序的数组的有序度叫作满有序度。
冒泡排序:相邻两个元素进行比较,看是否满足大小关系,不满足就交换,一次能比较出一个最小或最大的元素,一共n个元素,重复n次,完成n个数据的排序工作。
- 时间复杂度 O(n^2)
- 是原地排序算法
- 是稳定的排序算法
我们要对一组数据 4,5,6,3,2,1,从小到大进行排序。第一次冒泡操作的详细过程就是这样:
优化: 冒泡过程还可以优化。当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作。
@Test
public void bubbleTest() {
int n = 10;
int[] array = initArray(n);
System.out.println(Arrays.toString(array));
int count = 0;
for (int i = 0; i < n; ++i) {
// 提前退出冒泡循环的标志位
boolean flag = false;
System.out.println(Arrays.toString(array));
for (int j = 0; j < n - i - 1; ++j) {
count++;
if (array[j] > array[j + 1]) { // 交换
int tmp = array[j];
array[j] = array[j + 1];
array[j + 1] = tmp;
flag = true; // 表示有数据交换
}
}
if (!flag)
break; // 没有数据交换,提前退出
}
System.out.println("执行次数" + count);
System.out.println(Arrays.toString(array));
}
插入排序:从一个数组的第二个元素开始向后遍历每个即将插入的数据,逐个向前比较,找到即将插入的位置(即比较前一个元素是否满足比较关系,不满足,向后移动,腾出位置,找到满足比较关系的元素位置),放入数据。
- 时间复杂度 O(n^2)
- 是原地排序算法
- 是稳定的排序算法
@Test
public void insertSortTest() {
int n = 10;
int[] array = initArrays(n);
System.out.println(Arrays.toString(array));
for (int i = 1; i < n; i++) {
int insertValue = array[i];
int j = i - 1;
for (; j >= 0; j--) {
if (array[j] > insertValue) {
array[j + 1] = array[j];
} else {
break;
}
}
array[j + 1] = insertValue;
}
System.out.println(Arrays.toString(array));
}
选择排序:每次选择排序列表中最小的元素,与有序后的第一个无序数据进行交换位置,当有序数组为全数组时,排序完成。
- 时间复杂度O(n^2)
- 是原地排序算法
- 不是稳定的排序算法。
由于每次在剩余的元素查找到最小的元素,和之前的元素进行比较并交换,所以,位置就会进行变化,比如:5,8,5,2,9 这样一组数据,使用选择排序算法来排序的话,第一次找到最小元素 2,与第一个 5 交换位置,那第一个 5 和中间的 5 顺序就变了,所以就不稳定了。正是因此,相对于冒泡排序和插入排序,选择排序就稍微逊色了。
@Test
public void selectSortTest() {
int n = 10;
int[] array = initArray(n);
System.out.println(Arrays.toString(array));
for (int i = 0; i < n; i++) {
int min = array[i];
for (int j = i + 1; j < n; j++) {
if (array[j] < min) {
int temp = array[j];
array[j] = min;
min = temp;
}
}
array[i] = min;
}
System.out.println(Arrays.toString(array));
}
总结: 插入排序要比冒泡排序更受欢迎.
原因:从代码实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要 3 个赋值操作,而插入排序只需要 1 个。
冒泡排序中数据的交换操作:
if (a[j] > a[j+1]) { // 交换
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
flag = true;
}
插入排序中数据的移动操作:
if (a[j] > value) {
a[j+1] = a[j]; // 数据移动
} else {
break;
}
归并排序:递归到只有两个元素时,进行排序,再向上返回三个/四个元素进行排序,直到全数组进行排序,即排序完成。
1.是稳定的排序算法
2.时间复杂度:O(nlogn)
3.空间复杂度:O(n)
4.稳定的排序算法
5.不是原地排序
public void mergeSort(int[] original) {
int arrayLength = original.length;
int middle = arrayLength / 2;
if (middle < 1) {
return;
}
int[] partitionA = Arrays.copyOfRange(original, 0, middle);
int[] partitionB = Arrays.copyOfRange(original, middle, arrayLength);
mergeSort(partitionA);
mergeSort(partitionB);
sort(partitionA, partitionB, original);
}
public void sort(int[] partA, int[] partB, int[] original) {
int i = 0;
int j = 0;
int k = 0;
while (i < partA.length && j < partB.length) {
if (partA[i] <= partB[j]) {
original[k++] = partA[i++];
} else {
original[k++] = partB[j++];
}
}
if (i == partA.length) {
while (j < partB.length) {
original[k++] = partB[j++];
}
} else if (j == partB.length) {
while (i < partA.length) {
original[k++] = partA[i++];
}
}
}
- 添加哨兵优化版:
package sorts;
/**
* Created by wangzheng on 2018/10/16.
*/
public class MergeSort {
// 归并排序算法, a是数组,n表示数组大小
public static void mergeSort(int[] a, int n) {
mergeSortInternally(a, 0, n-1);
}
// 递归调用函数
private static void mergeSortInternally(int[] a, int p, int r) {
// 递归终止条件
if (p >= r) return;
// 取p到r之间的中间位置q,防止(p+r)的和超过int类型最大值
int q = p + (r - p)/2;
// 分治递归
mergeSortInternally(a, p, q);
mergeSortInternally(a, q+1, r);
// 将A[p...q]和A[q+1...r]合并为A[p...r]
merge(a, p, q, r);
}
private static void merge(int[] a, int p, int q, int r) {
int i = p;
int j = q+1;
int k = 0; // 初始化变量i, j, k
int[] tmp = new int[r-p+1]; // 申请一个大小跟a[p...r]一样的临时数组
while (i<=q && j<=r) {
if (a[i] <= a[j]) {
tmp[k++] = a[i++]; // i++等于i:=i+1
} else {
tmp[k++] = a[j++];
}
}
// 判断哪个子数组中有剩余的数据
int start = i;
int end = q;
if (j <= r) {
start = j;
end = r;
}
// 将剩余的数据拷贝到临时数组tmp
while (start <= end) {
tmp[k++] = a[start++];
}
// 将tmp中的数组拷贝回a[p...r]
for (i = 0; i <= r-p; ++i) {
a[p+i] = tmp[i];
}
}
/**
* 合并(哨兵)
*
* @param arr
* @param p
* @param q
* @param r
*/
private static void mergeBySentry(int[] arr, int p, int q, int r) {
int[] leftArr = new int[q - p + 2];
int[] rightArr = new int[r - q + 1];
for (int i = 0; i <= q - p; i++) {
leftArr[i] = arr[p + i];
}
// 第一个数组添加哨兵(最大值)
leftArr[q - p + 1] = Integer.MAX_VALUE;
for (int i = 0; i < r - q; i++) {
rightArr[i] = arr[q + 1 + i];
}
// 第二个数组添加哨兵(最大值)
rightArr[r-q] = Integer.MAX_VALUE;
int i = 0;
int j = 0;
int k = p;
while (k <= r) {
// 当左边数组到达哨兵值时,i不再增加,直到右边数组读取完剩余值,同理右边数组也一样
if (leftArr[i] <= rightArr[j]) {
arr[k++] = leftArr[i++];
} else {
arr[k++] = rightArr[j++];
}
}
}
}
快速排序 :找一个任意数据作为分区点pivote(案例是将最后一个元素作为pivote),将数组分为大于和小于pivot的两部分,直至分组区间缩小为1,说明数据是有序的了。
1.时间复杂度:O(nlogn)
2.最坏:O(n2)
3.原地排序算法
4.不稳定的排序算法
- 优化:
- pivot 设值为 三数取中法/五数取中法/十数取中法,这样可以避免pivot选取不均,数据全部一边倒,导致两边数据区间过大或过小
- pivote 设值为 随机法取数,避免每次选择最后一个,防止该序列是倒序序列等,每次分的区间会导致严重不均匀。
递推公式:
quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1… r)
终止条件:
p >= r
@Test
public void quickSortTest() {
int[] arrays = initArrays(22);
printArray("排序前:", arrays);
quickSort(arrays, arrays.length);
printArray("排序后:", arrays);
}
public void quickSort(int[] array, int n) {
quickSort(array, 0, n - 1);
}
public void quickSort(int[] array, int p, int r) {
if (p >= r)
return;
int q = partition(array, p, r);
quickSort(array, p, q - 1);
quickSort(array, q + 1, r);
}
public int partition(int[] array, int p, int r) {
int pivot = array[r];
int i = p;
for (int j = p; j < r - 1; j++) {
if (array[j] < pivot) {
int temp = array[j];
array[j] = array[i];
array[i] = temp;
i++;
}
}
int temp = array[r];
array[r] = array[i];
array[i] = temp;
return i;
}
**线性排序:**桶排序、计数排序、基数排序 时间复杂度:O(n)
桶排序:将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。
- 应用场景:适用于数据量较大,且内存不足的情况。
计数排序:当要排序的 n 个数据,把数据划分成 k 个桶,再将其取出,便是有序的了。 - 应用场景:考生分数的查询及人数
基数排序:对数据的要求是可以分隔的,对分隔后的数据进行排序,排好序后,整体数据便是有序的了 - 应用场景:1-万手机号的排序