Java -- 常见的排序算法及Java实现

- 说明

- 术语说明

  • 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
  • 不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
  • 内排序:所有排序操作都在内存中完成;
  • 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
  • 时间复杂度: 一个算法执行所耗费的时间。
  • 空间复杂度:运行完一个程序所需内存的大小。

- 算法分类

在这里插入图片描述

  • 比较和非比较的区别
    常见的快速排序、归并排序、堆排序、冒泡排序等属于比较排序。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置。
    在冒泡排序之类的排序中,问题规模为n,又因为需要比较n次,所以平均时间复杂度为O(n²)。在归并排序、快速排序之类的排序中,问题规模通过分治法消减为logN次,所以时间复杂度平均O(nlogn)。

  • 比较排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。

  • 计数排序、基数排序、桶排序则属于非比较排序。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置。

  • 非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度O(n)。

  • 非比较排序时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。

- 算法性能

在这里插入图片描述

- 图片名词解释图片名词解释:

  • n: 数据规模
  • k: “桶”的个数
  • In-place: 占用常数内存,不占用额外内存
  • Out-place: 占用额外内存

1. 冒泡排序,BubbleSort

算法思想:

  1. 首先从数组的第一个元素开始到数组最后一个元素为止,对数组中相邻的两个元素进行比较
  2. 如果位于数组左端的元素大于数组右端的元素,则交换这两个元素在数组中的位置
  3. 此时数组最右端的元素即为该数组中所有元素的最大值,不需再比较这个值
  4. 接着对该数组剩下的n-1个元素排序,直到整个数组有序排列

代码实现:

import java.util.Arrays;

public class BubbleSort {
    public static void main(String[] args) {
        int[] dataArr = {3,2,5,4,1,6,1,3,2,5,4,6};
        bubbleSort(dataArr);
        System.out.println(Arrays.toString(dataArr));
    }

   private static void bubbleSort(int[] dataArr) {
   		//循环 length - 1 次
       for (int i = 0; i < dataArr.length - 1; i++) {
	       //是否有序标识,如果当轮后,已经有序,则后续不再排序,直接退出
           boolean orderedFlag = false;
       	   //需要减 i ,  因为每轮最后一个元素不需要再比较
           for (int j = 0; j < dataArr.length - i - 1; j++) {
                if(dataArr[j] > dataArr[j + 1]){
                    int temp = dataArr[j];
                    dataArr[j] = dataArr[j + 1];
                    dataArr[j + 1] = temp;
                    orderedFlag = true;
                }
           }
           if (!orderedFlag) {
               break;
           }
       }
    }
}

动态图:
在这里插入图片描述
复杂度和稳定性:

  1. 如果数据正序,只需要走一趟即可完成排序。所需的比较次数C和记录移动次数M均达到最小值,
    即:Cmin=n-1;Mmin=0;所以,冒泡排序最好的时间复杂度为O(n)
  2. 如果很不幸数据是反序的,则需要进行n-1趟排序。每趟排序要进行n-i次比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:
    最坏情况下时间复杂度为O(n2)
  3. 所以,冒泡排序总的平均时间复杂度为:O(n2)
  4. 空间复杂度:O(1)
  5. 稳定性:稳定

2. 选择排序,SelectionSort

基本选择排序,算法思想:

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
  2. 然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
  3. 以此类推,直到所有元素均排序完毕

二元选择排序,算法思想:

  1. 选择一个当前位置i,从开始到序列一半位置。每次遍历产生一个最大值,一个最小值。
  2. 将产生的最小值和最大值分别与当前位置i和序列长度-i上的元素交换

算法说明:

  1. 表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
  2. 二元选择排序,时间要比普通选择排序快一倍

代码实现:

import java.util.Arrays;

/**
 * @Author: TheBigBlue
 * @Description: 选择排序
 * @Date: 2020/6/1
 */
public class SelectionSort {
    public static void main(String[] args) {
        int[] dataArr = {3,4,5,1,6,2};
        simpleSelectionSort(dataArr);
        binarySelectionSort(dataArr);
        System.out.println(Arrays.toString(dataArr));
    }

