最近在学习《极客时间》王争老师的《数据结构与算法之美》,这块知识一直是我比较薄弱的点,之前也没有意识到这块知识的重要性,也在学习的过程中做一些总结,如果想要系统的学习推荐去看一下王争老师的课
排序篇
1、冒泡排序
冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复n次,就完成了n个数据的排序工作
// 冒泡排序,a 表示数组,n 表示数组大小
public void bubbleSort(int[] a, int n) {
if (n <= 1)
return;
boolean flag;
for (int i = 0; i < n; ++i) {
// 提前退出冒泡循环的标志位
flag = false;
for (int j = 0; j < n - i - 1; ++j) {
if (a[j + 1] < a[j]) {
// 交换
int tmp = a[j + 1];
a[j + 1] = a[j];
a[j] = tmp;
// 表示有数据交换
flag = true;
}
}
if (!flag)
// 没有数据交换,提前退出
break;
}
}
冒泡排序是原地排序算法(空间复杂度O(1)),是稳定的排序算法,最好情况时间复杂度O(n),最坏情况时间复杂度O(n2),平均时间复杂度O(n2)
2、插入排序
我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束
// 插入排序,a 表示数组,n 表示数组大小
public void insertionSort(int[] a, int n) {
if (n <= 1)
return;
for (int i = 1; i < n; ++i) {
int value = a[i];
int j = i - 1;
// 查找插入的位置
for (; j >= 0; --j) {
if (a[j] > value) {
a[j + 1] = a[j]; // 数据移动
} else {
break;
}
}
a[j + 1] = value; // 插入数据
}
}
插入排序是原地排序算法,是稳定的排序算法,最好情况时间复杂度O(n),最坏情况时间复杂度O(n2),平均时间复杂度O(n2)
3、快速排序
快排的思想:如果要排序数组中下标从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 class QuickSort {
// 快速排序,a是数组,n表示数组的大小
public static void quickSort(int[] a, int n) {
quickSortInternally(a, 0, n - 1);
}
// 快速排序递归函数,p,r为下标
private static void quickSortInternally(int[] a, int p, int r) {
if (p >= r)
return;
int q = partition(a, p, r); // 获取分区点
quickSortInternally(a, p, q - 1);
quickSortInternally(a, q + 1, r);
}
private static int partition(int[] a, int p, int r) {
int pivot = a[r];
int i = p;
for (int j = p; j < r; ++j) {
if (a[j] < pivot) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
++i;
}
}
int tmp = a[i];
a[i] = a[r];
a[r] = tmp;
return i;
}
}
快排是原地排序算法,不是稳定的排序算法,大部分情况下的时间复杂度都是O(nlogn),只有在极端情况下(如果数组中的数据原来已经是有序的了),才会退化到O(n2)