七种常用的排序算法--思路加代码实现

列举了七种常用的排序算法的思路及代码实现
冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序、桶排序
代码上有很详细的注解,跟着代码的思路走,能够轻松理解这些算法
以后会开始刷leetCode的算法题,有趣的题目会写下来与大家分享,欢迎关注和讨论

冒泡排序

冒泡排序(bubble sorting)的基本思想是:通过对需要排序的序列从前向后(下标较小的元素开始),依次比较相邻元素的值,如果发现逆序的元素则交换,使值较大(或者值较小)的元素逐渐从前移到后

代码实现:
使用双重for循环来进行冒泡排序,外层for循环是为了将遍历整个数组,所以循环的次数是(数组的长度-1)次,因为数组的最后一位是不需要进行比较的。内层的for循环是将元素进行比较和换位

package test;

import java.util.Arrays;

public class DataStructureTest {
    public static void main(String[] args) {
        //将{3, 9, -1, 10, -2}从小到大进行排序
        int arr[] = {3, 9, -1, 10, -2};

        int temp = 0;//临时变量

        for(int j = 0; j < arr.length - 1; j++) {

            for(int i = 0; i < arr.length - 1 - j; i++) {
                if(arr[i] > arr[i + 1]) {//如果前面的数比后面的数要大,就交换
                    temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                }
            }

            System.out.println("第" + (j+1) + "次排序后的数组");
            System.out.println(Arrays.toString(arr));//输出数组
        }

    }
}

选择排序

选择排序(select sorting)是从需要排序的数据中,按照指定的规则选出某一元素,再按照规则交换位置后,达到排序的目的

选择排序的基本思想是:第一次从arr[0] ~ arr[n-1]中选出最小值,与arr[0]交换,第二次从arr[1]~ arr[n-1]中选出最小值,与arr[1]交换……,第i次从arr[i-1]~ arr[n-1]中选出最小值,与arr[i-1]交换……,第n-1次从arr[n-2]~ arr[n-1]中选出最小值,与arr[n-2]交换,总共通过n-1次排序,得到一个按排序码从小到大排列的有序序列

package test;

import java.util.Arrays;

public class DataStructureTest {
    public static void main(String[] args) {
        int[] arr = { 44, 33, 55, 22, 11, 22};

        System.out.println("排序前");
        System.out.println(Arrays.toString(arr));

        selectSort(arr);

        System.out.println("排序后");
        System.out.println(Arrays.toString(arr));
    }

    // 选择排序,数组为[44,33,55,22]
    public static void selectSort(int[] arr) {

        for (int i = 0; i < arr.length - 1; i++) {
            int minIndex = i;// 假定最小值的索引为i,即为第i个数据下标
            int min = arr[i];//假定最小值为arr的第i个数据
         
         /*
          * j从minIndex的下一个开始比较,所以for循环从1开始 
          * 同时要注意遍历的数组长度,因为j从1开始遍历,所以想要遍历arr.length-1个数据,只需使j< arr.length
          */
            for (int j = 1 + i; j < arr.length; j++) {
                if (min > arr[j]) {// 假定的第一个数据为最小值,并不是最小值
                    min = arr[j];
                    minIndex = j;
                }
            }
            //经过for循环,就已经找到了最小值及其索引,将最小值与arr[i]进行交换
            if (minIndex != i) {//如果最小值的索引为i,说明当前值就是最小值,没必要交换
                //将最小值与arr[i]进行交换
                arr[minIndex] = arr[i];
                arr[i] = min;
            }
        }
    }
}

插入排序

对需要排序的元素以插入的方式找该元素的适当位置,以达到排序的目的

插入排序的基本思想是,把n个待排序的元素看成一个有序表和一个无序表,开始时有序表只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把其排序码依次与有序表元素的排序码进行比较,把它插入到有序表的适当位置,使之成为新的有序表

package com.sort;

import java.util.Arrays;

