数据结构和算法——常用排序算法

253 阅读5分钟

前言:排序算法分类

img

排序算法的时间复杂度

这里主要关心平均时间复杂度和最坏情况即可。 image.png

1、冒泡排序法

1.1示意图

img

  • 从左往右遍历元素,比较相邻元素的大小,如果左大右小就交换
  • 每次外部循环都能找出最大的放在当次排序的最右边,直到排序完成

1.2代码实现

public class BubbleSort {
    public static void main(String[] args) {
        //随机产生100000个数
        int[] arr = new int[100000];
        for (int i = 0; i < arr.length; i++) {
            int value = new Random().nextInt(5000);
            arr[i] = value;
        }

        //记录排序开始时间和结束时间
        Date date1 = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String begin = sdf.format(date1);
        System.out.println("冒泡排序开始时间是" + begin);

        //排序
        bubbleSort(arr);

        Date date2 = new Date();
        String end = sdf.format(date2);
        System.out.println("冒泡排序结束时间是" + end);

        System.out.println(Arrays.toString(arr));

        /*
            冒泡排序开始时间是2022-01-28 10:36:34
            冒泡排序结束时间是2022-01-28 10:36:49
         */
    }

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

2、选择排序法

2.1示意图

img

  • 遍历数组,每次都把初始位置假设为最小值,记录minIndex
  • 然后从后一个位置遍历,都和假定的最小值比较,如果找到比它小的,就把这个索引设为minIndex
  • 最后交换minIndex位置的值和初始位置值,每次都找到当前遍历的最小值
  • 相比于冒泡排序法,省略很多交换的步骤

2.2代码实现

public class SelectSort {
    public static void main(String[] args) {
        //随机产生100000个数
        int[] arr = new int[100000];
        for (int i = 0; i < arr.length; i++) {
            int value = new Random().nextInt(5000);
            arr[i] = value;
        }

        //记录排序开始时间和结束时间
        Date date1 = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String begin = sdf.format(date1);
        System.out.println("选择排序开始时间是" + begin);

        //排序
        selectSort(arr);

        Date date2 = new Date();
        String end = sdf.format(date2);
        System.out.println("选择排序结束时间是" + end);

        System.out.println(Arrays.toString(arr));

        /*
            选择排序开始时间是2022-01-28 10:53:44
            选择排序结束时间是2022-01-28 10:53:48
         */
    }

    public static void selectSort(int[] arr){
        for (int i = 0; i < arr.length; i++) {
            //把当前第一个位置当作最小值,记录当前索引
            int minIndex = i;
            //记录是否进行过交换
            boolean flag = false;
            int j;
            for (j = i+1; j < arr.length; j++) {
                //如果发现比预设最小值更小
                if (arr[minIndex] > arr[j]){
                    //索引交换
                    minIndex = j;
                    //记录确实交换过了
                    flag = true;
                }
            }
            //上面遍历过后,会找到当次最小值,交换
            if (flag){
                int temp = arr[i];
                arr[i] = arr[minIndex];
                arr[minIndex] = temp;
            }
        }
    }
}

3、快速排序法

3.1示意图

img

  • 首先要取一个标准值,这里采用初始位置的值。设置两个指针一个low指针,一个high指针,分别位于两端。
  • 然后从最右侧开始遍历,如果值比标准值大,那就把high指针前移,找到第一个比标准值小的,和low位置的元素交换。
  • 换过一次之后,就从low开始遍历,如果值比标准值小,就把low指针后移,找到第一个比标准值大的,和high位置交换。
  • 经过多次交换,会出现low和high重叠的情况,这个时候把标准值赋值给这个位置,这样,这个位置的前面都是比它小的,后面都是比它大的。然后再次循环,直到每次只比较一个元素,这样比完才是真正的有序。

3.2代码实现

public class QuickSort {
    public static void main(String[] args) {
        //随机产生100000个数
        int[] arr = new int[100000];
        for (int i = 0; i < arr.length; i++) {
            int value = new Random().nextInt(5000);
            arr[i] = value;
        }

        //记录排序开始时间和结束时间
        Date date1 = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String begin = sdf.format(date1);
        System.out.println("快速排序开始时间是" + begin);

        //排序
        quickSort(arr,0,arr.length - 1);

        Date date2 = new Date();
        String end = sdf.format(date2);
        System.out.println("快速排序结束时间是" + end);

        System.out.println(Arrays.toString(arr));

        /*
            快速排序开始时间是2022-01-28 11:13:52
            快速排序结束时间是2022-01-28 11:13:52
         */
    }

