基础排序算法及优化

本文深入解析了选择排序、冒泡排序、插入排序、希尔排序、归并排序、快速排序、计数排序和基数排序等经典排序算法的特点、时间复杂度、空间复杂度及稳定性,并提供了详细的代码实现和优化思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

选择排序及优化

特点:

名称平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
选择排序(SelectionSort)n^2n^2n^21不稳定

选择排序(Selection),平均时间复杂度、最好/最坏时间复杂度都是n^2,空间复杂度为1,不稳定。
思想
选择排序的思想就是,假设数组(arr)的第一个元素(arr[0])为最大/最小值,并记下它的角标 index=0。然后依次同数组的其他元素比较,如果被比较元素大于/小于该元素,则记录被比较元素并替代index。
如此,每一轮遍历都会将最大值/最小值移动到最左边。从而完成排序。区别与冒泡排序的数组间相邻元素的两两交换,选择排序是将这个比较放到整个数组的元素之间。
代码如下:

public class SelectionSort {
    public static void main(String[] args) {
        int[] arr = {10,4,5,8,17,6,11,12,23,32,34,95,54,56,77,88,21,43,45,65,23,43,23};
        for (int j = 0; j < arr.length - 1; j++) {
            int minPos = j;
            for (int i = j+1; i < arr.length; i++) {
                minPos = arr[i] < arr[minPos] ? i : minPos;
            }
            swap(arr,minPos,j);
        }
        print(arr);
    }

    public static void swap(int[] arr,int i,int j){
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
    }

    public static void print(int arr[]){
        System.out.println(Arrays.toString(arr));
    }
}

优化之后
优化思想:选择排序每次循环是将最小值/最大值比较之后放到最数组最左侧,那么如果我们每次循环的时候,同时找出最小值和最大值并分别放到数组最左侧和最右侧。如此程序便得到了优化。
代码如下:

public class SelectionSort {
    public static void main(String[] args) {
        int[] arr = {10,4,5,8,17,6,11,12,23,32,34,95,54,56,77,88,21,43,45,65,23,43,23};
         int left = 0;
        int right = arr.length-1;
        while (left < right){
            int minPos = left;
            int maxPos = right;
            for (int i = left; i <= right; i++) {
                minPos = arr[i] < arr[minPos] ? i : minPos;
                maxPos = arr[i] > arr[maxPos] ? i : maxPos;
            }
            swap(arr,maxPos,right);
            // 处理特殊情况,最大值和最小值的位置刚好相反。
            if (minPos == right){
                minPos = maxPos;
            }
            swap(arr,minPos,left);
            left++;
            right--;
        }
        print(arr);
    }

    public static void swap(int[] arr,int i,int j){
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
    }

    public static void print(int arr[]){
        System.out.println(Arrays.toString(arr));
    }
}

冒泡排序及优化

特点

名称平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
冒泡排序(Bubble)n^2n^2n1稳定

思想
冒泡排序每次比较数组中相邻的两个元素。举个例子,五个高矮不同的人排队,此时排在第一个的人比第二个高一点,组长让他和第二个人换个位置。换完之后发现它比第三个人还是高一点(此时他在第二个人位置)。以此类推。
代码如下:

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr1 = {2,4,7,1,4,7,5,9,5,6,5,3}; 
        sort(arr1);     
        print(arr);     
    }

    static void sort(int[] arr){       
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {    
                if (arr[j] > arr[j + 1]) {                   
                    swap(arr, j);
                }
            }          
        }      
    }

    static void swap(int[] arr,int j){
        int temp = arr[j];
        arr[j] = arr[j+1];
        arr[j+1] = temp;
    }

    static void print(int[] arr){
        System.out.println(Arrays.toString(arr));
    }
}

然而上面的代码的时间复杂度 O(n^2) 但是冒泡算法的最好时间复杂度却为O(n),问题在于整个代码需要优化。
代码如下:

public class BubbleSort {

    public static void main(String[] args) {
        int[] arr = {2,4,7,1,4,7,5,9,5,6,5,3};
        int[] arr2 = {3, 2, 1, 5, 4, 4, 5, 5, 6, 7, 7, 9};
        sort(arr);
        sort(arr2);
        print(arr);
        System.out.println("---------------");
        print(arr2);
    }

    static void sort(int[] arr){
        boolean flag = false;
        int count = 0;
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
                count++;
                if (arr[j] > arr[j + 1]) {
                    flag = true;
                    swap(arr, j);
                }
            }
            if(!flag){
                break;
            }else {
                flag = false;
            }
        }
        System.out.println("循环次数: "+count);
    }

    static void swap(int[] arr,int j){
        int temp = arr[j];
        arr[j] = arr[j+1];
        arr[j+1] = temp;
    }

    static void print(int[] arr){
        System.out.println(Arrays.toString(arr));
    }
}

插入排序及优化

名称平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
插入排序(Insertion)n^2n^2n1稳定

