数据结构与算法-----常见排序算法

本文详细介绍了冒泡排序、选择排序、插入排序、希尔排序、快速排序、归并排序和基数排序的原理及Java实现。针对希尔排序的两种变体进行了性能测试,分析了二分查找算法及其在查找相同数时的应用。

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

一、冒泡排序

int score[] = {67,100,50,99,87,89,90,75};

boolean flag = false;
/**
 * 1、length方法用于获取数组的长度。
 * 2、而length()用于获取String字符串中字符的个数。
 * 第一轮计算的是需要几趟排序才能排完
 */
for (int i=0;i<score.length-1;i++){
    //第二轮这个计算的是一趟排序内需要比较几次才能比较完
    for (int j=0;j<score.length-1-i;j++){
        if (score[j] > score[j+1]){
            flag = true;
            int number = score[j];
            score[j] = score[j+1];
            score[j+1] = number;
        }
    }
    if (!flag){
        //说明一次一次比较都没有发生,已经排好序了
        break;
    } else{
        //说明至少排过一次序,重置标志才能有效
        flag = false;
    }
}

二、选择排序

int score[] = {67,100,50,99,87,89,90,75};
    selectSort(score);
    System.out.println(Arrays.toString(score));
}
private static void selectSort(int[] arr) {
    //一共有几轮,比当前数组的长度少一轮
    for (int i = 0; i < arr.length - 1; i++) {
        /**
         * 用于暂存数据,两个功能
         * 一、比如:假设第一个是最小的,则记录第一个下标以及数据,实际第一个可能不是最小的
         * 等到循环比较以后,第一个就变成最小的,则进行第二个数比较,取得最小值。
         * 可以说是记录比较到了第几位
         * 二、将暂存那个比假设的那个值要大的数,用于后续进行交换
         */
        int midIndex = i;
        int mid = arr[i];
        //已经假设第一个是最小的,所以从第二个开始,与第一个进行比较
        for (int j = i + 1; j < arr.length ; j++) {
            //如果想按从大到小排序,可以把这里变成<,这里的比较要么去mid,要么取arr[midIndex],                    
            //这是因为比较之后最小的元素以及下标会放到这里,是会动态变化的,不能取arr[0]
            if (mid > arr[j]) {
                //将比假设值要大的那个元素以及元素下标付给中间变量进行存储
                midIndex = j;
                mid = arr[j];
            }
        }
        //等于的话,就相当于没有交换,本身就是最小的
        if (midIndex != i) {
            /**这下面两部不能反过来,否则会出错,因为原先大的元素已经放入mid这个中间值里面了
             * 原先位置已经空出来了,所以要把arr[i]先放过去,然后再发mid中间值放入arr[i]中,
             */
            //将原来假设小的放到后面,因为假设小的不是最小的,这里为什么用midIndex,就是因为那个被置换的小标是j是在里面的循环里面,只能先付给外面的才行
            arr[midIndex] = arr[i];
            //将比假设的值小的元素,付给原来假设小的元素的那个位置
            arr[i] = mid;
        }
    }
}

三、插入排序

int[] scop = {3, 1, 5, 4, 9, 7, 6, 8};
    insertSort(scop);
    System.out.println("拍好后:"+ Arrays.toString(scop));
}

private static void insertSort(int[] arr) {
    //循环几次,注意最后一位也要进行比较
    for (int i = 1; i < arr.length; i++) {
        //第一位是标志位,所以从第二位开始
        int insertValue = arr[i];
        //这里的意思就是,取前一位的下标,如果是从第二位开始,那就是第一位的下标也就是0
        int insertIndex = i - 1;

        /**
         * 1、insertIndex >= 0:保证给insertValue这个数找插入的位置不越界
         * 2、insertValue < arr[insertIndex]:这个意思就是标志位比前一位要小,这个时候需要让前一位向后移,继续比较标志位与前前一位,如果比他大,或者小于0了,就可以退出循环,把标志为插入到比
比较的哪位大的数据位置的后面
         *
         */
        while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
            arr[insertIndex + 1] = arr[insertIndex];
            insertIndex--;
        }
        /**
         * 假如标志位是第一位,那么从第二位开始,
         * insertValue 就是第二位,
         * insertIndex 就是第一位的坐标
         * arr[insertIndex + 1]其实就是第二位,相当于把第二位的值再付给他自己
         * 这里不能用a[i],原因是在while循环中a[i]有可能会被覆盖。 arr[insertIndex + 1] =     
         * arr[insertIndex];这里把前一个赋值给后一个了,两个值暂时是一样。原来的后一个的值,在
         *循环前就已经拿出来放在insertvalue里面了
         */
        arr[insertIndex + 1] = insertValue;
        System.out.println("每一轮排序:"+ Arrays.toString(arr));
    }
}