    /**
      * @Author: TheBigBlue
      * @Description: 普通的选择排序
      * @Date: 2020/6/1
      * @Param:
      * @return:
      **/
    private static void simpleSelectionSort(int[] dataArr) {
        if (dataArr != null && dataArr.length > 0) {
            for (int i = 0; i < dataArr.length - 1; i++) {
                int min = i;
                for (int j = i + 1; j < dataArr.length; j++) {
                    if (dataArr[j] < dataArr[min]){
                        min = j;
                    }
                }
                if(min != i) {
                    int temp = dataArr[i];
                    dataArr[i] = dataArr[min];
                    dataArr[min] = temp;
                }
            }
        }
    }

    /**
      * @Author: TheBigBlue
      * @Description: 优化的选择排序:二元选择排序
      * @Date: 2020/6/1
      * @Param:
      * @return:
      **/
    private static void binarySelectionSort(int[] dataArr) {
        if (dataArr != null && dataArr.length > 0) {
            for (int i = 0; i <= dataArr.length/2; i++) {
                int minIndex = i;
                int maxIndex = i;
                //循环一轮,找出最小值
                for (int j = i + 1; j < dataArr.length - i; j++) {
                    if(dataArr[minIndex] > dataArr[j]){
                        minIndex = j;
                        continue;
                    }
                    if (dataArr[j] > dataArr[maxIndex]) {
                        maxIndex = j;
                    }
                }
                //将当前值和最小值交换
                if(minIndex != i){
                    int temp = dataArr[i];
                    dataArr[i] = dataArr[minIndex];
                    dataArr[minIndex] = temp;
                }
                //将当前循环的最后一个位置和最大索引所在位置交换
                if (maxIndex != i) {
                    int temp = dataArr[dataArr.length - 1 - i];
                    dataArr[dataArr.length - 1 - i] = dataArr[maxIndex];
                    dataArr[maxIndex] = temp;
                }
            }
        }
    }
}

动态图:
在这里插入图片描述
复杂度和稳定性:

  1. 最优:O(n2), 最差:O(n2), 平均:O(n2)
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定

3. 插入排序,InsertionSort

算法思想:

  1. 通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间

算法说明:

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5。
  7. 插入排序更适合小规模数据集或者基本有序数据集比较高效

代码实现:

import java.util.Arrays;

/**
 * @Author: TheBigBlue
 * @Description: 插入排序
 * @Date: 2020/6/1
 */
public class InsertionSort {

    public static void main(String[] args) {
        int[] dataArr = {3,4,5,1,6,2};
        insertionSort(dataArr);
        System.out.println(Arrays.toString(dataArr));
    }

    private static void insertionSort(int[] dataArr) {
        if (dataArr != null && dataArr.length > 0) {
            int currEle, preIndex;
            for (int i = 0; i < dataArr.length - 1; i++) {
                currEle = dataArr[i + 1];
                preIndex = i;
                //从后往前比,找到比当前元素小的,咋插入小元素后面
                while (preIndex >= 0 && currEle < dataArr[preIndex]) {
                    dataArr[preIndex + 1] = dataArr[preIndex];
                    preIndex--;
                }
                dataArr[preIndex + 1] = currEle;
            }
        }
    }
}

动态图:
在这里插入图片描述
复杂度和稳定性:

  1. 最优:O(n), 最差:O(n2), 平均:O(n2)
  2. 空间复杂度:O(1)
  3. 稳定性:稳定

4. 希尔排序,ShellSort

算法思想:

  1. 先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序

算法说明:

  1. 希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一
  2. 希尔排序是基于插入排序的以下两点性质而提出改进方法的:
    - 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
    - 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

代码实现:

import java.util.Arrays;

public class ShellSort {

    public static void main(String[] args) {
        int[] dataArr = {3,4,5,1,6,2};
        shellSort(dataArr);
        System.out.println(Arrays.toString(dataArr));
    }

    private static void shellSort(int[] dataArr) {
        int len = dataArr.length;
        int currEle, preIndex, gap = len / 2;
        while (gap > 0) {
            for (int i = gap; i < len; i++) {
                currEle = dataArr[i];
                preIndex = i - gap;
                while (preIndex >= 0 && dataArr[preIndex] > currEle) {
                    dataArr[preIndex + gap] = dataArr[preIndex];
                    preIndex -= gap;
                }
                dataArr[preIndex + gap] = currEle;
            }
            gap /= 2;
        }
    }
}