思想
插入排序每次在待排序的区间内选定一个假设的最小值/最大值,保存角标和值。分别和已经排过序的区间一一比较,并插入合适位置,类似与打牌时,每次取出一张牌插入到手里已经排过序的牌里。
代码如下;


    public static void main(String[] args) {
        int[] arr = {13,5,24,5,1,23,16,13,87,23};
        sort(arr);
        print(arr);
    }

    static void sort(int[] arr){
        for (int i = 1; i < arr.length; i++) {
            for (int j = i; j > 0 && arr[j] < arr[j-1]; j--) {
                swap(arr,j,j-1);
            }
        }
    }
    static void swap(int[] arr,int i,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    static void print(int[] arr){
        System.out.println(Arrays.toString(arr));
    }

}

优化之后:

public static void main(String[] args) {
        int[] arr = {13,5,24,5,1,23,16,13,87,23};
        sort(arr);
        print(arr);
    }

   static void sort(int[] arr){
        int minIndex = 0;
        int minValue = 0;
        for (int i = 1; i < arr.length; i++) {
            minIndex = i;
            minValue = arr[i];
            for (int j = i; j > 0 && minValue < arr[j-1]; j--) {
                arr[j] = arr[j-1];
                minIndex = j-1;
            }
            if (minIndex !=  i){
                arr[minIndex] = minValue;
            }
        }
    }
   
    static void print(int[] arr){
        System.out.println(Arrays.toString(arr));
    }

}

希尔排序

特点

名称平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
希尔排序(shell)n^1.3n^2n1不稳定

思想
插入排序可以看成是一种间隔为1的希尔排序,希尔排序主要通过设定间隔(gap)来提高算法的效率。当间隔大的时候,移动次数少,当间隔小的时候,移动距离短。
代码如下:

public class ShellSort {

    public static void main(String[] args) {
        int[] arr = {32,4,1,3,54,65,1,3,5,32,567,23,33};
        sort(arr);
        print(arr);
    }
    
    static void sort(int[] arr){
        // 使用shell来设置间隔(本质上是插入排序)
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < arr.length; i++) {
                for (int j = i; j > gap-1 ; j -= gap) {
                    if(arr[j] < arr[j-gap]){
                        swap(arr,j,j-gap);
                    }
                }
            }
        }
    }
     
    static void swap(int[] arr,int i,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    
    static void print(int[] arr){
        System.out.println(Arrays.toString(arr));
    }
    
}

优化之后:
由于shell排序是通过控制间隔来实现优化,所以好的间隔能够提高算法的效率
使用Knuth序列 公式h=3*h+1


public class ShellSort {

    public static void main(String[] args) {
        int[] arr = {32,4,1,3,54,65,1,3,5,32,567,23,33};
        sort(arr);
        print(arr);
    }
    
    static void sort(int[] arr){
        // 检测使用 knuth序列设置间隔
        int h = 1;
        while (h <= arr.length / 3){
            h = h * 3 + 1;
        }

        for (int gap = h; gap > 0; gap = (gap-1)/3) {
            for (int i = gap; i < arr.length; i++) {
                for (int j = i; j > gap-1 ; j-=gap) {
                    if(arr[j] < arr[j-gap]){
                        swap(arr,j,j-gap);
                    }
                }
            }
        }
    }
    
    static void swap(int[] arr,int i,int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    
    static void print(int[] arr){
        System.out.println(Arrays.toString(arr));
    }
}

归并排序

特点

名称平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
归并排序(merge)nlog2^nnlog2^nnlog2^nn稳定

思想
归并排序采用分治的思想,将大的数列通过递归不断划分成小数组,然后排序。在归并到一起。

public class MergeSort {

    public static void main(String[] args) {
        int[] arr = {3,4,7,2,6,2,5,3,5};
        sort(arr,0,arr.length-1);
        print(arr);
    }

    static void sort(int[] arr,int left,int right){
        if(left == right){
            return;
        }
        int mid = left + (right - left) / 2;
        // 左边排序
        sort(arr,left,mid);
        // 右边排序
        sort(arr,mid+1, right);
        merge(arr,left,mid+1,right);
    }

    static void merge(int[] arr,int leftPtr,int rightPtr, int rightBound){
        int mid = rightPtr - 1;
        int[] temp = new int[rightBound - leftPtr + 1];

        int i = leftPtr;
        int j = rightPtr;
        int k = 0;

        while (i <= mid && j <= rightBound){
           temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }
        while (i <= mid){
            temp[k++] = arr[i++];
        }
        while (j <= rightBound){
            temp[k++] = arr[j++];
        }
        for (int m = 0; m < temp.length; m++) {
            arr[leftPtr + m] = temp[m];
        }
    }

    static void print(int[] arr){
        System.out.println(Arrays.toString(arr));
    }

}

快速排序

特点

名称平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
快速排序(quick)nlog2^nn^2nlog2^nnlog2^n不稳定

思想
通过设定一个中间值,并以此值为间隔将数列分为大于该数值和小于该数值的区间。并通过递归,不断重复这个操作,最终实现数列有序。

public class QuickSort {
	// 设定最右边的值为中间值
    public static void main(String[] args) {
        int[] arr = {7,3,2,10,8,1,9,5,4,6};
        sort(arr,0,arr.length-1);
        print(arr);
    }