    public static void quickSort(int[] arr,int start,int end){
        if (start < end){
            int standard = arr[start];
            int low = start;
            int high = end;
            while (low < high){
                while (low < high && arr[high] >= standard){
                    high--;
                }
                arr[low] = arr[high];
                while (low < high && arr[low] <= standard){
                    low++;
                }
                arr[high] = arr[low];
            }
            arr[low] = standard;
            quickSort(arr,start,low);
            quickSort(arr,low+1,end);
        }
    }
}

4、插入排序

4.1示意图

img

  • 从初始位置的后一个位置开始向后遍历,每次都对当前位置及前面进行排序
  • 如果当前位置小于前面的一个元素值,说明当前的这个元素可以插入到前面已经排好序的数组的某个位置去,然后向前遍历找到这个位置插入即可
  • 插入排序的好处是,前面已经排好序了,不需要像冒泡排序那样再次比较后面所有元素了,只需要找到比这个值小的就可以停下来了

4.2代码实现

public class InsertSort {
    public static void main(String[] args) {
        //随机产生100000个数
        int[] arr = new int[100000];
        for (int i = 0; i < arr.length; i++) {
            int value = new Random().nextInt(5000);
            arr[i] = value;
        }

        //记录排序开始时间和结束时间
        Date date1 = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String begin = sdf.format(date1);
        System.out.println("插入排序开始时间是" + begin);

        //排序
        insertSort(arr);

        Date date2 = new Date();
        String end = sdf.format(date2);
        System.out.println("插入排序结束时间是" + end);

        System.out.println(Arrays.toString(arr));

        /*
            插入排序开始时间是2022-01-28 11:05:46
            插入排序结束时间是2022-01-28 11:05:47
         */
    }

    public static void insertSort(int[] arr){
        //从1的位置开始遍历
        for (int i = 1; i < arr.length; i++) {
            //去和它前一个位置比较
            if(arr[i] < arr[i-1]){
                //如果大于前一个元素就记录这个元素
                int temp = arr[i];
                int j;
                //然后前一个元素开始向前遍历,如果比这个值大就后移
                for (j = i-1; j >= 0 && arr[j] > temp; j--) {
                    arr[j+1] = arr[j];
                }
                arr[j+1] = temp;
            }
        }
    }
}

5、归并排序

5.1示意图

img

  • 基本思路时递归的对数组进行对半分割,然后把两个进行排序
  • 实现的方式是这种不断的递归会一直平分,直到到对每个元素进行排序,两两排序,最后一直成为一个有序的数组,说着不方便,还是直接看动图吧

5.2代码实现

public class MergeSort {
    public static void main(String[] args) {
        //随机产生100000个数
        int[] arr = new int[100000];
        for (int i = 0; i < arr.length; i++) {
            int value = new Random().nextInt(5000);
            arr[i] = value;
        }

        //记录排序开始时间和结束时间
        Date date1 = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String begin = sdf.format(date1);
        System.out.println("归并排序开始时间是" + begin);

        //排序
        mergeSort(arr,0,arr.length - 1);

        Date date2 = new Date();
        String end = sdf.format(date2);
        System.out.println("归并排序结束时间是" + end);

        System.out.println(Arrays.toString(arr));

        /*
            归并排序开始时间是2022-01-28 12:29:33
            归并排序结束时间是2022-01-28 12:29:33
         */
    }

    public static void mergeSort(int[] arr,int low,int high){
        int middle = (low + high)/2;
        //这个递归到每个数组只有1个元素的情况,也就是low = high时
        if (low < high){
            mergeSort(arr,low,middle);
            mergeSort(arr,middle+1,high);
            merge(arr,low,middle,high);
        }
    }

    public static void merge(int[] arr,int low,int middle,int high){
        //先创建一个临时数组
        int[] temp = new int[high - low + 1];
        //这传递的虽然是一个数组,但是由两个有序的数组组成
        //给这两个数组的开头设置指针用于遍历
        int i = low;
        int j = middle+1;
        //设置一个索引用于给temp放元素
        int index = 0;

        //循环遍历两个数组,退出条件是直到某一个数组遍历完了
        while (i <= middle && j <= high){
            //如果左边的值小,就把左边的放入temp,以达到排序的目的
            if (arr[i] <= arr[j]){
                temp[index] = arr[i];
                i++;
            }else{  //相反,把右边的放入
                temp[index] = arr[j];
                j++;
            }
            index++;
        }

        //有一个遍历完了,剩下的是有序的数组,直接都加进来就行
        while (i <= middle){
            temp[index] = arr[i];
            i++;
            index++;
        }
        while (j <= high){
            temp[index] = arr[j];
            j++;
            index++;
        }

        //最后把temp赋值给原来的数组
        for (int k = 0; k < temp.length; k++) {
            //因为low可能不为0,所以要放在指定的位置上
            arr[k+low] = temp[k];
        }
    }
}

6、希尔排序

6.1示意图

img