四、希尔排序

第一种:
public static void shellSort(int[] arr) {
        // 循环次数控制,先将数组分成两个组进行排序,然后再对两个组进行分组,每个分组分成两组
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
        // 利用查询排序思想
            for (int i = gap; i < arr.length; i++) {
                int j = i;
                int temp = arr[j];
                    while (j - gap >= 0 && temp < arr[j - gap]) {
                        arr[j] = arr[j - gap];
                        j -= gap;
                    }
                    arr[j] = temp;
            }
        }
    }

第二种:
 public static void shellSort(int[] arr) {
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < arr.length; i++) {
                int j = i;
                int temp = arr[j];
                // 加了if判断
                if (arr[j] < arr[j - gap]) {
                    while (j - gap >= 0 && temp < arr[j - gap]) {
                        arr[j] = arr[j - gap];
                        j -= gap;
                    }
                    arr[j] = temp;
                }
            }
        }
    }


亲测:八千万随机数据排序
没加if:耗时28,26
加了if:耗时25,26
总的来说相差不大

五、快速排序

int[] scop = {3, 1, 5, 4, 9, 7, 6, 8};
    quickSort(scop,0,scop.length-1);
    System.out.println(Arrays.toString(scop));
}

private static void quickSort(int[] arr, int left, int right) {

    int l = left;
    int r = right;
    int mid = arr[(left + right) / 2];
    int temp =0;

    //比mid小的放到左边,比mid大的放到右边
    while (l < r) {

        //从最左边找,比中间值小的,一直知道比中间值大的就退出
        while (arr[l] < mid) {
            l +=1;
        }

        //从最右边找,找到比mid小的,就退出
        while(arr[r] > mid){
            r -= 1;
        }

        //这里说明左边已经是全部小于等于中间值,右边已经全部是大于等于中间值
        if (l>= r){
            break;
        }

        //找到了,那就要开始交换了
        temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;


        /**
         * 其实就是mid这个值左右两边,刚好有两个和mid相等的数
         * 然后这里只是在mid左边与mid相等时,去移动右边的下标,
         * 然后右边的值和mid相等时,移动左边的下标而已
         */
        //如果交换后,发现这个arr[l] == mid这个值,r--,前移
        if (arr[l] == mid){
            r -= 1;
        }
        //如果交换后,发现这个arr[r] == mid这个值,l++,后移
        if (arr[r] == mid){
            l += 1;
        }
    }
    //如果l == r,必须l++,r--,否则会出现栈溢出
    if(l ==r ){
        l+=1;
        r-=1;
    }

    //向左递归
    if (left<r){
        quickSort(arr, left, r);
    }
    //向右递归
    if (right>l){
        quickSort(arr, l, right);
    }
}

六、分治算法

/**
 * 归并排序
 */
public class test6 {
    public static void main(String[] args) {

        int[] scop = {3, 1, 5, 4, 9, 7, 6, 8};
        int[] temp = new int[scop.length];
        mergeSort(scop, 0, scop.length - 1, temp);
        System.out.println(Arrays.toString(scop));
    }

