插入&希尔&归并&选择&冒泡&快速排序

1.插入排序

一个很形象的例子:打扑克。
手里的扑克牌(数组)分成两部分,一部分是你手里的牌(已经排好序),一部分是要拿的牌(无序)。然后将拿过来的牌插入到自己的手里面去(把一个无序的数列一个个插入到有序数列中去)。

具体步骤如下:
1.将数组分成已排序和未排序段。初始化已排序的只有一个元素。
2.到未排序取元素插入到已排序段,并保证插入后仍然有序。
3.重复执行上述操作,直到未排序段元素全部加完。
在这里插入图片描述
代码如下:

 public static void insertionSort(int[] data) {
        //i从1开始,第一个默认已经进入排好序段
        for (int i = 1; i < data.length; i++) {
            int j;
            int locData = data[i];
            //从已排好序段最后一个开始和未排序段第一个比较
            for (j = i - 1; j >= 0; j--) {
                if (data[j] > locData) {
                    //往后挪一个
                    data[j + 1] = data[j];
                } else {
                    //前面已经排好序了,那么找到一个比他小的就不用找了,因为前面的肯定更小
                    break;
                }
            }
            data[j + 1] = locData;
        }
    }

    public static void main(String[] args) {
        int[] data = {7, 8, 9, 0, 4, 3};
        insertionSort(data);
        System.out.println(Arrays.toString(data));
    }

测试结果为:

[0, 3, 4, 7, 8, 9]

分析:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定

2.希尔排序

希尔排序其实就是插入排序的一个改进版。
希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序,随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法终止。

在这里插入图片描述
代码如下:

  public static void shellSort(int[] data){
        //和插入排序就多了一个分组
        for (int i = data.length/2; i>0 ; i/=2) { 
            for (int j = i; j <data.length ; j++) {
                int k;
                int locData=data[j];
                for ( k = j-i; k >=0 ; k-=i) {
                    if(data[k]>locData){
                        data[k+i]=data[k];
                    }else {
                        break;
                    }
                }
                data[k+i]=locData;
            }
        }
    }

    public static void main(String[] args) {
        int[] data={7,8,9,0,4,3,1,2,5,10};
        shellSort(data);
        System.out.println(Arrays.toString(data));
    }

测试结果:

[0, 1, 2, 3, 4, 5, 7, 8, 9, 10]

分析:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定

3.归并排序

归并排序是一种非常高效的排序算法,其核心思想就用到了我们上节课讲的递归和分治的思想。
举个例子:归并排序数组[9,5,6,8,0,3,7,1]
在这里插入图片描述
代码如下:

  public static void mergeSort(int[] data, int left, int right) {
        // 相等了就表示只有一个数了 不用再拆了
        if (left < right) {
            int mid = (left + right) / 2;
            mergeSort(data, left, mid);
            mergeSort(data, mid + 1, right);
            // 分完了 接下来就要进行合并,也就是我们递归里面归的过程
            merge(data, left, mid, right);
        }
    }

    private static void merge(int[] data, int left, int mid, int right) {
        //借助一个临时数组用来保存合并的数据
        int[] temp = new int[data.length];
        //表示的是左边的第一个数的位置
        int point1 = left;
        //表示的是右边的第一个数的位置
        int point2 = mid + 1;
        //表示的是我们当前已经到了哪个位置了
        int loc = left;
        while (point1 <= mid && point2 <= right) {
            //如果没有这个=,排序就不稳定
            if (data[point1] <= data[point2]) {
                temp[loc] = data[point1];
                loc++;
                point1++;
            } else {
                temp[loc] = data[point2];
                loc++;
                point2++;
            }
        }
        //将剩下还没加入临时数组的元素放进去
        while (point1 <= mid) {
            temp[loc++] = data[point1++];
        }
        while (point2 <= right) {
            temp[loc++] = data[point2++];
        }

        for (int i = left; i <= right; i++) {
            data[i] = temp[i];
        }
    }

    public static void main(String[] args) {
        int[] data = {9, 5, 6, 8, 0, 3, 7, 1};
        mergeSort(data, 0, data.length - 1);
        System.out.println(Arrays.toString(data));
    }

测试结果:

[0, 1, 3, 5, 6, 7, 8, 9]

分析:
时间复杂度:O(n*logn)
空间复杂度 O(n)
稳定性:稳定

4.选择排序

选择排序的思路和插入排序非常相似,也分已排序和未排序区间。但选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。但是不像插入排序会移动数组 选择排序会每次进行交换。每一轮都前往后确定了一个数字
如以下例子:
4 5 6 3 2 1
第一次: 1 5 6 3 2 4
第二次: 1 2 6 3 5 4
第三次:1 2 3 6 5 4
第四次: 1 2 3 4 5 6

代码如下:

public static void selectSort(int[] data) {
        for (int i = 0; i < data.length - 1; i++) {
            int loc = i;
            for (int j = i + 1; j < data.length; j++) {
                if (data[j] < data[loc]) {
                    loc = j;
                }
            }
          //a^a=0,所以一定要loc!=i
            if (loc != i) {
                data[i] = data[i] ^ data[loc];
                data[loc] = data[i] ^ data[loc];
                data[i] = data[i] ^ data[loc];
           }
        }
    }

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

测试结果:

[1, 2, 3, 4, 5, 6]

分析:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定

5.冒泡排序

核心思路:冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复n次,就完成了n个数据的排序工作。每一轮都从后往前确定了一个数字

举例说明:
数组[4 5 6 3 2 1],从小到大排序。
第一次冒泡的结果:4 5 6 3 2 1->4 5 3 6 2 1 - > 4 5 3 2 6 1 -> 4 5 3 2 1 6
第二次冒泡的结果:4 5 3 2 1 6->4 3 5 2 1 6 -> 4 3 2 5 1 6 -> 4 3 2 1 5 6
第三次冒泡的结果:4 3 2 1 5 6->3 4 2 1 5 6 -> 3 2 4 1 5 6 -> 3 2 1 4 5 6
第四次冒泡的结果:3 2 1 4 5 6->2 3 1 4 5 6 -> 2 1 3 4 5 6
第五次冒泡的结果:2 1 3 4 5 6->1 2 3 4 5 6

代码如下:

 public static void bubbleSort(int[] data) {
        for (int i = 0; i < data.length - 1; i++) {
          boolean flag=false;
            for (int j = 0; j <data.length-1-i ; j++) {
            //如果加上了=,就不稳定了
                if(data[j]>data[j+1]){
                    data[j]=data[j]^data[j+1];
                    data[j+1]=data[j]^data[j+1];
                    data[j]=data[j]^data[j+1];
                    flag=true;
                }
            }
            //用标记,当后面已经是排好序的时候,直接跳出循环
            if(!flag){
                break;
            }
        }
    }

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

测试结果:

[1, 2, 3, 4, 5, 6]

分析:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定

6.快速排序

直接上例子理解:
给数组[45,28,80,90,50,16,100,10]排序。

定基准数:一般就是取要排序序列的第一个。(也可以取三个数的中间值,优化的关键是如何定基准数
第一次排序基准数:45
从后面往前找到比基准数小的数进行对换:
10 28 80 90 50 16 100 45
从前面往后面找比基准数大的进行对换:
10 28 45 90 50 16 100 80

10 28 16 90 50 45 100 80
10 28 16 45 50 90 100 80
以基准数分为3部分,左边的比之小,右边比之大:
{10 28 16} 45 {50 90 100 80}
到此第一次以45位基准数的排序完成,每一轮后都能使基准到它的最终位置

在这里插入图片描述
代码如下:

 public static void quickSort(int[] data, int left, int right) {
        // 表示的是从左边找的位置
        int ll = left;
        // 表示从右边开始找的位置
        int rr = right;
        //就是我们的基准数,取序列的第一个,不能用data[0]
        int base = data[left];
        while (ll < rr) {
            // 从后面往前找比基准数小的数
            while (data[rr] > base) {
                rr--;
            }
            // 表示是找到有比之大的
            if (ll < rr) {
                data[ll] = data[ll] ^ data[rr];
                data[rr] = data[ll] ^ data[rr];
                data[ll] = data[ll] ^ data[rr];
                ll++;
            }
            while (data[ll] < base) {
                ll++;
            }
            if (ll < rr) {
                data[ll] = data[ll] ^ data[rr];
                data[rr] = data[ll] ^ data[rr];
                data[ll] = data[ll] ^ data[rr];
                rr--;
            }
        }
        // 肯定是递归 分成了三部分,左右继续快排,注意要加条件不然递归就栈溢出了
        if (ll > left) {
            quickSort(data, left, ll - 1);
        }
        if (rr < right) {
            quickSort(data, ll + 1, right);
        }
    }

    public static void main(String[] args) {
        int[] data = {45, 28, 80, 90, 50, 16, 100, 10};
        quickSort(data, 0, data.length - 1);
        System.out.println(Arrays.toString(data));
    }

测试结果:

[10, 16, 28, 45, 50, 80, 90, 100]

分析:
时间复杂度:O(n*logn)
空间复杂度:O(1)
稳定性:不稳定

六种排序的分析总结

在这里插入图片描述
这么多种排序算法我们究竟应该怎么选择呢?:
1.分析场景:稳定还是不稳定
2.数据量:数据量小的时候选什么?比如就50个数,优先选插入;如果5000个数据 5000*5000=25000000,就不行了
3.分析空间:
综上所述,没有一个固定的排序算法,都是要根据情况分析的。但是如果你不会分析的情况下 选择归并或者快排。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值