  • 对数组按间距分割,每个间距的元素进行排序
  • 然后再间距调小,再排序,直到最后间距为1时,就是对所有元素进行排序

6.2代码实现

public class ShellSort {
    public static void main(String[] args) {
        //随机产生100000个数
        int[] arr = new int[100000];
        for (int i = 0; i < arr.length; i++) {
            int value = new Random().nextInt(5000);
            arr[i] = value;
        }

        //记录排序开始时间和结束时间
        Date date1 = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String begin = sdf.format(date1);
        System.out.println("希尔排序开始时间是" + begin);

        //排序
        shellSort(arr);

        Date date2 = new Date();
        String end = sdf.format(date2);
        System.out.println("希尔排序结束时间是" + end);

        System.out.println(Arrays.toString(arr));

        /*
            希尔排序开始时间是2022-01-28 02:13:55
            希尔排序结束时间是2022-01-28 02:14:01
         */
    }

    public static void shellSort(int[] arr) {
        //最外层的作用是每次把跨度减少一半的
        for (int d = arr.length / 2; d > 0; d /= 2) {
            //遍历所有元素
            for (int i = d; i < arr.length; i++) {
                //同间距的比较
                for (int j = i - d; j >= 0; j -= d) {
                    if (arr[j] > arr[j + d]) {
                        int temp = arr[j];
                        arr[j] = arr[j + d];
                        arr[j + d] = temp;
                    }
                }
            }
        }
    }
}

7、基数排序

7.1示意图

img
  • 基数排序的原理比较简单,创建一个二维数组,按照从0-9的数字排序,这就表示一个篮子
  • 分别按照个位、十位、百位....最高位的顺序,把各个元素相应位置的值为索引,放入指定的篮子中,然后再按照顺序取出,这样经过从个位~最高位的顺序依次存入并取出,得到的就是有序的数组

7.2代码实现

public class RadixSort {
    public static void main(String[] args) {
        //随机产生100000个数
        int[] arr = new int[100000];
        for (int i = 0; i < arr.length; i++) {
            int value = new Random().nextInt(5000);
            arr[i] = value;
        }

        //记录排序开始时间和结束时间
        Date date1 = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String begin = sdf.format(date1);
        System.out.println("基数排序开始时间是" + begin);

        //排序
        radixSort(arr);

        Date date2 = new Date();
        String end = sdf.format(date2);
        System.out.println("基数排序结束时间是" + end);

        System.out.println(Arrays.toString(arr));

        /*
            基数排序开始时间是2022-01-28 03:06:00
            基数排序结束时间是2022-01-28 03:06:00
         */
    }

    public static void radixSort(int[] arr){
        //找出最大值
        //先设置一个最大值,然后让它和所有元素进行比较替换
        int maxVal = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > maxVal){
                maxVal = arr[i];
            }
        }

        //有了最大值就可以知道到底有几位了,也就能知道外层循环要比较几次了
        int maxLength = (maxVal + "").length();

        //创建用于存放数字的数组,每行肯定都是0-9这10个数字,纵向最多放arr.length个元素
        //也就是假设所有元素的某一位完全相同的情况
        int[][] temp = new int[10][arr.length];
        //需要一个数组来明确这一行的各个位置存放了几个元素
        int[] counts = new int[10];
        //进行循环遍历,这里要看到底有几位
        for (int i = 0,n = 1; i < maxLength; i++,n *= 10){
            //遍历每个数字,存放每个元素
            for (int j = 0; j < arr.length; j++) {
                //计算余数
                int remainer = arr[j] / n % 10;
                //计算了余数在temp中放入这个元素
                temp[remainer][counts[remainer]] = arr[j];
                //然后再把counts数组这个位置+1,以便后面能准确找到存放位置
                counts[remainer]++;
            }

            //元素都存放完了,该取出元素了
            //记录arr数组下标,便于读取元素
            int index = 0;
            for (int k = 0; k < counts.length; k++) {
                //如果counts中某一列不为0
                if (counts[k] != 0){
                    for (int l = 0; l < counts[k]; l++) {
                        arr[index] = temp[k][l];
                        index++;
                    }
                    //该行循环完后,还需要清空元素
                    counts[k] = 0;
                }
            }
        }
    }
}

8、桶排序

8.1示意图