动态图:

在这里插入图片描述
复杂度和稳定性:

  1. 时间复杂度:O(n1.3 – n2.0)
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定

5. 归并排序,MergeSort

算法思想:

  1. 归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

算法说明:
  和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。
  归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
  因为归并排序需要创建额外内存,所以小数据量效率不如快排,但是当数据量达到亿级以上,归并的优势就显现出来了,因为归并的最差时间复杂度也是nlogn,而快排最差是n^2

代码实现:

import java.util.Arrays;

public class MergeSort {
    public static void main(String[] args) {
        int[] dataArr = {3, 4, 5, 1, 6, 2};
        //辅助数组
        int[] tmp = new int[dataArr.length];
        mergeSort(dataArr, 0, dataArr.length - 1, tmp);
        System.out.println(Arrays.toString(dataArr));
    }

    public static void mergeSort(int[] dataArr, int left, int right, int[] tmp) {
        //当子序列中只有一个元素时结束递归
        if (left < right) {
            //划分子序列
            int mid = left + (right - right) / 2;
            //对左侧子序列进行递归排序
            mergeSort(dataArr, left, mid, tmp);
            //对右侧子序列进行递归排序
            mergeSort(dataArr, mid + 1, right, tmp);
            //合并
            merge(dataArr, left, mid, right, tmp);
        }
    }

    //两路归并算法,两个排好序的子序列合并为一个子序列
    public static void merge(int[] dataArr, int left, int mid, int right, int[] tmp) {
        //p1、p2是检测指针,k是存放指针
        int p1 = left, p2 = mid + 1, k = left;
        while (p1 <= mid && p2 <= right) {
            tmp[k++] = dataArr[p1] < dataArr[p2] ? dataArr[p1++] : dataArr[p2++];
        }
        //如果第一个序列未检测完,直接将后面所有元素加到合并的序列中
        while (p1 <= mid) tmp[k++] = dataArr[p1++];
        //同上
        while (p2 <= right) tmp[k++] = dataArr[p2++];
        //复制回原素组
        for (int i = left; i <= right; i++) {
            dataArr[i] = tmp[i];
        }
    }
}

动态图:
在这里插入图片描述
复杂度和稳定性:

  1. 最优:O(n), 最差:O(nlogn), 平均:O(nlogn)
  2. 空间复杂度:O(n)
  3. 稳定性:稳定

6. 快速排序,QuickSort

算法思想:

  1. 先从数列中取出一个数作为基准数。
  2. 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
  3. 再对左右区间重复第二步,直到各区间只有一个数。

算法说明:
  快速排序是一种排序执行效率很高的排序算法,它利用分治法来对待排序序列进行分治排序。
  快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。

代码实现:

import java.util.Arrays;

public class QuickSort {
    public static void main(String[] args) {
        int[] dataArr = {3,2,5,4,1,6,1,3,2,5,4,6};
        quickSort(dataArr, 0, dataArr.length - 1);
        System.out.println(Arrays.toString(dataArr));
    }

    private static void quickSort(int[] dataArr, int left, int right) {
        if(left >= right){
            return;
        }
        //设置最左边的元素为基准值
        int key = dataArr[left];
        int i = left;
        int j = right;
        while(i < j) {
        	// j向左移,直到遇到比key小的值
            while(dataArr[j] >= key && i < j){
                j--;
            }
            // i向右移,直到遇到比key大的值
            while(dataArr[i] <= key && i < j){
                i++;
            }
            int temp = dataArr[i];
            dataArr[i] = dataArr[j];
            dataArr[j] = temp;
        }
        dataArr[left] = dataArr[i];
        dataArr[i] = key;
        quickSort(dataArr, left, i - 1);
        quickSort(dataArr, i + 1, right);
    }
}

动态图:
在这里插入图片描述
复杂度和稳定性:

  1. 最坏情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序)
    这种情况时间复杂度就好计算了,就是冒泡排序的最差时间复杂度:O(n2)

  2. 最好情况下是O(nlog2n),推导过程如下:(递归算法的时间复杂度公式:T[n] = aT[n/b] + f(n) )
    在这里插入图片描述

  3. 所以平均时间复杂度为O(nlogn)

  4. 空间复杂度:O(logn)

  5. 稳定性:不稳定

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值