排序(基于Java语言编写)

1、冒泡排序

1.1 排序思想

假设数组长度为n(从小到大):

  • 第一轮比较:比较n-1次
    依次比较相邻两个元素,如果前一个元素比后一个元素大,就交换这两个元素的位置;对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素;最终最后位置的元素就是本轮冒泡的最大值。
  • 第二轮比较:比较n-2次,此时已经排好序的元素不参与比较,即最后一个元素
    比较过程与第一轮相同
  • 最后一轮比较:比较1次,即第一个元素和第二个元素比较,确定最终元素位置。至此,排序结束

总结:

  1. 一次比较数组A中相邻两个元素大小,若A[ j ] > A[ j+1 ],则交换两个元素,两两都比较一遍称为一轮冒泡,结果是让最大的元素排至最后。
  2. 重复以上步骤,知道整个数组有序

1.2 代码实现

public class BubbleSort {

    /**
     * 原始冒泡排序
     * @param a
     * @return
     */
    public static int[] bubbleSort(int[] a) {
        //外层循环确定冒泡的轮数
        for (int i = 0; i < a.length - 1; i++) {
            //一轮冒泡
            for (int j = 0; j < a.length - 1 - i ; j++) {
                System.out.println("比较次数"+j);
                //比较,并判断是否要交换元素
                if(a[j] > a[j+1]){
                    swap(a,j,j+1);
                }
            }
            System.out.println("第"+i+"轮冒泡:"+Arrays.toString(a));
        }

        return a;
    }

    /**
     * 冒泡次数优化的冒泡排序
     * @param a
     * @return
     */
    public static int[] bubbleSort1(int[] a) {
        //冒泡的轮数
        for (int i = 0; i < a.length - 1; i++) {
            //标记元素是否发生交换,默认false,表示没有发生交换
            boolean flag = false;
            //一轮冒泡
            for (int j = 0; j < a.length - 1 - i ; j++) {
                System.out.println("比较次数"+j);
                if(a[j] > a[j+1]){
                    swap(a,j,j+1);
                    flag = true;//表示发生了交换
                }
            }
            System.out.println("第"+i+"轮冒泡:"+Arrays.toString(a));

            if(!flag){
               break;
            }
        }
        return a;
    }

    /**
     * 每轮冒泡排序的交换次数优化的冒泡排序(最优)
     * @param a
     * @return
     */
    public static int[] bubbleSort2(int[] a) {
        //定义每轮冒泡的比较次数
        int count = a.length -1;
        //冒泡的轮数
        for (int i = 0; i <a.length -1 ; i++) {
            //定义最后一次交换索引的位置
            int lastIndex = 0;
            //一轮冒泡
            for (int j = 0; j < count ; j++) {
                System.out.println("比较次数: "+j);
                if(a[j] > a[j+1]){
                    swap(a,j,j+1);
                    lastIndex = j;
                }
            }
            //最后一次交换索引的位置 等于 下次比较的次数
            count = lastIndex;
            System.out.println("第"+i+"冒泡:"+Arrays.toString(a));

            if(count == 0){
                break;
            }
        }
        return a;
    }

    //交换元素的方法
    public static void swap(int[] a,int i,int j){
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    public static void main(String[] args) {
        int[] nums = {13,16,1,25,3,49,11,19,30,20};
        System.out.println(Arrays.toString(bubbleSort2(nums)));
    }
}

1.3 冒泡排序优化

传统方法:完成排序需要冒泡n-1轮, 但有时不需要比较这么多轮,可能在第三次比较之后就有序了,那么后面的比较就不需要再执行了,没有意义。接下来看看冒泡排序的优化。

1.3.1 减少冒泡排序的冒泡次数

  • 重点:怎么判断数组有序,不用再进行冒泡呢?
  • 思路:如果某一轮冒泡,它相邻两个元素比较比较,没有发生一次交换,说明整个数组已经有序了,则可以用 是否发生交换来 判断数组是否有序
  • 做法:
    在每轮冒泡之前设一个布尔变量 flag,表示是否发生交换。默认为false,表示没发生交换。
    在冒泡比较中,当发生交换了之后,将flag 设置为true;表示发生了交换;在每轮冒泡结束之后,判断flag是否为flase,如果为false,说明数组有序,直接退出循环。
  • 代码实现:1.2中的bubbleSort1()方法

1.3.2 减少每轮冒泡排序的交换次数

  • 记录每轮冒泡比较过程中最后发生交换的索引位置,做为下一次冒泡比较的次数,直到最后记录的索引位置为0时,意味着下一次不用比较,数组已有序,退出循环。
  • 代码实现:1.2中的bubbleSort2()方法

2、选择排序

2.1 排序思想