    //分加合
    public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        /**
         * 这里用了递归
         * (1)第一次:执行第一个 mergeSort   执行前判断:left=0,right=7 
         * 左边:3, 1, 5, 4    右边:9, 7, 6, 8  这是一次循环
         *  进行第二次:执行第一个 mergeSort  数组是:3, 1, 5, 4   left=0,right=3
         *  左边:3, 1    右边:5, 4
         * 第三次:执行第一个 mergeSort  数组是:3, 1    left=0,right=1 
         * 左边:3      右边  1
         * 第四次前:数组是:3  left=0,right=0
         * 然后left < right不满足,跳出第四次循环的,
         * 执行第三次循环中第二个mergeSort,右边数组为5, 4   执行前判断:mid 为 0 ,left=mid+1 = 1,right=1 不满足left < right
         * 进入merge(),数组虽然是[3, 1, 5, 4, 9, 7, 6, 8],但是他只比较,3, 1,因为这是第三次循环中的元素----3,1  之后会比较5,4(注意如果第二个mergeSort里面还有递归,还会执行第一个
         * mergeSort方法)
         * 如此一致递归
         * 
         */
        if (left < right) {
            int mid = (left + right) / 2;
            mergeSort(arr, left, mid, temp);
            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) {

        int i = left; //初始化i为左边有序序列的初始索引
        int j = mid + 1; //初始化j为右边有序序列的初始索引
        int t = 0; //这向temp数组的当前索引
        /**
         * 一、
         * 先把左右两边有序的数组按照规则填充到temp数组中,
         * 直到有一边处理完为止
         */
        while (i <= mid && j <= right) {
            //如果左边的有序序列的当前元素,小于右边有序序列的当前元素,就将左边的当前元素转移到temp数组中,
            // 然后左边数组下边加一,temp数组下边向后移一位
            if (arr[i] <= arr[j]) {
                temp[t] = arr[i];
                t += 1;
                i += 1;
            } else {//反之将右边的放到temp当中
                temp[t] = arr[j];
                t += 1;
                j += 1;
            }
        }
        /**
         * 二、
         * 如果比完后还有剩下的,就将剩下的全部移动到temp数组当中
         */
        while (i <= mid) {//左边还有剩
            temp[t] = arr[i];
            t += 1;
            i += 1;
        }
        while (j <= right) {//右边还有剩
            temp[t] = arr[j];
            t += 1;
            j += 1;
        }
        /**
         * 三、
         * 将temp数组元素拷贝到arr当中
         * 注意不是每次都拷贝所有
         *
         * 就是合并的几个步骤
         */
        t = 0;
        int tempLeft = left;
        //第一次合并 tempLeft = 0,right = 1     第二次:tempLeft = 2,right = 3   第三次:tempLeft = 0,right = 3
        //最后一次合并:tempLeft = 0,right = 7
        System.out.println("tempLeft=" + tempLeft + ",right = " + right);
        while (tempLeft <= right) {
            arr[tempLeft] = temp[t];
            t += 1;
            tempLeft += 1;
        }

    }
}

