排序算法总结

冒泡排序

1.算法思路

从后往前,两两进行比较,将较小的元素移动到数组靠前的位置,循环这个过程,直到所有的元素按从小到大的顺序排列

2.代码实现
public class Sort {

    //冒泡排序,从后往前,将较小值一个一个往前面冒
    private void bubbleSort2(int[] nums) {
        int n = nums.length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = n - 1; j >= i + 1; j--) {
                if (nums[j] < nums[j - 1]) {
                    swap(nums, j, j - 1);
                }
            }
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

3.复杂度分析
  • 时间复杂度:需要两层循环,所以时间复杂度为O(n2)。
  • 空间复杂度:不需要额外的内存空间,所以空间复杂度为O(1)。

简单选择排序

1.算法思路

由于冒泡排序每次比较,满足条件就会执行一次交换操作,选择排序为了减少交换次数,将每一次的最小值记录下来,最后再与当前元素交换。

2.代码实现
public class Sort {

    //选择排序,首先定义一个min下标,用来记录当前最小值,当某个值小于他时,跟新
    //最后将最小值与当前游标所在值交换
    private void chooseSort(int[] nums) {
        int n = nums.length;
        for (int i = 0; i < n - 1; i++) {
            int min = i;
            for (int j = i + 1; j < n; j++) {
                if (nums[min] > nums[j]) {
                    min = j;
                }
            }
            swap(nums, min, i);
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

}

3.复杂度分析
  • 时间复杂度:需要两层循环,所以时间复杂度为O(n2)。
  • 空间复杂度:需要额外常数级别的内存空间,所以空间复杂度为O(1)。

直接插入排序

1.算法思路

直接插入排序的思想是,维护一个已经排好序的列表,当遍历到列表后未排序的当前元素时,找到元素在之前列表中对应的位置,然后插入到这个位置,重复这个过程,直到所有的元素有序

2.代码实现
public class Sort {

    //记录当前游标和对应的值,然后把他插入到正确的位置,(游标前的元素都是排好序的)
    private void insertSort(int[] nums) {
        int n = nums.length;
        for (int i = 1; i < n; i++) {
            int j = i;
            int temp = nums[i];
            while (j > 0 && temp < nums[j - 1]) {
                nums[j] = nums[j - 1];
                j--;
            }
            nums[j] = temp;
        }
    }
}

3.复杂度分析
  • 时间复杂度:需要两层循环,所以时间复杂度为O(n2)。
  • 空间复杂度:需要额外常数级别的内存空间,所以空间复杂度为O(1)。

希尔排序

1.算法思路

希尔排序是插入排序的改进版,也是最先发现的高效排序算法。
与插入排序相比,希尔排序多了一个间隔的设置,间隔的设置可以自行决定。比如下面的实现,将初始间隔设置为数组长度一半,每次减半,直到间隔为1。然后将间隔处的几个元素进行插入排序(比如数组长度为8,间隔为4时,比较的是索引为0,4;1,5;2,6;3,7处的元素),排好之后,再缩小间隔,重复这个过程,直到间隔为1,所有元素排好为止。

2.代码实现
public class Sort {

    //设置一个排序间隔,假设数组总长度为8,不妨设初始间隔为4,然后每隔4个元素
    //进行一次插入排序,所有元素都排好后,再缩小间隔为2,重复上一步的操作,直到
    //间隔为1,则完成整个数组的排序
    private void shellSort(int[] nums) {
        int n = nums.length;
        for(int gap=n/2;gap>0;gap/=2){
            for (int i = gap; i < n; i++) {
                int j = i;
                int temp = nums[i];
                while (j >=gap && temp < nums[j - gap]) {
                    nums[j] = nums[j - gap];
                    j-=gap;
                }
                nums[j] = temp;
            }
        }

    }
}

3.复杂度分析
  • 时间复杂度:希尔排序的时间复杂度与gap的选取有关,平均时间复杂度为O(nlogn)~O(n2)。
  • 空间复杂度:需要额外常数级别的内存空间,所以空间复杂度为O(1)。

堆排序

1.算法思路

堆排序是简单选择排序的改进版。
按从小到大排序,需要借助大顶堆。首先遍历整个数组,构建一个大顶堆。然后再进行遍历,交换堆顶元素与数组末尾元素(由于堆顶元素总是最大的,所以最大的都被移动到数组末尾),然后重新调整成大顶堆,循环遍历,直到所有元素有序。
adjust方法用于构造大顶堆,其思路是比较当前节点,左孩子,右孩子三者的大小,将最大的放到当前节点,然后重复这个过程,直到根节点是最大节点。

2.代码实现

迭代:

public class Sort {

    //堆排序
    private void heapsort(int[] nums) {
        int n = nums.length;
        for (int i = n / 2 - 1; i >= 0; i--) {
            adjust(nums, i, n - 1);
        }
        for (int i = n - 1; i >= 1; i--) {
            swap(nums, i, 0);
            adjust(nums, 0, i - 1);
        }
    }

    private void adjust(int[] nums, int s, int m) {
        int j, temp = nums[s];
        for (j = 2 * s + 1; j <= m; j = j * 2 + 1) {
            while (j < m && nums[j] < nums[j + 1]) {
                j++;
            }
            if (temp >= nums[j]) break;
            nums[s] = nums[j];
            s = j;
        }
        nums[s] = temp;
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

递归:

public class Sort {
    private void heapsort(int[] nums) {
        int n=nums.length;
        for(int i=n/2-1;i>=0;i--){
            adjust(nums,i,n-1);
        }
        for(int i=n-1;i>=1;i--){
            swap(nums,0,i);
            adjust(nums,0,i-1);
        }
    }
    private void adjust(int[] nums,int s,int m){
        if(s>=m) return;
        int left=2*s+1;
        int right=2*s+2;
        int maxIdx=s;
        if(left<=m&&nums[left]>nums[maxIdx]) maxIdx=left;
        if(right<=m&&nums[right]>nums[maxIdx]) maxIdx=right;
        if(maxIdx!=s){
            swap(nums,maxIdx,s);
            adjust(nums,maxIdx,m);
        }
    }
    private void swap(int[] nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
}
3.复杂度分析
  • 时间复杂度:adjust建堆的时间复杂度为O(logn),最多需要循环n次,所以平均时间复杂度为O(nlogn)。
  • 空间复杂度:需要额外常数级别的内存空间,所以空间复杂度为O(1)。

归并排序

1.算法思路

归并排序是几个高效排序算法中唯一稳定的排序算法。
归并排序的思路是先将数组两两分开,每部分排好序后,再两两进行合并。排序合并的过程中,需要借助一个辅助数组,将排好序的元素先加入辅助数组,再转到原数组对应位置。

2.代码实现
public class Sort {
    //归并排序
    private void mergesort(int[] nums, int low, int high) {
        if (low >= high) return;
        int middle = low + (high - low) / 2;
        mergesort(nums, low, middle);
        mergesort(nums, middle + 1, high);
        merge(nums, low, middle, high);
    }

    private void merge(int[] nums, int low, int middle, int high) {
        int[] temp = new int[high - low + 1];
        int index = high - low;
        int i = middle, j = high;
        while (i >= low && j >= middle + 1) {
            if (nums[i] > nums[j]) {
                temp[index--] = nums[i--];
            } else {
                temp[index--] = nums[j--];
            }
        }
        while (i >= low) {
            temp[index--] = nums[i--];
        }
        while (j >= middle + 1) {
            temp[index--] = nums[j--];
        }
        for (int k = 0; k < temp.length; k++) {
            nums[k + low] = temp[k];
        }
    }

}

3.复杂度分析
  • 时间复杂度:merge的时间复杂度为O(n),需要归并logn次,所以平均时间复杂度为O(nlogn)。
  • 空间复杂度:需要额外O(n)空间的辅助空间,所以空间复杂度为O(n)。

快速排序

1.算法思路

快速排序是冒泡排序的改进版,优化之后的快速排序是最快的排序算法。
快速排序的思想是选取一个枢纽值,让所有元素基本有序(前面的小于它,后面的大于它);然后再对前半部分以及后半部分进行同样的操作,让它们分别基本有序。直到所有的元素有序。
快速排序的优化,可以从以下四个地方考虑:

  • 枢纽值得选取,三值取中
  • 优化不必要得交换
  • 数据量较小时,使用插入排序,较大时,再改为快排
  • 优化递归操作,可以使用尾递归
2.代码实现
public class Sort {

    //快速排序
    private void qsort(int[] nums, int low, int high) {
        if (low < high) {
            int povit = partition(nums, low, high);
            qsort(nums, low, povit - 1);
            qsort(nums, povit + 1, high);
        }
    }

    private int partition(int[] nums, int low, int high) {
        int povitkey = nums[low];
        while (low < high) {
            while (low < high && povitkey <= nums[high]) high--;
            swap(nums, low, high);
            while (low < high && povitkey >= nums[low]) low++;
            swap(nums, low, high);
        }
        return low;
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

3.复杂度分析
  • 时间复杂度:partition的时间复杂度为O(n),qsort在理想状态下时间复杂度为O(logn),最坏情况下(取决于枢纽值得选取),递归深度为n,时间复杂度为O(n),所以综合下来,平均时间复杂度为O(nlogn)。
  • 空间复杂度:额外空间占用取决于递归栈得深度,所以空间复杂度为O(logn)~O(n)。

动图展示

可以参考https://visualgo.net/zh/sorting

排序算法对比

排序方法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序O(n2)O(n)O(n2)O(1)稳定
简单选择排序O(n2)O(n2)O(n2)O(1)稳定
直接插入排序O(n2)O(n)O(n2)O(1)稳定
希尔排序O(nlogn)~O(n2)O(n1.3)O(n2)O(1)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
快速排序O(nlogn)O(nlogn)O(n2)O(logn)~O(n)不稳定
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值