  • 将数组分为已排序数组和未排序数组两个子集
    -已经排序的元素不参与每轮的选择排序
  • 排序规则如下:
    选取第一个元素标记为临时最小数,与其他元素依次进行比较,若有某个元素小于临时最小数,则将该元素标记为临时最小数,直到比较结束,找到本轮最小数,将最小数与第一个元素进行交换,第一轮选择排序结束。
    选取第二个元素标记为临时最小数,与其他元素依次比较,若有某个元素小于临时最小数,则将该元素标记为临时最小数,直到比较结束,找到本轮最小数,将最小数与第二个元素进行交换,第二轮选择排序结束。
    ······
    重复上述步骤,直到选取数组最后一个元素标记为临时最小数时,说明整个数组有序,选择排序结束。

总结:

  1. 每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,
    如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引出的值为最小值,最后可以找到最小值所在的索引
  2. 交换第一个索引处和最小值所在的索引处的值
  3. 总得来说,将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集;重复以上步骤,直到整个数组有序。

2.2 代码实现

public class SelectSort {
    
    public static int[] selectSort(int[] a){
        // i 代表每轮选择最小元素需要交换到的目标索引
        for (int i = 0; i < a.length -1; i++) {
            //记录最小元素的索引,默认位未排序数组的第一个元素,即i
            int s = i;
            int temp = a[i];
            for (int j = s+1; j < a.length; j++) {
                if(a[s] > a[j]){
                    s = j;
                }
            }
            //最小元素与目标索引处的值进行交换
            if(i!=s){
                a[i] = a[s];
                a[s] = temp;
            }
        }
        return a;
    }

    public static void main(String[] args) {
        int[] nums = {13,16,1,25,3,49,11,19,30,20};
        System.out.println(Arrays.toString(selectSort(nums)));
    }
}

2.3 优化方式

减少交换次数:每轮可以先找到最小的索引,在每轮的最后再交换元素。

2.4 与冒泡排序的比较

  • 二者平均时间复杂度O(n^2)
  • 选择排序一般快于冒泡排序,因为其交换次数少
  • 但如果数组有序度高,冒泡优于选择
  • 冒泡为稳点排序算法,选择是不稳定排序算法
    ​​排序算法的稳定性是指两个相同的元素排序后,相对位置不改变
    若位置改变,则为不稳定排序算法

3、插入排序

3.1 排序思想

  • 将数组分为已排序区域的和未排序区域
  • 找到未排序的数组中的第一个元素,向已经排序的数组中进行插入;
  • 倒叙遍历已经排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素,那么就把待插入元素放到这个位置,其他的元素向后移动一位;

总结:
时间复杂度O(n)

3.2 代码实现

public class InsertSort {

    public static int[] insertSort(int[] a) {
        //i 表示待插入元素的索引
        for (int i = 1; i < a.length; i++) {

            // j表示数组中已排数组的最大索引,
            for (int j = i - 1; j >= 0; j--) {
                if (a[j] > a[j + 1]) {
                    swap(a,j,j+1);
                } else {
                    break;//退出循环,说明前面都是有序的,减少比较次数
                }
            }
            System.out.println(Arrays.toString(a));
        }
        return a;
    }

    /**
     * 插入排序优化
     * @param a
     * @return
     */
    public static int[] insert(int[] a) {
        //i 表示待插入元素的索引
        for (int i = 1; i < a.length; i++) {
            //表示带插入的值
            int temp = a[i];
            //已排序区域的最大索引
            int j = i - 1;
            while(j >= 0){
                if (temp < a[j]) {
                    a[j + 1] = a[j];//交换元素
                } else {
                    break;//退出循环,说明前面都是有序的,减少比较次数
                }
                j--;
            }
            //将带插入元素 插入到空位值上
            a[j + 1] = temp;
            System.out.println(Arrays.toString(a));
        }
        return a;
    }

    public static void swap(int[] a,int i,int j){
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    public static void main(String[] args) {
        int[] nums = {13, 16, 1, 25, 3, 49, 11, 19, 30, 20};
//        insertSort(nums);
        insert(nums);
    }
}

3.3 优化方式

  • 带插入元素进行比较时,遇到比自己小的元素,就代表找到了插入位置,无需进行后续比较
  • 插入时可以直接移动元素,而不是交换元素
  • 实现代码为3.2中的 insert(int[] a) 方法

3.4 与选择排序比较

  • 二者平均时间复杂度O(n^2)
  • 大部分情况下,插入都优于选择
  • 有序集合插入的时间复杂度为O(n)

4、希尔排序

4.1 排序思想

核心:是把间隙相同的元素化为一组,同一组元素进行插入排序

