常用的排序算法
1、冒泡排序
大的数下沉,小的数上升;从后往前一次两两比较,将小的前移,最后达到前面的最小
时间复杂度:O(N^2)
/**
* 冒泡排序
* 从后往前依次两两比较
* 时间复杂度O(n^2)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] bubbleSort(int[] arr) {
int temp; // 临时变量
boolean flag; // 排序优化,使用标志,在排序一次没有数据交换时,说明已经有序
for (int i = 0; i < arr.length - 1; i++) { // 排序次数为长度减一次
flag = true;// 默认没有交换
for (int j = arr.length - 1; j > i; j--) { // 从后往前依次两两比较,把小的前移
if (arr[j] < arr[j - 1]) {
temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
flag = false; // 有交换改为false
}
}
if (flag)
break; // 终止接下来的排序
}
return arr;
}
2、选择排序
找到剩余数中最小的依次从前往后填
时间复杂度O(N^2)
/**
* 选择排序
* 每次选择最小的依次从前到后填
* 时间复杂度O(n^2)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] selctionSort(int[] arr) {
// 从头到尾一次选择最小的来填
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
// 寻找最小下标
for (int j = i + 1; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
minIndex = j;
}
}
// 如果minIndex不等于当前下标,进行交换
if (minIndex != i) {
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
}
return arr;
}
3、插入排序
从前往后,从第二个数依次插入之前的有序数组,类似于冒泡,冒泡是从最后往前前,这是从当前数往前冒到合适位置
时间复杂度O(N^2)
/**
* 插入排序
* 从第二个数开始依次插入之前的有序数组
* 时间复杂度O(n^2)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] insertSort(int[] arr) {
// 从第二个数开始,插入之前的有序数组
for (int i = 1; i < arr.length; i++) {
// 从当前数依次往前比较找到自己的位置
for (int j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
int temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
} else {
// 如果当前没和前面的数交换,说明i之前的数组已经有序,停止比较
break;
}
}
}
return arr;
}
4、希尔排序
这也是插入排序的一种,将一组数据分成多组数据,每组进行插入排序,逐渐缩小增量,类似合并组,之前排完接近有序,在插入排序效率就很高。
时间复杂度O(N^(1.3-2))
/**
* 希尔排序,插入排序的一种,又叫缩小增量排序
* 设置增量,增量越小,数组就越基本有序,在进行插入排序,高效,当增量为1结束插入排序时,完成排序
* 时间复杂度O(N^(1.3-2))
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] shellSort(int arr[]) {
int incre = arr.length; // 设置增量
while (true) {
incre = incre / 2; // 每次循环增量减半
for (int k = 0; k < incre; k++) { // 根据增量分为若干子序列
for (int i = k + incre; i < arr.length; i += incre) { // 拿到当前子序列组,进行插入排序
for (int j = i; j > k; j -= incre) {
if (arr[j] < arr[j - incre]) {
int temp = arr[j - incre];
arr[j - incre] = arr[j];
arr[j] = temp;
} else {
break;
}
}
}
}
if (incre == 1) { // 如果增量为1,结束
break;
}
}
return arr;
}
5、快速排序
取当前左边的数为key,同时得到一个左坑,先从右往左寻找第一个比key小的数填到左坑中,又得到一个右坑,再从左往右找到第一个比key大或等于的数填进右坑,得到左坑…依次继续进行,直到找到一个点的左面都比key小,右面都比key大或等于,将key填入。在分别对key左边和右边进行同样的操作。
时间复杂度:O(NlogN)
/**
* 快速排序
* 采用分治的思想,取当前第一个数为key,同时第一个数为当前坑
* 先从右开始第一个小于key的数,进行填坑,并重新设置坑位,同时左表加一
* 再从左开始第一个大于或等于key的数,进行填坑,并重新设置坑位,同时右标减一
* 当左标和右标相等时候,对当前key完成排序
* 排完左块小于key,右块大于或等于key
* 左块和右块还无序,再分别对左块和右块进行此操作
* 在最后分成的每块只有一个值得时候结束
* 时间复杂度O(NlogN)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] quickSort(int arr[]) {
quickSort(arr, 0, arr.length - 1);
return arr;
}
/**
* @author 枫叶火火
* @param arr 要排序的数组
* @param lIndex 左下标
* @param rIndex 右下标
*/
private void quickSort(int[] arr, int lIndex, int rIndex) {
if (lIndex >= rIndex) { // 左边下标大于右边时结束
return;
}
int i = lIndex; // i等于左边下标
int j = rIndex; // j等于右边下标
int key = arr[lIndex]; // 关键值key等于最左边第一个值,左边第一个空就是第一个坑位
while (i < j) {
while (i < j && arr[j] >= key)// 如果符合条件,找到最右边第一个比关键值小的
j--;
if (i < j) { // 这里只能是i小于或者等于j,符合条件将找到的值进行填坑,同时i++
arr[i] = arr[j];
i++;
}
while (i < j && arr[i] < key)// 如果符合条件,找到最左边第一个比关键值大或等于的
i++;
if (i < j) {// 这里也只能是i小于或者等于j,符合条件将找到的值进行填坑,同时j--
arr[j] = arr[i];
j--;
}
}
// 当i == j的时候
arr[i] = key;
// 这时左边的都小于arr[i],右边的都大于等于arr[i]
quickSort(arr, lIndex, i - 1);// 进行递归排左边
quickSort(arr, i + 1, rIndex);// 进行递归排右边
}
6、归并排序
将一组数据分成左右两堆,左右两堆再分别分出自己的左右两堆…最后分成一堆只有一个数据的时候可以看成有序,对两个有序数组合并…最后合并最后的两堆有序数组,完成排序。
时间复杂度:O(NlogN)
例如{5,9,7,2,4,6,8,3}
(5,9,7,2)和(4,6,8,3) -->(5,9),(7,2),(4,6),(8,3)–>(5),(9),(7),(2),(4),(6),(8),(3)
(5)和(9)合并(5,9) (7)和(2)合并(2,7) (5,9)和(2,7)合并(2,5,7,9)
(4)和(6)合并(4,6) (8)和(3)合并(3,8) (4,6)和(3,8)合并(3,4,6,8)
(2,5,7,9)和(3,4,6,8)合并(2,3,4,5,6,7,8,9)
/**
* 归并排序
* 采用分治的思想,将一个数组分成两块,每块还可以再分成两块...分成每块只有一个值时,可以看成有序
* 最后分成的每两个小块数组比较,有序的合并成一个数组...
* 时间复杂度O(NlogN)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] mergeSort(int[] arr) {
mergeSort(arr, 0, arr.length - 1, new int[arr.length]);
return arr;
}
/**
* 归并排序私有的处理方法
* @author 枫叶火火
* @param arr 数组
* @param first 第一个坐标
* @param last 最后一个坐标
* @param temp 临时数组
*/
private void mergeSort(int[] arr, int first, int last, int[] temp) {
if (first >= last) {
return;
}
// 将arr看成两块
int i = first; // 块1的第一个坐标
int j = first + (last - first) / 2; // 块1的最后一个坐标
int m = j + 1; // 块2的第一个坐标
int n = last; // 块2的最后一个坐标
int k = 0; // 临时数组坐标
mergeSort(arr, i, j, temp); // 递归处理块1
mergeSort(arr, m, n, temp); // 递归处理块2
// 对有序的块1和块2进行合并
while (i <= j && m <= n) { // 当两块数组都有值时
if (arr[i] <= arr[m]) { // 将小的先填入临时数组
temp[k++] = arr[i++];
} else {
temp[k++] = arr[m++];
}
}
while (i <= j) { // 如果第1块还有剩余填入临时数组
temp[k++] = arr[i++];
}
while (m <= n) { // 如果第2块还有剩余填入临时数组
temp[k++] = arr[m++];
}
// 将两块数组替换成排序好的一块
for (int ii = 0; ii < k; ii++) {
arr[first + ii] = temp[ii];
}
}
7、堆排序
从小到大排序,构建最大堆,取根节点也就是最大的数据与当前构成最大堆的最后面的值进行换位,从堆中撤出。将新堆的数据进行最大堆修正(将换位后的当前根节点调到应该在的位置)…
构建最大堆(利用完全二叉树的性质):从下至上从右至左找到堆中对应的非叶子节点,也就是i=length/2-1,Lchild肯定存在:2i+1,如果Rchild存在为:2i+2;参考完全二叉树;
时间复杂度:O(NlogN)
/**
* 堆排序
* 先创建最大堆,找到数组中对应堆的第一个非叶子节点,从下至上从右至左,也就是下标值i=length/2-1的值
* 找到下标i对应的左右子节点 左子:2i+1,如果存在右子节点:2i+1+1,找出最大的值
* 在与父节点也就是i对应的相比较,如果子节点大互换位置,更新i值
* 构造出最大堆(构造最小堆就是找最小值)
* 再将当前最大根节点与最后的子节点互换位置,调整最大堆
* 时间复杂度:O(NlogN)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] heapSort(int[] arr) {
for (int i = arr.length / 2 - 1; i >= 0; i--) {
maxHeapFix(arr, i, arr.length);// 从第一个非叶子节点,从下至上从右至左
}
// 将构造出的堆中的根节点与最后一个叶子节点互换位置
for (int i = arr.length - 1; i > 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
maxHeapFix(arr, 0, i);// 重新调整互换后的堆,注:当前长度为i
}
return arr;
}
/**
* 最大堆调整
* @author 枫叶火火
* @param arr 调整数组
* @param i 节点下标
* @param length长度
*/
private void maxHeapFix(int[] arr, int i, int length) {
for (int j = 2 * i + 1; j < length; j = 2 * i + 1) {// 找到当前i的左子节点
if (j + 1 < length && arr[j] < arr[j + 1]) {// 如果有右子节点,进行左右比较选出最大的
j++;
}
if (arr[i] < arr[j]) {// 将最大的叶子节点与当前节点比较,符合互换位置
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i = j;// 调整当前节点坐标值,继续调整
} else {
break;
}
}
}
/**
* 最小堆调整
* @author 枫叶火火
* @param arr 调整数组
* @param i 节点下标
* @param length长度
*/
private void minHeapFix(int[] arr, int i, int length) {
for (int j = 2 * i + 1; j < length; j = 2 * i + 1) {// 找到当前i的左子节点
if (j + 1 < length && arr[j] > arr[j + 1]) {// 如果有右子节点,进行左右比较选出最小的
j++;
}
if (arr[i] > arr[j]) {// 将最小的叶子节点与当前节点比较,符合互换位置
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i = j;// 调整当前节点坐标值,继续调整
} else {
break;
}
}
}
完整的常用排序算法封装加测试
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
Sort sort = new Sort();
int [] arr1 = {5,9,7,2,4,6,8,3};
System.out.print("冒泡排序:");
System.out.println(Arrays.toString(sort.bubbleSort(arr1)));
int [] arr2 = {5,9,7,2,4,6,8,3};
System.out.print("选择排序:");
System.out.println(Arrays.toString(sort.selctionSort(arr2)));
int [] arr3 = {5,9,7,2,4,6,8,3};
System.out.print("插入排序:");
System.out.println(Arrays.toString(sort.insertSort(arr3)));
int [] arr4 = {5,9,7,2,4,6,8,3};
System.out.print("希尔排序:");
System.out.println(Arrays.toString(sort.shellSort(arr4)));
int [] arr5 = {5,9,7,2,4,6,8,3};
System.out.print("快速排序:");
System.out.println(Arrays.toString(sort.quickSort(arr5)));
int [] arr6 = {5,9,7,2,4,6,8,3};
System.out.print("归并排序:");
System.out.println(Arrays.toString(sort.mergeSort(arr6)));
int [] arr7 = {5,9,7,2,4,6,8,3};
System.out.print("堆排序:");
System.out.println(Arrays.toString(sort.heapSort(arr7)));
}
}
/**
* 常用的排序算法封装
* @author 枫叶火火
*/
public class Sort {
/**
* 冒泡排序
* 从后往前依次两两比较
* 时间复杂度O(n^2)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] bubbleSort(int[] arr) {
int temp; // 临时变量
boolean flag; // 排序优化,使用标志,在排序一次没有数据交换时,说明已经有序
for (int i = 0; i < arr.length - 1; i++) { // 排序次数为长度减一次
flag = true;// 默认没有交换
for (int j = arr.length - 1; j > i; j--) { // 从后往前依次两两比较,把小的前移
if (arr[j] < arr[j - 1]) {
temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
flag = false; // 有交换改为false
}
}
if (flag)
break; // 终止接下来的排序
}
return arr;
}
/**
* 选择排序
* 每次选择最小的依次从前到后填
* 时间复杂度O(n^2)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] selctionSort(int[] arr) {
// 从头到尾一次选择最小的来填
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
// 寻找最小下标
for (int j = i + 1; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
minIndex = j;
}
}
// 如果minIndex不等于当前下标,进行交换
if (minIndex != i) {
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
}
return arr;
}
/**
* 插入排序
* 从第二个数开始依次插入之前的有序数组
* 时间复杂度O(n^2)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] insertSort(int[] arr) {
// 从第二个数开始,插入之前的有序数组
for (int i = 1; i < arr.length; i++) {
// 从当前数依次往前比较找到自己的位置
for (int j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
int temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
} else {
// 如果当前没和前面的数交换,说明i之前的数组已经有序,停止比较
break;
}
}
}
return arr;
}
/**
* 希尔排序,插入排序的一种,又叫缩小增量排序
* 设置增量,增量越小,数组就越基本有序,在进行插入排序,高效,当增量为1结束插入排序时,完成排序
* 时间复杂度O(N^(1.3-2))
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] shellSort(int arr[]) {
int incre = arr.length; // 设置增量
while (true) {
incre = incre / 2; // 每次循环增量减半
for (int k = 0; k < incre; k++) { // 根据增量分为若干子序列
for (int i = k + incre; i < arr.length; i += incre) { // 拿到当前子序列组,进行插入排序
for (int j = i; j > k; j -= incre) {
if (arr[j] < arr[j - incre]) {
int temp = arr[j - incre];
arr[j - incre] = arr[j];
arr[j] = temp;
} else {
break;
}
}
}
}
if (incre == 1) { // 如果增量为1,结束
break;
}
}
return arr;
}
/**
* 快速排序
* 采用分治的思想,取当前第一个数为key,同时第一个数为当前坑
* 先从右开始第一个小于key的数,进行填坑,并重新设置坑位,同时左表加一
* 再从左开始第一个大于或等于key的数,进行填坑,并重新设置坑位,同时右标减一
* 当左标和右标相等时候,对当前key完成排序
* 排完左块小于key,右块大于或等于key
* 左块和右块还无序,再分别对左块和右块进行此操作
* 在最后分成的每块只有一个值得时候结束
* 时间复杂度O(NlogN)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] quickSort(int arr[]) {
quickSort(arr, 0, arr.length - 1);
return arr;
}
/**
* @author 枫叶火火
* @param arr 要排序的数组
* @param lIndex 左下标
* @param rIndex 右下标
*/
private void quickSort(int[] arr, int lIndex, int rIndex) {
if (lIndex >= rIndex) { // 左边下标大于右边时结束
return;
}
int i = lIndex; // i等于左边下标
int j = rIndex; // j等于右边下标
int key = arr[lIndex]; // 关键值key等于最左边第一个值,左边第一个空就是第一个坑位
while (i < j) {
while (i < j && arr[j] >= key)// 如果符合条件,找到最右边第一个比关键值小的
j--;
if (i < j) { // 这里只能是i小于或者等于j,符合条件将找到的值进行填坑,同时i++
arr[i] = arr[j];
i++;
}
while (i < j && arr[i] < key)// 如果符合条件,找到最左边第一个比关键值大或等于的
i++;
if (i < j) {// 这里也只能是i小于或者等于j,符合条件将找到的值进行填坑,同时j--
arr[j] = arr[i];
j--;
}
}
// 当i == j的时候
arr[i] = key;
// 这时左边的都小于arr[i],右边的都大于等于arr[i]
quickSort(arr, lIndex, i - 1);// 进行递归排左边
quickSort(arr, i + 1, rIndex);// 进行递归排右边
}
/**
* 归并排序
* 采用分治的思想,将一个数组分成两块,每块还可以再分成两块...分成每块只有一个值时,可以看成有序
* 最后分成的每两个小块数组比较,有序的合并成一个数组...
* 时间复杂度O(NlogN)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] mergeSort(int[] arr) {
mergeSort(arr, 0, arr.length - 1, new int[arr.length]);
return arr;
}
/**
* 归并排序私有的处理方法
* @author 枫叶火火
* @param arr 数组
* @param first 第一个坐标
* @param last 最后一个坐标
* @param temp 临时数组
*/
private void mergeSort(int[] arr, int first, int last, int[] temp) {
if (first >= last) {
return;
}
// 将arr看成两块
int i = first; // 块1的第一个坐标
int j = first + (last - first) / 2; // 块1的最后一个坐标
int m = j + 1; // 块2的第一个坐标
int n = last; // 块2的最后一个坐标
int k = 0; // 临时数组坐标
mergeSort(arr, i, j, temp); // 递归处理块1
mergeSort(arr, m, n, temp); // 递归处理块2
// 对有序的块1和块2进行合并
while (i <= j && m <= n) { // 当两块数组都有值时
if (arr[i] <= arr[m]) { // 将小的先填入临时数组
temp[k++] = arr[i++];
} else {
temp[k++] = arr[m++];
}
}
while (i <= j) { // 如果第1块还有剩余填入临时数组
temp[k++] = arr[i++];
}
while (m <= n) { // 如果第2块还有剩余填入临时数组
temp[k++] = arr[m++];
}
// 将两块数组替换成排序好的一块
for (int ii = 0; ii < k; ii++) {
arr[first + ii] = temp[ii];
}
}
/**
* 堆排序
* 先创建最大堆,找到数组中对应堆的第一个非叶子节点,从下至上从右至左,也就是下标值i=length/2-1的值
* 找到下标i对应的左右子节点 左子:2i+1,如果存在右子节点:2i+1+1,找出最大的值
* 在与父节点也就是i对应的相比较,如果子节点大互换位置,更新i值
* 构造出最大堆(构造最小堆就是找最小值)
* 再将当前最大根节点与最后的子节点互换位置,调整最大堆
* 时间复杂度:O(NlogN)
*
* @author 枫叶火火
* @param arr 要排序的数组
* @return 排序完的当前数组
*/
public int[] heapSort(int[] arr) {
for (int i = arr.length / 2 - 1; i >= 0; i--) {
maxHeapFix(arr, i, arr.length);// 从第一个非叶子节点,从下至上从右至左
}
// 将构造出的堆中的根节点与最后一个叶子节点互换位置
for (int i = arr.length - 1; i > 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
maxHeapFix(arr, 0, i);// 重新调整互换后的堆,注:当前长度为i
}
return arr;
}
/**
* 最大堆调整
* @author 枫叶火火
* @param arr 调整数组
* @param i 节点下标
* @param length长度
*/
private void maxHeapFix(int[] arr, int i, int length) {
for (int j = 2 * i + 1; j < length; j = 2 * i + 1) {// 找到当前i的左子节点
if (j + 1 < length && arr[j] < arr[j + 1]) {// 如果有右子节点,进行左右比较选出最大的
j++;
}
if (arr[i] < arr[j]) {// 将最大的叶子节点与当前节点比较,符合互换位置
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i = j;// 调整当前节点坐标值,继续调整
} else {
break;
}
}
}
/**
* 最小堆调整
* @author 枫叶火火
* @param arr 调整数组
* @param i 节点下标
* @param length长度
*/
private void minHeapFix(int[] arr, int i, int length) {
for (int j = 2 * i + 1; j < length; j = 2 * i + 1) {// 找到当前i的左子节点
if (j + 1 < length && arr[j] > arr[j + 1]) {// 如果有右子节点,进行左右比较选出最小的
j++;
}
if (arr[i] > arr[j]) {// 将最小的叶子节点与当前节点比较,符合互换位置
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i = j;// 调整当前节点坐标值,继续调整
} else {
break;
}
}
}
}