public class InsertSort {
    public static void main(String[] args) {
        int[] arr = {44,33,55,22};
        insertSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    //插入排序,从小到大排序
    public static void insertSort(int[] arr) {
      /*
       * 要注意,i从1开始遍历,因为把数组的第一个元素作为有序表,以此为基准进行后续比较和排序
       * 因为数组的第一个元素不需要遍历,所以遍历的长度为数组的长度-1  
       */
        for(int i = 1; i < arr.length; i++) {
            int insertNum = arr[i];//定义待插入的数,为33
         /*
          * 定义待插入的索引,为待插入数的前一个数的下标
          * 因为遍历是从第i个数据开始的,第i个数据要向前插入有序表
          * 所以先和第i-1个的数据进行比较
          */
            int insertIndex = i - 1;
         
         /*
          * 给insertNum找到插入的位置
          * 1.insertIndex >= 0保证给insertNum找插入位置的时候,不越界
          * 2.insertNum < arr[insertIndex],如果待插入的数小于前一个数,说明还没有找到插入位置
          * 3.就需要将前一个数arr[insertIndex]后移
          */
            while(insertIndex >= 0 && insertNum < arr[insertIndex]) {
            /*
             * 将前一个数arr[insertIndex]后移,此时数组会变成{44,44,55,22}
             * 但是本来的arr[1]=33已经保存在insertNum之中,所以数据并没有丢失
             */
                arr[insertIndex + 1] = arr[insertIndex];
                insertIndex--;//再把索引往前移动,会再走一次while循环,进行比较,直到找到插入位置
            }
         /*
          * 所以退出while循环时,说明此时插入的位置已经找到,此时的插入位置是insertIndex+1
          * 因为退出while循环的条件是,insertNum > arr[insertIndex]或者insertIndex < 0,即要插入的数比arr[insertIndex]要大或者已经到达数组的第一个数据arr[insertIndex]的位置
          * 所以要插入的数的下标应该是insertIndex+1,即要插入的数放在arr[insertIndex]之后
          */
            arr[insertIndex + 1] = insertNum;
        }
    }
}

希尔排序

希尔排序也是一种插入排序,是简单插入排序经过改进之后的一个更为高效的版本,也称为缩小增量排序
希尔排序是把记录按下标的一定增量进行分组,对每组使用直接插入排序算法排序,随着增量的逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件就被分为一组,算法终止

希尔排序思路

  1. 初始增量=数组的长度/2,在确定初始增量后,分组的依据是,每隔增量-1的数据,就分为一组,并进行排序
  2. 缩小的增量=分组的组数/2,在确定缩小增量之后,每隔增量-1的数据,就分为一组,并进行排序
  3. 最后经过数次缩小增量排序,整个数组被分为唯一一组,相比初始数组,是更有序的数组
  4. 最后对这个唯一的数组进行两两比较和交换

要注意理解增量的概念

package com.sort;

import java.util.Arrays;

public class ShellSort {

    public static void main(String[] args) {
        int[] arr = {8,9,1,7,2,3,5,4,6,0};

        shellSort(arr);

    }

