1. 冒泡排序
重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就交换位置。这个过程持续对数列的末尾进行,直到整个数列都排序完成。
时间复杂度为O(n^2)
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换arr[j+1]和arr[j]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
2. 选择排序
选择排序是一种简单的排序算法,它的基本思想是每次从待排序的元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的元素排完。
时间复杂度为O(n^2)
public static void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 交换arr[i]和arr[minIndex]
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
3. 插入排序
将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
时间复杂度为O(n^2)
public static int[] insertSort(int[] nums) {
for (int i = 0; i < nums.length; i++) {
//把当前遍历的数字存起来
int temp = nums[i];
//遍历当前数字前面所有的数字
int j;
for (j = i - 1; j >= 0 && nums[j] > temp; j--) {
//把前一个数字赋给后一个数字
nums[j + 1] = nums[j];
}
//把临时变量赋给不满足条件的后一个元素
nums[j + 1] = temp;
}
return nums;
}
4. 希尔排序
希尔排序是一种改进的插入排序算法,它的基本思想是将待排序的数组按照一定的间隔进行分组,对每组使用插入排序算法进行排序,然后缩小间隔,再对分组进行排序,直到间隔为1为止。
逐渐减小间隔大小的方法有助于提高排序过程的效率,可以减少比较和交换的次数。这是希尔排序算法的一个关键特点。
时间复杂度为O(n^2)
public class ShellSort {
public static void main(String[] args) {
int[] nums = {5, -1, 0, 9, -4, 5, 3};
System.out.println("排序前:" + Arrays.toString(nums));
nums = shellSort(nums);
System.out.println("排序后:" + Arrays.toString(nums));
}
// 升序排序
public static int[] shellSort(int[] nums) {
//遍历所有步长
for (int d = nums.length / 2; d > 0; d /= 2) {
//遍历所有元素
for (int i = 0; i < nums.length; i++) {
//遍历本组中所有元素
for (int j = i - d; j >= 0; j -= d) {
//如果当前元素大于加上步长后的那个元素
if (nums[j] > nums[j + d]) {
int temp = nums[j];
nums[j] = nums[j + d];
nums[j + d] = temp;
}
}
}
}
return nums;
}
// 降序排序
public static int[] shellSort2(int[] nums) {
//遍历所有步长
for (int d = nums.length / 2; d > 0; d /= 2) {
//遍历所有元素
for (int i = 0; i < nums.length; i++) {
//遍历本组中所有元素
for (int j = i - d; j >= 0; j -= d) {
//如果当前元素大于加上步长后的那个元素
if (nums[j] < nums[j + d]) {
int temp = nums[j];
nums[j] = nums[j + d];
nums[j + d] = temp;
}
}
}
}
return nums;
}
}
5. 归并排序
归并排序是一种分治思想的排序算法,它的基本思想是将待排序的数组分成若干个子序列,每个子序列都是有序的,然后再将子序列合并成一个有序的数组。
时间复杂度为O(nlogn)
public class Merge {
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
public static void merge(int[] arr, int left, int mid, int right) {
// 子数组 L 的大小
int n1 = mid - left + 1;
// 右子数组 R 的大小
int n2 = right - mid;
// 创建两个临时数组 L 和 R ,分别用来存储左子数组和右子数组的元素
int[] L = new int[n1];
int[] R = new int[n2];
// 使用 for 循环将原始数组 arr 中的元素复制到临时数组 L 和 R 中,分别从 left 和 mid + 1 开始
for (int i = 0; i < n1; i++) {
L[i] = arr[left + i];
}
for (int j = 0; j < n2; j++) {
R[j] = arr[mid + 1 + j];
}
// 初始化三个变量 i、j和k,分别指向数组 L 、R 和原始数组 arr 的起始位置
int i = 0, j = 0, k = left;
// 使用 while 循环,比较 L 和 R 的元素,并将较小的元素放回原始数组 arr 中
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
// 当 L 或 R 中的元素用完时,将剩余的元素依次放回原始数组 arr 中
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
// merge 方法执行完毕后,两个子数组范围内的元素已经按照从小到大的顺序合并到了原始数组 arr 中
}
public static void main(String[] args) {
int[] arr = {5, 2, 8, 3, 1, 6};
int[] expectedArr = {1, 2, 3, 5, 6, 8};
Merge.mergeSort(arr, 0, arr.length - 1);
System.out.println("arr = " + Arrays.toString(arr));
Assertions.assertArrayEquals(expectedArr, arr);
}
}
mergeSort 函数接受一个整数数组、一个左索引和一个右索引作为输入,并使用归并排序算法对指定范围内的数组元素进行排序。该函数使用递归将数组分成两个子数组,然后对它们进行排序,并最后将它们合并成一个有序数组。 merge 函数用于将两个有序数组合并成一个有序数组。它创建两个临时数组 L 和 R ,将左子数组的元素存储在 L 中,将右子数组的元素存储在 R 中,然后将它们合并成一个有序数组并存储在原始数组中。
6. 快速排序
快速排序是一种分治思想的排序算法,它的基本思想是通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后再分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
时间复杂度为O(nlogn)
public class QuickSort {
public static void main(String[] args) {
int[] nums = {5, -1, 0, 9, -4, 5, 3};
System.out.println("排序前:" + Arrays.toString(nums));
nums = quickSort(nums, 0, nums.length - 1);
System.out.println("排序后:" + Arrays.toString(nums));
}
/**
* @param nums 被排序的数组
* @param start 排序开始位置
* @param end 排序结束位置
* @return 返回排序后的数组
*/
public static int[] quickSort(int[] nums, int start, int end) {
if (start < end) {
//低位置
int low = start;
//高位置
int high = end;
//取开始位置元素作为标准数
int standard = nums[start];
while (low < high) {
//如果右边的数组比标准数大
while (nums[high] >= standard && low < high) {
high--;
}
//使用右边的数字替换左边的数字
nums[low] = nums[high];
//如果左边的数组比标准数小
while (nums[low] <= standard && low < high) {
low++;
}
//使用左边的数字替换右边的数字
nums[high] = nums[low];
}
//把标准数赋回给低位置或者高位置(此时低位置和高位置已经重合)
nums[low] = standard;
//处理标准数左边的数字
nums = quickSort(nums, start, low);
//处理标准数右边的数字
nums = quickSort(nums, low + 1, end);
}
return nums;
}
}
7. 堆排序
堆排序是一种树形选择排序算法,它的基本思想是将待排序的数组构建成一个大根堆(或小根堆),然后将堆顶元素与堆底元素交换位置,再将剩余元素重新构建成堆,重复执行交换和重构堆的操作,直到整个数组有序。
堆排序是一种基于堆数据结构的排序算法,它的时间复杂度为O(nlogn)。
public class HeapSort {
public static void main(String[] args) {
int[] nums = {5, -1, 0, 9, -4, 5, 3};
System.out.println("排序前:" + Arrays.toString(nums));
nums = heapSort(nums);
System.out.println("排序后:" + Arrays.toString(nums));
}
//
public static int[] heapSort(int[] nums) {
int start = (nums.length - 1) / 2;
//调整为大顶堆
for (int i = start; i >= 0; i--) {
maxHeap(nums, nums.length, i);
}
//
for (int i = nums.length - 1; i >= 0; i--) {
int temp = nums[0];
nums[0] = nums[i];
nums[i] = temp;
maxHeap(nums, i, 0);
}
return nums;
}
//转大顶堆的方法
public static void maxHeap(int[] nums, int size, int index) {
//当前节点
int self = index;
//左子节点
int left = 2 * index + 1;
//和左子节点进行对比,选出最大的节点放到自身位置
if (left < size && nums[self] < nums[left]) {
int temp = nums[self];
nums[self] = nums[left];
nums[left] = temp;
maxHeap(nums, size, left);
}
//右子节点
int right = 2 * index + 2;
//和右子节点进行对比,选出最大的节点放到自身位置
if (right < size && nums[self] < nums[right]) {
int temp = nums[self];
nums[self] = nums[right];
nums[right] = temp;
maxHeap(nums, size, right);
}
}
}
8. 桶排序
基本思想:按找最大值和最小值确定桶的数量,然后根据当前数组的值与最小值的插值来确定这个数应该放入哪个桶,等数组中的所有数全部入桶之后,再对每个桶内进行排序,排序算法自己定,然后按照桶的序号以及排好序后的顺序依次出桶,最终达到排好序的效果。
时间复杂度:O(n)
public class Main {
public static void bucketSort(int[]num,int size,int max,int min){
ArrayList<ArrayList<Integer>>list=new ArrayList<>((max-min)/size+1);
for(int i=0;i<(max-min)/size+1;++i){
list.add(new ArrayList<>());
}
for(int i=0;i<size;++i){
list.get((num[i]-min)/size).add(num[i]);
}
int k=0;
for(int i=0;i<(max-min)/size+1;++i){
Collections.sort(list.get(i));
for(int x:list.get(i)){
num[k++]=x;
}
}
}
public static void main(String[] args) {
int[]num=new int[]{1,90,8,7,10,6,5,40,3,2};
System.out.println("基数排序之前:"+ Arrays.toString(num));
bucketSort(num,10,90,1);
System.out.println("基数排序之后:"+Arrays.toString(num));
}
}
9. 基数排序
基本思想:按照从个位开始比较的顺序,依次入对应数字的桶,当全部个位全部遍历完之后,按照入桶的先后顺序,从0号桶开始,依次出桶,直到将最高位的数遍历完,由于每一位可能的取值为0~9,所以要创建10个桶,标号分别从0 - 9。
时间复杂度为O(n)
public class Main {
public static void cardinalSort(int[]num,int size,int maxSize){
ArrayList<ArrayList<Integer>>list=new ArrayList<>(10);
for(int i=0;i<10;++i){
list.add(new ArrayList<>());//必须这样,不然下面会越界
}
int k=0;
while(k<maxSize){
for(int i=0;i<size;++i){
int n=(num[i]/(int)Math.pow(10,k))%10;//取对应位的值
if(list.get(n)==null){//用list存数据切记要初始化,尤其是没插入数据时,因为list默
//认没插入数据时的容量为0,就算你设置了list的大小也是一样的
list.set(n,new ArrayList<>());
}
list.get(n).add(num[i]);
}
k++;
int t=0;
for(int i=0;i<10;++i){
if(list.get(i)!=null){
for(int x:list.get(i)){
num[t++]=x;
}
list.get(i).clear();
}
}
}
}
public static void main(String[] args) {
int[]num=new int[]{1,9,8,7,10,6,5,4,3,2};
System.out.println("基数排序之前:"+ Arrays.toString(num));
cardinalSort(num,10,2);
System.out.println("基数排序之后:"+Arrays.toString(num));
}
}