七、基数排序

    public static void sort(int[] arr) {
        // 1、得到数组中最大数的位数
        int max = arr[0]; // 用于接收最大数
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > arr[0]) {
                max = arr[i];
            }
        }
        // 1.2、获取最大数的位数
        int maxLength = (max + "").length();

        // 2、定义一个二维数组表示10个桶,每个桶就是一个一维数组
        /**
         * 2.1 二维数组包含十个桶,也就是10个一维数组
         * 2.2 为防止在放入数据的时候,数据溢出,则每个一维数组的大小为数组的长度,arr.length
         */
        int[][] bucket = new int[10][arr.length];

        /**
         * 主要是用来每个桶中放入的有效个数
         * 数组初始化都是零,位置代表桶号,数组的值代表有效个数。
         */
        int[] bucketElementCounts = new int[10];

        // 3、开始进行数据比较,
        /**
         * 这里的n主要就是遍历各位数的时候,取余即可,但是遍历十位数的时候,就需要先
         * 除10取整,再除10取余;比如:784 ,十位数是8,这个时候使用784/10取整得78,再对78%10 取余得到十位数8;
         * 所以开始时n=1,步长为10
         */
        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
            // 3.1 开始对每个元素的每个位,进行排序。第一个轮回是个位,第二轮回是十位,等等。
            // 下一步是循环对数组中的元素放入每个桶中;
            for (int j = 0; j < arr.length; j++) {
                // 取出每个元素对应位上的值
                int digOfElement = arr[j] / n % 10;

                /**放入到桶中,digOfElement:标志着在那个桶,
                 * bucketElementCounts[digOfElement]这个值第一次取出来是0,之后会自增,表示已经放入了一个有效数据
                 **/
                bucket[digOfElement][bucketElementCounts[digOfElement]] = arr[j];
                bucketElementCounts[digOfElement]++;
            }

            // 4、把桶里面的数据按顺序取出,放入到原数组中
            int index =0;

            /**
             * 遍历每一个桶,并将桶中数据放入到原数组中
             * <bucketElementCounts.length这个主要是取数组的有效个数,也可以使用二维数组中的10或者是数组参数arr.length
             * 来代替
             *index:主要是用来放入原数组时记录下标用的。
             * bucketElementCounts[k]的值:为桶中的有效个数
             * bucket[k][l]:桶中数据的值,可能会有多个,所以l取得是bucketElementCounts[k]
             */
            for (int k = 0;k<bucketElementCounts.length;k++){
                // 4.1如果该桶中有数据,再进入下一步
                if (bucketElementCounts[k] != 0){
                    // 4.2 遍历桶中数据,bucketElementCounts[k]的值为桶中的有效个数。
                    for (int l = 0;l<bucketElementCounts[k];l++){
                        arr[index++] = bucket[k][l];
                    }
                }

                /**
                 * 5、最后需要清空统计有效个数的桶,为下一轮做准备
                 *
                 * 有一个问题就是:为啥没有清空二维数组,从新为下一轮做准备呢,
                 * 主要是:就算二维数组还有上一次排列的数组值,也不会对下一轮有影响,
                 * 首先是,如果下一轮出现和上一轮同样的数据,那么他会被放到同一个桶,然后
                 * bucket[digOfElement][bucketElementCounts[digOfElement]] = arr[j];
                 *digOfElement:这个相同就决定了进入同一个桶
                 * bucketElementCounts[digOfElement]:这个已经清空了,会从第0个位置开始放置数据,也就是说,如果
                 * 第0个的位置有上一轮的数据就会被覆盖。
                 * 最后,如果那些没有被覆盖,但是还在桶中的上一轮数据也会影响下一轮的遍历,原因就是:bucketElementCounts[digOfElement]
                 * 这个数组记录的有效个数已经清零,重新记录新的一轮的有效个数。取值的时候
                 * bucket[k][l];也都是每一个桶遍历,然后取这一轮记录下来的有效个数作为位置参数,有效个数有几个,就取几个,
                 * 原来的这几个数据也会被新一轮的覆盖,没有被覆盖的也取不到。
                 */
                bucketElementCounts[k] = 0;
            }
            
        }

    }

八、二分查找

/**
 * 二分查找,只能用于有序的数组当中,
 */
public class test7 {
    public static void main(String[] args) {
        int[] scop = {1, 2, 3, 4, 5, 6, 7, 8};
        System.out.println(binarySelect(scop,0,scop.length-1,5));

    }
    public static int binarySelect(int[] arr, int left, int right, int targ) {

        //当左边大于右边时,退出递归,这是第一种情况
        if (left > right) {
            return -1;
        }
        int mid = (left + right) / 2;
        //当目标值刚好和中间值相等时,就说明找到了,退出递归
        int midValue = arr[mid];

        if (targ > midValue) {
            //这里会一直递归,知道找到和中间值相等,或者找不到的情况下,才会退出递归
            return binarySelect(arr, mid + 1, right, targ);
        } else if (targ < midValue){
            return binarySelect(arr, 0, mid - 1, targ);
        }else {
            //返回找到的中间值
            return mid;
        }
    }
}



// 把相同的数也查找出来
 public static List<Integer> search2(int[] arr, int left, int right, int finalValue){
        if (left>right){
            return new ArrayList<>();
        }

        int mid = (left+right)/2;

        int midValue = arr[mid];

        if (finalValue<midValue){
            return search2(arr,left,mid-1,finalValue);
        }else if (finalValue>midValue){
            return search2(arr,mid+1,right,finalValue);
        }else{

            ArrayList<Integer> list = new ArrayList();
            list.add(mid);

            int temp = mid-1;
            // 先往左边查
            for (int i =mid-1;i>=0;i--){
                // 由于二分查找两个相同的数是连续在一起的,所以,当连续的数不同,就说明数据不相等了
                if (arr[i]!= finalValue){
                    break;
                }
                list.add(i);
            }
            temp = mid+1;
            // 再往右边查
            for (int j = mid+1;j<arr.length;j++){
                if (arr[j] != finalValue){
                    break;
                }
                list.add(j);
            }

            return list;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值