    //希尔排序,从小到大排序,对有序序列在插入时采用交换法
    public static void shellSort(int[] arr) {
        int temp = 0;

        //希尔排序的第一轮排序,将10个数据分成了5组
        for(int i = 5; i < arr.length; i++) {//i = 5开始for循环,i是分组的组数
         /*
          * 遍历各组中所有的元素,共5组,每组有两个元素,步长为5
          *j = j-5不是为了使for循环能再来一次,而是要保证for循环只进行一次
          * 因为这是第一次分组,每组只有两个数据,此for循环是为了能对这两个数据进行排序
          * 假如数组有14个数据,那么for循环的条件应该是int j = i - 7; j >= 0; j = j - 7
          * 当然i = 7,从7开始遍历
          */
            for(int j = i - 5; j >= 0; j = j - 5) {
                if(arr[j] > arr[j + 5]) {//如果第j个元素的值要大于第j+5个元素的值,就进行交换
                    temp = arr[j + 5];
                    arr[j + 5] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        System.out.println("希尔排序第一轮排序" + Arrays.toString(arr));
      
      /*
       * 从希尔排序的第一轮排序,推导到用for循环进行自动分组
       * 第一次分组是arr.length/2,即数组的长度除以2
       * 第二次分组就是第一次分组的组数/2
       * 最后直到只有一组,1/2=0,就不会进入for循环
       */
        for(int gap = arr.length/2; gap > 0; gap = gap/2) {

            for(int i = gap; i < arr.length; i++) {//i = gap开始for循环,gap是每次分组的组数
            /*
             * 遍历各组中所有的元素(共gap组,每组有arr.length/gap个元素,步长为gap
             * 到最后gap=1的时候,就两两比较并交换,比如[0, 2, 1, 4, 3, 5, 7, 6, 9, 8]此时gap=1
             * 先0和2比较,0<2则不交换
             * 2和1比较,2>1,交换,[0, 1, 2, 4, 3, 5, 7, 6, 9, 8]
             * 2和4进行比较,2<4,不交换
             * 4和3进行比较,4>3,交换[0, 1, 2, 3, 4, 5, 7, 6, 9, 8]
             * 以此类推
             * 最后所有的数排序完毕
             */
                for(int j = i - gap; j >= 0; j = j - gap) {
                    if(arr[j] > arr[j + gap]) {
                        temp = arr[j + gap];
                        arr[j + gap] = arr[j];
                        arr[j] = temp;
                    }
                }
            }
            System.out.println("希尔排序过程:" + Arrays.toString(arr));
        }
    }
}

快速排序

快速排序(Quick Sort)是对冒泡排序的一种改进,基本思想是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据要小,然后再按照此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

快速排序的思路就是:

  1. 以最右边的数为基准,向左遍历,比基准大的数放在基准的右边,比基准小的数放在基准的左边,结果会分成两个部分
  2. 左边比基准都要小的部分,再次以最右边的数为基准,向左遍历,比此基准小的数放在基准的左边,比此基准大的数放在基准的右边…以此类推,直到左边的部分排序完成
  3. 右边的部分同理
  4. 基准不一定要找最左边或者最右边的数,也可以找中间的数
package com.sort;

import java.util.Arrays;

public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {-9,77,0,23,-56,70,-111,90,88,-33};

        System.out.println("原始数组:"+ Arrays.toString(arr));

        quickSort(arr, 0, arr.length - 1);

        System.out.println("快速排序后数组"+Arrays.toString(arr));
    }

    public static void quickSort(int[] arr, int left, int right) {
        int l = left;//左下标,用于向左遍历
        int r = right;//右下标,用于向右遍历

        //中间值,即基准值
        int pivot = arr[(left + right) / 2];
        int temp = 0;//临时变量,交换时使用
      /*
       * while循环,while循环的目的是让比pivot基准值小的放在左边
       * 比pivot基准值大的放在右边
       */
        while(l < r) {
         /*
          * 在pivot的左边一直找,找到大于等于pivot的值的数,才退出while循环
          * l默认从最左边开始
          */
            while(arr[l] < pivot) {
                l = l + 1;//如果没找到,则l+1并再次循环
            }
         /*
          * 在pivot的右边一直找,找到小于等于pivot的值的数,才退出while循环
          * r默认从最右边开始
          */
            while(arr[r] > pivot) {
                r = r - 1;//如果没找到,则r-1并再次循环
            }
         /*
          * 退出while循环的时候
          * 如果l>=r,说明已经遍历完了左右两边,并且左边已经全是小于等于pivot的值
          * 右边全是大于等于pivot的值,此时可以退出大的while循环
          */
            if(l >= r) {
                break;
            }

            //交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
         
         /*
          * 交换完过后,还需要考虑arr[l]和arr[r]的值等于pivot的值的情况
          * 比如说[l, pivot, r]=[1, 1, 2],已经到了最后一步交换
          * 此时l的while循环不会通过,arr[l]=1,r的while循环会通过,r=r-1,arr[r]=1
          * 经过交换过后,[l, pivot, r]=[1, 1, 2],会发现数组还是原来的样子,此时l<r,不构成退出的条件
          * 依然还会继续while循环,变成死循环,所以如果交换完之后,发现arr[l]=pivot,就使r--(r前移)
          * 这样就会使得l=r,while循环退出
          */
            if(arr[l] == pivot) {
                r = r - 1;
            }
            if(arr[r] == pivot) {
                //注意,l应该是l = l + 1
                l = l + 1;
            }
        }

        //如果l == r,必须使l++,r--,否则会出现栈溢出
        if(l == r) {
            l = l + 1;
            r = r - 1;
        }
      
      /*
       * 向左递归,要注意左递归的条件,因为经过了第一次的快速排序
       * 原数组[-9,77,0,23,-56,70]变成了[-9, -56, 0, 23, 77, 70],此时l在-56的位置,r在23的位置
       * 所以向左递归的条件,是left < r,即取到了[-9, -56, 0],递归完之后变为有序[-56, -9, 0]
       */
        if(left < r) {//left可能会大于r,此时退出递归循环
            quickSort(arr, left, r);
        }

        //向右递归同理
        if(right > l) {//right可能会小于l,此时退出递归循环
            quickSort(arr, l, right);
        }
    }
}

归并排序

归并排序(Merge Sort)是利用归并的思想实现的排序问题,该算法采用经典的分治(divide and conquer)策略,分治法将问题分成一些小的问题然后递归求解,治则体现在将分的阶段得到的答案修补在一起

package com.sort;

import java.util.Arrays;

public class MergeSort {
    public static void main(String[] args) {
        int[] arr = {8,4,5,7,1,3,6,2};

        //临时数组
        int[] temp = new int[arr.length];

        mergeSort(arr, 0, arr.length - 1, temp);

        System.out.println("归并排序后=" + Arrays.toString(arr));
    }