img

  • 看图可以得知,先得到最大值和最小值,然后进行分桶
  • 然后把所有元素放入相应的桶中,桶中采用ArrayList进行排序
  • 最后取出就是有序的,感觉这种排序采用ArrayList等于没排,有了可用的数据类型排序还实现啥呀

8.2代码实现

public class BucketSort {
    public static void main(String[] args) {
        //随机产生100000个数
        int[] arr = new int[100000];
        for (int i = 0; i < arr.length; i++) {
            int value = new Random().nextInt(5000);
            arr[i] = value;
        }

        //记录排序开始时间和结束时间
        Date date1 = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String begin = sdf.format(date1);
        System.out.println("桶排序开始时间是" + begin);

        //排序
        int[] sortArr = bucketSort(arr);

        Date date2 = new Date();
        String end = sdf.format(date2);
        System.out.println("桶排序结束时间是" + end);

        System.out.println(Arrays.toString(sortArr));

        /*
            桶排序开始时间是2022-01-28 03:34:01
            桶排序结束时间是2022-01-28 03:34:01
         */
    }
    public static int[] bucketSort(int[] array){
        //得到数列的最大值和最小值,并计算出差值d
        int max=array[0];
        int min=array[0];
        for (int i=1;i<array.length;i++){
            if (array[i]>max){
                max=array[i];
            }
            if (array[i]<min){
                min=array[i];
            }
        }
        int d=max-min;

        //初始化桶
        int bucketNum=array.length;
        ArrayList<LinkedList<Integer>> bucketList=new ArrayList<LinkedList<Integer>>(bucketNum);
        for (int i=0;i<bucketNum;i++){
            bucketList.add(new LinkedList<Integer>());
        }

        //遍历原始数组将每个元素放入桶中
        for (int i=0;i<array.length;i++){
            int num=(int)((array[i]-min)*(bucketNum-1)/d);
            bucketList.get(num).add(array[i]);
        }

        //对每个桶内部进行排序
        for(int i=0;i<bucketList.size();i++){
            // 使用Collections.sort,其底层实现基于归并排序或归并排序的优化版本
            Collections.sort(bucketList.get(i));
        }

        //输出全部元素
        int[] sortedArray=new int[array.length];
        int index=0;
        for (LinkedList<Integer> list:bucketList) {
            for (int element:list){
                sortedArray[index]=element;
                index++;
            }
        }
        return sortedArray;
    }
}

9、堆排序

9.1示意图

堆排序
  • 首先对数组进行排序,变成最大堆,也就是每次都把最大的放上面,然后弹出去
  • 这样每次对最大堆的操作能保证数组是按顺序进行的,最后拿到的就是有序的

9.2代码实现

public class HeapSort {

    public static void main(String[] args) {
        //随机产生100000个数
        int[] arr = new int[100000];
        for (int i = 0; i < arr.length; i++) {
            int value = new Random().nextInt(5000);
            arr[i] = value;
        }

        //记录排序开始时间和结束时间
        Date date1 = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String begin = sdf.format(date1);
        System.out.println("堆排序开始时间是" + begin);

        //排序
        heapSort(arr);

        Date date2 = new Date();
        String end = sdf.format(date2);
        System.out.println("堆排序结束时间是" + end);

        System.out.println(Arrays.toString(arr));

        /*
            堆排序开始时间是2022-01-28 03:54:51
            堆排序结束时间是2022-01-28 03:54:51
         */
    }

    public static void heapSort(int[] arr) {
        //开始位置是最后一个非叶子节点,即最后一个节点的父节点
        int start = (arr.length-1)/2;
        //调整为大顶堆
        for(int i=start;i>=0;i--) {
            maxHeap(arr, arr.length, i);
        }
        //先把数组中的第0个和堆中的最后一个数交换位置,再把前面的处理为大顶堆
        for(int i=arr.length-1;i>0;i--) {
            int temp = arr[0];
            arr[0]=arr[i];
            arr[i]=temp;
            maxHeap(arr, i, 0);
        }
    }

    public static void maxHeap(int[] arr,int size,int index) {
        //左子节点
        int leftNode = 2*index+1;
        //右子节点
        int rightNode = 2*index+2;
        int max = index;
        //和两个子节点分别对比,找出最大的节点
        if(leftNode<size&&arr[leftNode]>arr[max]) {
            max=leftNode;
        }
        if(rightNode<size&&arr[rightNode]>arr[max]) {
            max=rightNode;
        }
        //交换位置
        if(max!=index) {
            int temp=arr[index];
            arr[index]=arr[max];
            arr[max]=temp;
            //交换位置以后,可能会破坏之前排好的堆,所以,之前的排好的堆需要重新调整
            maxHeap(arr, size, max);
        }
    }
}