各个排序的时间复杂度
简单选择排序
- 每一趟从待排序的数据元素中选择最小(或最大)的一个元素作为首元素,直到所有元素排完为止,简单选择排序是不稳定排序;在每次比较时将最小的数的下标保存起来,这个一次循环完成后,再进行交换顺序
- 数组分成有序区和无序区,初始时整个数组都是无序区,然后每次从无序区选一个最小的元素直接放到有序区的最后,直到整个数组变有序区。
- 图解
- 实现
public void simpleChoiceSort(int[] data) {
for (int i = 0; i < data.length - 1; i++) {
// 每一趟循环比较时,tempMinIndex 用于存放较小元素的数组下标,这样当前批次比较完毕最终存放的就是此趟内最小的元素的下标,避免每次遇到较小元素都要进行交换。
int tempMinIndex = i;
// 默认与i相等的一位作为基数,然后跟它的下一位比较。。。
for (int j = i + 1; j <= data.length - 1; j++) {
// 从大到小,是【<】;从小到大,是【>】
if (data[tempMinIndex] > data[j]) {
tempMinIndex = j;
}
}
// 进行交换,如果tempMinIndex 发生变化,则进行交换
if (tempMinIndex != i) {
int tempData = data[i];
data[i] = data[tempMinIndex];
data[tempMinIndex] = tempData;
}
}
}
冒泡排序
- 冒泡排序分从大到小和从小到大两种排序方式。它们的唯一区别就是两个数交换的条件不同,从大到小排序是前面的数比后面的小的时候交换,而从小到大排序是前面的数比后面的数大的时候交换。
- 从第一个数开始,依次往后比较,如果前面的数比后面的数大就交换,否则不作处理。这就类似烧开水时,壶底的水泡往上冒的过程。
- 图解
- 实现
public void bubbleSort(int[] data) {
for (int i = 0; i < data.length; i++) {
// 外层循环增加一次,对应内层循环就 减少一次;内层循环【data.length - 1 - i】外层循环【data.length - 1】
for (int j = 0; j < data.length - 1 - i; j++) {
// 将整个队列中相邻的两个元素做比较,将较小的元素和较大的元素交换位置
// 从大到小,是【<】;从小到大,是【>】
if (data[j] > data[j + 1]) {
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
}
- 冒泡排序优化
- 减少外层循环次数的优化【如果该次循环没有发生一次数的交换,就说明数组已经排好序了,那么后面的循环比较就可以停止了】
public void bubbleSortPlus(int[] data) {
// flag: 在一次循环中是否发生过数据交换。true:表示发生过交换,false:表示未发生过交换
boolean flag = true;
for (int i = 0; i < data.length; i++) {
// 外层循环增加一次,对应内层循环就 减少一次;内层循环【data.length - 1 - i】外层循环【data.length - 1】
// 如果未发生交换,则break
if (!flag) {
break;
}
// 每次循环都先置为false,即认为不会发生交换
flag = false;
for (int j = 0; j < data.length - 1 - i; j++) {
if (data[j] < data[j + 1]) {
// 发生了交换
flag = true;
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
}
- 减少内层循环次数的优化【如果我们将第一次出现该次不再交换数据时的位置下标找到,然后把这个值作为内层循环j的上限值。这样内层循环就会减少一些循环。所以内层循环的 j 的上限值是动态值】
public void bubbleSortPlusPlus(int[] data) {
// flag: 在一次循环中是否发生过数据交换。true:表示发生过交换,false:表示未发生过交换
boolean flag = true;
// 上一次内层循环上界值,在第一次循环时与arr.length-1-i相等,即:arr.length-1。也就是说默认是最后一个
int k = data.length - 1;
for (int i = 0; i < data.length - 1; i++) {
// 如果未发生交换,则break
if (!flag) {
break;
}
// 每次循环都先置为false,即认为不会发生交换
flag = false;
// 记录上一次内层循环时最后一次交换的位置
int last = 0;
for (int j = 0; j < k; j++) {
if (data[j] > data[j + 1]) {
// 发生了交换
flag = true;
// 记录每次交换的位置
last = j;
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
// 让下一次循环的上界值变成此次循环的最后一次交换的位置
k = last;
}
}
插入排序
- 类似于打扑克牌。每次将一个待排序的数据,跟前面已经有序的序列的数字一一比较找到自己合适的位置,插入到序列中,直到全部数据插入完成。
- 图解
- 实现
public void insertSort(int[] data) {
for (int i = 1; i < data.length; i++) {
// 将data[i]作为基数
int temp = data[i];
for (int j = i - 1; j >= 0; j--) {
if (temp < data[j]) {
data[j + 1] = data[j];
data[j] = temp;
} else {
break;
}
}
}
}
希尔排序
- 先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。由于希尔排序是对相隔若干距离的数据进行直接插入排序,因此可以形象的称希尔排序为“跳着插”
- 实现
// 希尔排序
public void shellsSort(int[] data) {
for (int incr = data.length / 2; incr > 0; incr = incr / 2) {
for (int i = incr; i < data.length; i = i + incr) {
int temp = data[i];
for (int j = i - incr; j >= 0; j = j - incr) {
if (temp < data[j]) {
data[j + incr] = data[j];
data[j] = temp;
} else {
// data[j + incr] = temp;
break;
}
}
}
}
}
快速排序
-
“挖坑填数+分治法”,首先令i =L; j = R; 将a[i]挖出形成第一个坑,称a[i]为基准数。然后j–由后向前找比基准数小的数,找到后挖出此数填入前一个坑a[i]中,再i++由前向后找比基准数大的数,找到后也挖出此数填到前一个坑a[j]中。重复进行这种“挖坑填数”直到i==j。再将基准数填入a[i]中,这样i之前的数都比基准数小,i之后的数都比基准数大。因此将数组分成二部分再分别重复上述步骤就完成了排序。
-
图解
-
实现
public static void quickSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process1(arr, 0, arr.length - 1);
}
public static void process1(int[] arr, int L, int R) {
if (L >= R) {
return;
}
// L..R partition arr[R] [ <=arr[R] arr[R] >arr[R] ]
int M = partition(arr, L, R);
process1(arr, L, M - 1);
process1(arr, M + 1, R);
}
归并排序
-
归并排序主要分为两步:分数列(divide),每次把数列一分为二,然后分到只有两个元素的小数列;合数列(Merge),合并两个已经内部有序的子序列,直至所有数字有序。用递归可以实现。
-
图解
-
实现
private static void mergeSort(int[] data) {
// 传入的是索引值
fun(0, data.length - 1, data);
}
private static void fun(int left, int right, int[] data) {
if (left >= right) {
return;
}
int mid = (left + right) / 2;
fun(left, mid, data);// 左边归并排序
fun(mid + 1, right, data);// 右边归并排序
// 归并
merge(left, right, data, mid);
}
private static void merge(int left, int right, int[] data, int mid) {
// new 一个相同长度的临时数组
int[] tempData = new int[right - left + 1];
int leftPos = left;// 左序列指针
int rightPos = mid + 1;// 右序列指针
int tempPos = 0;// 临时数组指针
while (leftPos <= mid && rightPos <= right) {
if (data[leftPos] <= data[rightPos]) {
tempData[tempPos++] = data[leftPos++];
} else {
tempData[tempPos++] = data[rightPos++];
}
}
while (leftPos <= mid) {// 将左边剩余元素填充进temp中
tempData[tempPos++] = data[leftPos++];
}
while (rightPos <= right) {// 将右序列剩余元素填充进temp中
tempData[tempPos++] = data[rightPos++];
}
tempPos = 0;
// 将temp中的元素全部拷贝到原数组中
while (left <= right) {
data[left++] = tempData[tempPos++];
}
}