  • 选定一个增长量h,按照增长量h作为数据分组的依据,对数据进行分组;h = nums.length-1
  • 对分好组的每一组数据完成插入排序;
  • 减小增长量,最小减为1,重复第二步操作。

4.2 代码实现

public class ShellSort {

    public static int[] shellSort(int[] a) {
        //1.根据数组的长度,确定增长量h的初始值
        int h = 1;
        while (h < a.length / 2) {
            h = 2 * h + 1;
        }
        //2.希尔排序
        while (h >= 1) {
            //排序
            //2.1找到带插入的元素
            for (int i = h; i < a.length; i++) {
                //2.2把待插入的元素插入到有序数列中
                for (int j = i; j >= h; j -= h) {
                    //比较a[j-h]与a[j]的值。a[j-h]>a[j],则交换位置;否则,元素已经找到合适的位置,退出循环
                    if (a[j - h] > a[j]) {
                        swap(a, j - h, j);
                    } else {
                        break;
                    }
                }
            }
            //减小h的值
            h = h / 2;
        }
        return a;
    }

    public static void swap(int[] a, int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    
    public static void main(String[] args) {
        int[] nums = {13,16,1,25,3,49,11,19,30,20};
        System.out.println(Arrays.toString(shellSort(nums)));
    }
}

5、快排

5.1 单边循环快排

5.1.1 单边循环快排步骤

  • 选择最右元素作为 基准点元素
  • j 指针负责找到比基准点小的元素,一旦找到则与i进行交换
  • i 指针维护小于基准点元素的边界,也是每次交换的目标索引
  • 最后基准点与 i交换,i 即为分区位置

5.1.2 代码实现

//单边循环快排
public class QuickSortOne {

    public static void quick(int[] a, int l, int h) {
        if (l >= h) {
            return;
        }
        int p = partition(a, l, h);//基准点的索引
        quick(a, l, p - 1);//左分区范围
        quick(a, p + 1, h);//由分区范围

    }

    //一轮单边循环快排
    public static int partition(int[] a, int l, int h) {
        //表示基准点元素
        int pv = a[h];
        //表示小于基准点元素的边界
        int i = l;
        //找到比基准点元素小的元素,并与 小于基准点元素的边界i 处的值交换
        for (int j = l; j < a.length; j++) {
            if (a[j] < pv) {
                if (i != j) {
                    swap(a, i, j);
                }
                i++;
            }
        }
        //交换基准点元素与i处得值
        if (i != h) {
            swap(a, i, h);
        }
        System.out.println(Arrays.toString(a) + "i=" + i);
        return i;
    }

    public static void swap(int[] a, int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    public static void main(String[] args) {
        int[] nums = {13, 16, 1, 25, 3, 49, 11, 19, 30, 20};
        quick(nums, 0, nums.length - 1);
    }
}

5.1.3 单边查找基准点的过程图

在这里插入图片描述

5.2 双边循环快排

5.2.1 双边循环快排步骤

  • 选择最左元素作为基准 点元素
  • j 指针负责从右向左找比基准点小的元素, i指针负责从左向右找比基准点大的元素(),一旦找到二者交换,直至i, j相交
  • 最后基准点与i (此时i与j相等)交换, i即为分区位置

5.2.2 代码实现

//双边边循环快排
public class QuickSortTwo {

    public static void quick(int[] a, int l, int h) {
        if (l >= h) {
            return;
        }
        int p = partition(a, l, h);//基准点的索引
        quick(a, l, p - 1);//左分区范围
        quick(a, p + 1, h);//由分区范围

    }

    //一轮双边循环快排
    public static int partition(int[] a, int l, int h) {
        int pv = a[l];
        int i = l;
        int j = h;
        while (i < j) {
            //j 从右找比基准点小的元素
            while (i < j && a[j] > pv) {
                j--;
            }
            //i 从左找比基准点大的元素
            while (i < j && a[i] <= pv) {
                i++;
            }
            swap(a, i, j);
        }
        swap(a,l,j);
        System.out.println(Arrays.toString(a)+"j="+j);
        return j;
    }

    public static void swap(int[] a, int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    public static void main(String[] args) {
        int[] nums = {13, 16, 1, 25, 3, 49, 11, 19, 30, 20};
        System.out.println(Arrays.toString(nums));
        quick(nums, 0, nums.length - 1);
    }
}

5.2.3 双边循环查找基准点的过程图

在这里插入图片描述

5.2.4 双边循环几个要点

  1. 基准点在左边,并且要先j后i
  2. while (i < j && a[j] > pv) j–
  3. while (i < j && a[i] <= pv) i++
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值