    //将原始数组分开+合并的方法
    public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if(left < right) {
            int mid = (left + right) / 2;//中间索引
         /*
          * 向左递归进行分解,此时arr相当于[left,xxx,mid,xxx,right]
          * 取[left,mid]部分的进行分解
          */
            mergeSort(arr, left, mid, temp);
         /*
          * 向右递归进行分解
          * 要注意,mid划给了左边,所以向右递归的时候,左界限为mid + 1
          */
            mergeSort(arr, mid + 1, right, temp);
            //每分解一次就合并一次
            merge(arr, left, mid, right, temp);
        }
    }


    //合并的方法
    /**
     * @param arr 待排序的原始数组
     * @param left 左边有序序列的初始索引
     * @param mid 中间索引
     * @param right 右边索引
     * @param temp 中转的临时数组
     */
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        System.out.println("xxxx");
        int i = left;//初始化i,表示左边有序序列的初始索引
        int j = mid + 1;//初始化j,表示右边有序序列的初始索引
        int t = 0;//t是指向temp数组的当前索引
      
      /*
       * 1.先把左右两边的数据(有序)按照规则填充到temp数组
       * 直到左右两边的有序序列,有一边处理完毕为止
       * 2.把有剩余数据的一边依次全部填充到temp数组
       * 3.将temp数组的元素,拷贝至arr数组
       */
        while(i <= mid && j <= right) {//左右两边都还没有处理完
         /*
          * 如果左边的有序序列的当前元素(下标为i),小于或者等于右边序列的当前元素(下标为j)
          * 就将左边的当前元素(下标为i),填充到temp数组,并使temp数组的下标后移
          */
            if(arr[i] <= arr[j]) {
                temp[t] = arr[i];
                i++;
                t++;
                //反之,就将右边的当前元素(下标为j),填充到temp数组,并使temp数组的下标后移
            }else if(arr[i] > arr[j]) {
                temp[t] = arr[j];
                j++;
                t++;
            }
        }
      /*
       * 把有剩余数据的一边的数据依次全部填充至temp
       * 从while循环出来,就满足i > mid 或者 j > right两个条件中的一个
       * 即有一边已经遍历完成
       * 所以应该对未完成的一边做判断,要注意while中的条件就是i <= mid或者j <= right
       * 因为假如是i没有遍历完,那么上面的while最后一次遍历的一定是j,然后j++不满足上面的while条件
       * 但是i没有进行遍历,也就没有i++,所以是i <= mid
       */
        while(i <= mid) {//i所在的左边的有序序列没有遍历完,将其中元素全部填充到temp
            temp[t] = arr[i];
            i++;
            t++;
        }
        while(j <= right) {//j所在的右边的有序序列没有遍历完,将其中元素全部填充到temp
            temp[t] = arr[j];
            j++;
            t++;
        }
      /*
       * 将temp数组的元素拷贝至arr,但是注意,并不是每次都拷贝所有
       * 因为arr = {8,4,5,7,1,3,6,2},在治的时候,会进行合并和排序,
       * 第一次合并,会变成4个部分[4,8],[5,7],[1,3],[2,6]
       * 第二次合并,会变成2个部分[4,5,7,8],[1,2,3,6]
       * 第三次合并,才会变成有序的最后的数组[1,2,3,4,5,6,7,8]
       * 所以在将temp数组的元素拷贝至arr时,会经过7次拷贝,第一次tempLeft=0,right=1,对应[4,8]部分
       * 第二次tempLeft=2,right=3,对应[5,7]部分,第三次tempLeft=0,right=3,对应[4,5,7,8]部分
       * 第四次[1,3]部分,第五次[2,6]部分,第六次[1,2,3,6]部分
       * 最后一次,也就是第七次tempLeft=0,right=7,对应[1,2,3,4,5,6,7,8]
       */
        t = 0;
        int tempLeft = left;
        System.out.println("tempLeft=" + tempLeft + " right=" + right);
        while(tempLeft <= right) {
            arr[tempLeft] = temp[t];
            t++;
            tempLeft++;
        }
    }
}

