1.冒泡排序
从第一个元素开始,相邻两元素比较,若前一个元素大于后一个元素则交换,这样一趟比较下来可以确定最大值放到最后。每一趟比较的元素都比前一趟少一个。
- 时间复杂度:O(n^2)
- 空间复杂度(是否额外开辟内存空间):O(1)
- 稳定性(有无相同的数据的前后顺序跳转):稳定(若代码第4行条件改为>=0,就变得不稳定了,因此稳定性并不绝对,要具体看代码如何实现)
public static <T extends Comparable<T>> void bubbleSort(T[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j].compareTo(arr[j + 1]) > 0) {
T temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
冒泡排序的优化
若某一趟元素没有发生交换,则数据已经有序,不需要再继续循环判断,此时可以借助flag,最优时间复杂度可达到O(n)。
public static <T extends Comparable<T>> void bubbleSort(T[] arr) {//类型擦除
for (int i = 0; i < arr.length; i++) {
boolean flag = false;
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j].compareTo(arr[j + 1]) > 0) {
flag = true;//优化,如果此趟过程中没有交换,那么数组已经有序直接跳出循环
T temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if (!flag) {
return;
}
}
}
2.选择排序
第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。
- 时间复杂度 :O(n^2)
- 空间复杂度 :O(1)
- 稳定性:不稳定
public static <T extends Comparable<T>> void selectSort(T[] arr) {
int count = 0;
while (count < arr.length) {
int min = count;
for (int i = count; i < arr.length; i++) {
if (arr[min].compareTo(arr[i]) > 0) {
min = i;//最小的数据的下标赋给min
}
}
swap(arr,min,count);
count++;
}
}
public static <T> void swap(T[] arr, int a, int b) {//若参数只传下标的参数,错误!
T temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
选择排序的优化
每一次在待排序序列中同时选出最大与最小元素,最小元素放在已排序序列1的末尾,最大元素放在已排序序列2的首部。
ublic static <T extends Comparable<T>> void selectSort(T[] arr) {
int count1 = 0, count2 = arr.length - 1;
while (count1 < count2) {
int min = count1, max = count2;
for (int i = count1; i <= count2; i++) {//一次循环同时找到最大与最小
if (arr[min].compareTo(arr[i]) > 0) {
min = i;//最小的数据的下标赋给min
}
if (arr[max].compareTo(arr[i]) < 0) {
max = i;//最大的数据的下标赋给max
}
}
if (min == count2 && max == count1) {//解决最大最小元素位置出现冲突的特殊情况
swap(arr, min, max);
} else if (max == count1) {
swap(arr, max, count2);
swap(arr, min, count1);
} else {
swap(arr, min, count1);
swap(arr, max, count2);
}
count1++;
count2--;
}
}
3.插入排序
从第二个数据开始,依次将待插入数据与已经排好序的有序数据中的每个数据进行比较,若比某个数据大,待插入数据就插到此数据的后面,若比某个数据小,则此数据向后移动,待插入数据再与此数据前一个数据比较,从而插入到正确位置上。
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)
- 稳定性:稳定
public static <T extends Comparable<T>> void insertSort(T[] arr) {
for (int i = 1; i < arr.length; i++) {
int j = i - 1;
T temp = arr[i];
while (j >= 0 && arr[j].compareTo(temp) > 0) {
//若写为arr[j].compareTo(temp)>0&&j>=0则会产生越界异常
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = temp;
}
}
4.希尔排序
是插入排序的一种又称“缩小增量排序”,先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1,即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。(这里尽量不使用都是偶数的增量,因为会 出现重复分组的情况,降低效率)
- 时间复杂度:O(n^1.3~1.5)
- 空间复杂度:O(1)
- 稳定性:不稳定
//代码只是在插入排序的基础上简单做了修改
public static <T extends Comparable<T>> void shellSort(T[] arr, int gap) {
for (int i = gap; i < arr.length; i++) {
int j = i - gap;
T temp = arr[i];
while (j >= 0 && arr[j].compareTo(temp) > 0) {
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = temp;
}
}
private static <T extends Comparable<T>> void shellSort(T[] arr) {//接口
int[] gaps = {5, 3, 1};
for (int i = 0; i < gaps.length; i++) {
shellSort(arr, gaps[i]);
}
}
5.快速排序
通过一趟排序将要排序的数据分割成独立的两部分,定一个基准,其中左边部分的数据比基准小,右边部分数据比基准大,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行。
- 时间复杂度:O(nlogn)
- 空间复杂度:O(logn)
- 稳定性:不稳定
private static <T extends Comparable<T>> int partition(T[] arr, int left, int right) {//一次划分
T temp = arr[left];//定一个基准,使得基准左边都比基准小,右边都比基准大
while (left != right) {
while (left != right && arr[right].compareTo(temp) >= 0) {//从后向前找第一个比基准小的数,加上left != right的条件防止越界
right--;
}
arr[left] = arr[right];
while (left != right && arr[left].compareTo(temp) <= 0) {//从前向后找第一个比基准大的数
left++;
}
arr[right] = arr[left];
}
arr[left] = temp;
return left;//返回左右相遇的下标
}
private static <T extends Comparable<T>> void quickSort(T[] arr, int left, int right) {
int index = partition(arr, left, right);
//当左边或右边的数为1时停止
if (index - left >= 2) {
quickSort(arr, left, index - 1);
}
if (right - index >= 2) {
quickSort(arr, index + 1, right);
}
}
public static <T extends Comparable<T>> void quickSort(T[] arr) {//提供对外界接口
quickSort(arr, 0, arr.length - 1);
}
6.归并排序
归并排序建立在归并的有效操作上进行排序,主要采用分治法将已有序的子序列合并,得到完全有序的序列。 即先让每一小段有序,再让小段之间变得有序。若将两个有序合成一个有序段,这又称为二路归并。
- 时间复杂度:O(nlogn)
- 空间复杂度:O(n)
- 稳定性:稳定
public static <T extends Comparable<T>> void mergeSort(T[] arr) {
for (int i = 1; i < arr.length; i *= 2) {
merge(arr, i);
}
}
public static <T extends Comparable<T>> void merge(T[] arr, int gap) {
int left1 = 0;//第一个归并段的起始位置
int right1 = gap - 1; //第一个归并段的终止位置
int left2 = gap;//第二个归并段的起始位置
int right2 = gap * 2 - 1 < arr.length ? gap * 2 - 1 : arr.length - 1; //第二个归并段的终止位置
T[] brr = (T[]) new Comparable[arr.length];//向上造型
int i = 0;//遍历brr的下标
//有两个归并段时
while (right1 < arr.length - 1) {
while (left1 <= right1 && left2 <= right2) {
if (arr[left1].compareTo(arr[left2]) > 0) {
brr[i++] = arr[left2++];
} else if (arr[left1].compareTo(arr[left2]) < 0) {
brr[i++] = arr[left1++];
} else {
brr[i++] = arr[left2++];
brr[i++] = arr[left1++];
}
}
if (left1 > right1) {
for (int j = left2; j <= right2; j++) {
brr[i++] = arr[j];
}
} else if (left2 > right2) {
for (int j = left1; j <= right1; j++) {
brr[i++] = arr[j];
}
}
left1 = right2 + 1;
right1 = left1 + gap - 1;
left2 = right1 + 1;
right2 = left2 + gap - 1 < arr.length ? left2 + gap - 1 : arr.length - 1;
}
//只剩一个归并段
for (int j = left1; j < arr.length; j++) {
brr[i++] = arr[j];
}
for (int j = 0; j < brr.length; j++) {
arr[j] = brr[j];
}
}
7.堆排序
堆是一个近似完全二叉树的结构。先来一组概念:
二叉树:存储方式数组,逻辑上采用堆的形状,根节点i,左孩子2i+1,右孩子2i+2。
大根堆:子节点总是小于它的父节点。
堆排序的具体操作为:
①将堆调整为大根堆形式(从倒数第一个非叶子节点开始)
②交换0号下标元素(根节点)和“相对最后”的元素(每次调整确定一个元素,交换后继续调整)
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1)
- 稳定性:不稳定
public static <T extends Comparable<T>> void adjust(T[] arr, int begin, int end) {
//1.保存begin位置的值
T temp = arr[begin];
//2.挑选左右孩子较大值
for (int i = 2 * begin + 1; i <= end; i = 2 * i + 1) {
if (i + 1 <= end && arr[i].compareTo(arr[i + 1]) < 0) {
i = i + 1;
}
//3. 较大值和begin位置元素进行比较
if (arr[i].compareTo(temp) > 0) {
arr[begin] = arr[i];
begin = i;
} else {
break;
}
arr[begin] = temp;
}
}
public static <T extends Comparable<T>> void heapSort(T[] arr) {
//将堆调整为大根堆形式(从倒数第一个非叶子节点开始)
for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
adjust(arr, i, arr.length - 1);
}
//交换0号下标元素和“相对最后”的元素
for (int i = 0; i < arr.length; i++) {
T temp = arr[0];
arr[0] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
adjust(arr, 0, arr.length - 1 - 1 - i);
}
}