    static void sort(int[] arr,int leftBound,int rightBound){
        if(rightBound <= leftBound){
            return;
        }
        int mid = quick(arr, leftBound, rightBound);
        sort(arr,leftBound,mid-1);
        sort(arr,mid + 1,rightBound);
    }

    static int quick(int[] arr,int leftBound,int rightBound){
        int pivot = arr[rightBound];
        int left = leftBound;
        int right = rightBound - 1;

        while (left < right){
            while (arr[left] <= pivot && left <= right){
                left++;
            }
            while (arr[right] > pivot && left < right){
                right--;
            }
            if(left < right){
                swap(arr,left,right);
            }

        }
        if(pivot < arr[left]){
            swap(arr,left,rightBound);
        }
        return left;
    }

    static void swap(int[] arr,int l,int r){
        int temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;
    }

    static void print(int[] arr){
        System.out.println(Arrays.toString(arr));
    }
}

// 以最左侧的值为中间值
static int quick(int[] arr,int leftPst,int rightPst){
        int pivot = arr[leftPst];
        int left = leftPst;
        int right = rightPst;
        while (left < right){
            while (arr[right] > pivot && left < right){
                right--;
            }
            if(left < right){
                arr[left] = arr[right];
                left++;
            }
            while (arr[left] < pivot && left < right) {
                left++;
            }
            if(left < right){
                arr[right] = arr[left];
                right--;
            }
        }
        arr[left] = pivot;
        return left;
    }

计数排序

特点

名称平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
计数排序(counting)n+kn+kn+kn+k稳定

思想
桶排序的思想,针对数据量大,范围小的数据

public class CountSort {

    public static void main(String[] args) {
        int[] arr = {1,5,7,6,10,4,6,2,7,1,2,4,5,8,9,3,4,2};
        int max = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if(arr[i] > max){
                max = arr[i];
            }
        }
        int[] sort = sort(arr, max);
        print(sort);
    }

    static int[] sort(int[] arr,int max){
        // 定义一个范围的数组
        int[] result = new int[arr.length];
        int count[] = new int[max+1];
        for (int i = 0; i < arr.length; i++) {
            count[arr[i]]++;
        }
        // 算法是不稳定的
        for (int i = 0, k = 0; i < count.length; i++) {
            while (count[i]-- > 0){
                result[k++] = i;
            }
        }
     
        return result;

    }

    static void print(int[] arr){
        System.out.println(Arrays.toString(arr));
    }
}

如上的算法是不稳定的。可以通过小技巧来处理,通过将Count数组的每两位值相加,这样可以通过后一个减去前一个得到当前count记录的“桶”里的个数。然后将原数组倒序取出,并通过处理后的count放进新的数组中。由于count对“桶”内个数累加的时候是正序,最后通过倒叙取出并不会改变原相同数据的相对前后位置。

public class CountSort {

    public static void main(String[] args) {
        int[] arr = {1,5,7,6,10,4,6,2,7,1,2,4,5,8,9,3,4,2};
        int max = arr[0];
        for (int i = 0; i < arr.length; i++) {
            if(arr[i] > max){
                max = arr[i];
            }
        }
        int[] sort = sort(arr, max);
        print(sort);
    }

    static int[] sort(int[] arr,int max){
        // 定义一个范围的数组
        int[] result = new int[arr.length];
        int count[] = new int[max+1];
        for (int i = 0; i < arr.length; i++) {
            count[arr[i]]++;
        }
        // 解决技巧
        for (int i = 1; i < count.length; i++) {
            count[i] = count[i] + count[i-1];
        }
        for (int i = arr.length-1; i >= 0; i--) {
            result[--count[arr[i]]] = arr[i];
        }
        return result;

    }

    static void print(int[] arr){
        System.out.println(Arrays.toString(arr));
    }

}

基数排序

特点

名称平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度稳定性
基数排序(radix)n*kn*kn*kn+k稳定

public class RadixSort {

    public static void main(String[] args) {
        int[] arr = {321,453,543,754,222,421};
        sort(arr);
        print(arr);
    }
    static void sort(int[] arr){
        int result[] = new int[arr.length];
        int count[] = new int[10];
        for (int i = 0; i < 3; i++) {
            int division = (int) Math.pow(10,i);
            for (int j = 0; j < arr.length; j++) {
                count[arr[j] / division % 10]++;
            }
            for (int m = 1; m < count.length; m++) {
                count[m] = count[m] + count[m-1];
            }
            for (int n = arr.length-1; n >= 0 ; n--) {
                result[--count[arr[n] / division % 10]] = arr[n];
            }
            System.arraycopy(result,0,arr,0,arr.length);
            // 将Count数组全部填充为0
            Arrays.fill(count,0);
        }
    }
    static void print(int[] arr){
        System.out.println(Arrays.toString(arr));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值