桶排序

  1. 基数排序(radix sort)属于分配式排序,又称桶排序,通过键值的每个位的值,将要排序的元素分配至某些桶中,达到排序的作用
  2. 基数排序是属于稳定性的排序,基数排序法是效率高的稳定性排序法
  3. 基数排序是桶排序的扩展

基数排序基本思想:将所有待比较的数统一为同样的数位长度,数位较短的数前面补零,然后从最低位开始,依次进行一次排序,这样从最低位排序一直到最高位排序完成后,数组就变成了一个有序序列
桶排序的思路图解:
在这里插入图片描述

桶排序的代码实现

package com.sort;

import java.util.Arrays;

public class RadixSort {
    public static void main(String[] args) {
        int[] arr = { 53, 3, 542, 748, 14, 214 };

        radixSort(arr);
    }

    //基数排序方法
    public static void radixSort(int[] arr) {
        //第一轮排序,针对每个元素的个位进行排序处理
      /*
       * 1.定义一个二维数组,表示10个桶,每个桶就是一个一维数组
       * 2.为了防止在放入数的时候,数据溢出,每一个一维数组(桶),大小为arr.length
       * 3.所以基数排序也是空间换时间的经典算法
       */
        int[][] bucket = new int[10][arr.length];
      
      /*
       * 为了记录每个桶中实际存放了多少个数据,定义一个一维数组来记录各个桶每次放入的数据个数
       * 即bucketElementCounts[0],记录的就是bucket[0]代表的桶的放入数据的个数
       */
        int[] bucketElementCounts = new int[10];

        //第一轮(针对每个元素的个位进行排序处理)
        for(int j = 0; j < arr.length; j++) {
            //取出每个元素的个位的数,用取模的方式来获取个位数
            int digitOfElement = arr[j] % 10;
         /*
          * 放入到对应的桶中
          * bucket[digitOfElement][bucketElementCounts[digitOfElement]]中
          * [digitOfElement]表示放在哪个桶,因为取模得到了个位数就对应相应的桶
          * [bucketElementCounts[digitOfElement]]表示放在该桶的第几个元素位置
          * 比如digitOfElement=7,[bucketElementCounts[7]]就表示[0,0,0,0,0,0,x,0,0,0],x表示放置的位置,x值为0
          * 然后bucketElementCounts[digitOfElement]++,为了下一次在7的桶存入数据时
          * bucket[digitOfElement][0]会变成bucket[digitOfElement][1]
          */
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            bucketElementCounts[digitOfElement]++;
        }
        //按照桶的顺序(一维数组的下标),依次取出数据,放入原来数组
        int index = 0;
        //遍历每一个桶,并将桶中的数据放入到原数组
        for(int k = 0; k < bucketElementCounts.length; k++) {
            //如果桶中有数据,放入到原数组
            if(bucketElementCounts[k] != 0) {
                //循环第k个桶,即二维数组bucket中的第k个一维数组
                for(int l = 0; l < bucketElementCounts[k]; l++) {
                    //从bucket取出元素,放到arr中
                    arr[index] = bucket[k][l];
                    index++;
                }
            }
         /*
          * 在每一轮处理后,需要将每个桶对应的bucketElementCounts[k]清零
          * 因为下一轮的bucketElementCounts[k]的值是需要从零开始重新进行累计的
          * 而不是在第一轮的基础上进行累计
          */
            bucketElementCounts[k] = 0;
        }
        System.out.println("第一轮对个位的排序处理" + Arrays.toString(arr));
      
      /*
       * 根据第一轮的过程,可知第二轮,第三轮...对数的处理方式,可以写一个最终的基数排序的代码
       * 需要先得到数组中最大的数的位数
       */
        int max = arr[0];//假设第一个数就是最大数
        for(int i = 1; i < arr.length; i++) {
            if(arr[i] > max) {
                max = arr[i];
            }
        }
      /*
       * for循环之后,已经找到了最大数为max
       * 将max与空字符串拼接,再用length方法得到最大数的位数,来决定循环的轮数
       */
        int maxLength = (max + "").length();

        index = 0;

        for(int i = 0, n = 1; i < maxLength; i++, n = n * 10) {

            //对每个元素的对应位数进行排序处理,第一次是个位,第二次是十位...以此类推
            for(int j = 0; j < arr.length; j++) {
                //取出每个元素需要的位数,用取模的方式来获取位数
                int digitOfElement = arr[j] /(1 * n) % 10;

                //将数据放入到对应的桶中
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                bucketElementCounts[digitOfElement]++;
            }
            //按照桶的顺序(一维数组的下标),依次取出数据,放入原来数组
            index = 0;

            //遍历每一个桶,并将桶中的数据放入到原数组
            for(int k = 0; k < bucketElementCounts.length; k++) {

                //如果桶中有数据,放入到原数组
                if(bucketElementCounts[k] != 0) {

                    //循环第k个桶,即二维数组bucket中的第k个一维数组
                    for(int l = 0; l < bucketElementCounts[k]; l++) {
                        //从bucket取出元素,放到arr中
                        arr[index] = bucket[k][l];
                        index++;
                    }
                }

                //在每一轮处理后,需要将每个桶对应的bucketElementCounts[k]清零
                bucketElementCounts[k] = 0;
            }
            System.out.println("第" + (i+1) +"轮的排序处理" + Arrays.toString(arr));
        }
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值