数据结构--十大排序算法

如何判断排序算法是否稳定?

如果在一个待排序的序列中,存在2个相等的数,在排序后这2个数的相对位置保持不变,那么该排序算法是稳定的;否则是不稳定的。

一、冒泡排序 --稳定

1、思想:

每次遍历数组选出一个最大与后面的交换

  • 外层循环:1<= i <=arr.length-1
  • 内层循环:0<= j <arr.length-i

2、时间复杂度:

  • 平均:O(n2)
  • 最坏:O(n2)
  • 最好:O(n2)

3、代码:

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

4、冒泡优化:

如果已经排好序了就结束循环

public void mapperSort1(int[] arr){
    for(int i=1;i<=arr.length-1;i++){
        boolean isSorted=true;
        for(int j=0;j<arr.length-1;j++){
            if (arr[j]>arr[j+1]){
                swap(arr,j,j+1);
            }
        }
        if (isSorted){
            break;
        }
    }
}

二、选择排序--不稳定

1、思想:

  • 第一步,找到数组最小的元素,把它和数组的第一个元素交换位置
  • 第二步,在剩下的元素中继续寻找最小的元素,与数组的第二个元素交换位置
  • 循环,直到整个数组排序完成

  • 外层循环:0 <= i < arr.length-1
  • 内层循环:i+1<= j <arr.length

2、时间复杂度:

  • 平均:O(n2)
  • 最坏:O(n2)
  • 最好:O(n2)

3、代码:

public void selectSort(int[] arr){
    for (int i=0;i<arr.length-1;i++){
        for(int j=i+1;j<arr.length;j++){
            if (arr[j]<arr[i]){
                swap(arr,j,i);
            }
        }
    }
}

三、插入排序-稳定

1、思想

  • 把一个数组分成未排序和已排序两部分,每次从未排序取一个数插入到已排序的数组中
  • 对于一个还未开始排序的数组,假设第一个数是已排序区域,右边是未排序区域,取右边第一个数插入左边,记录这个数值,与已排序的区域比较,比他大的右移,a[j+1]=a[j],最后找到待插入的位置,赋值给记录数值

2、时间复杂度:

  • 平均:O(n2)
  • 最坏:O(n2)
  • 最好:O(n)

O(n):在完全有序的情况下,插入排序每个未排序区间元素只需要比较1次,所以时间复杂度是O(n)。

3、代码

public void insertSort(int[] arr){
    for(int i=1;i<arr.length;i++){//待排序
        int value=arr[i];
        int j=0;
        for(j=i-1;j>=0;j--){//已排序
            if(value<arr[j]){
                arr[j+1]=arr[j];//后移,腾位置
            }else {
                break;
            }
        }
        //j多减了一次
        arr[j+1]=value;
    }
}

四、希尔排序-不稳定

1、思想:

  • 希尔排序也叫”缩小增量排序“,是插入排序的一种更高效的改进版本
  • 插入排序对于大规模的乱序数组效率是比较慢的,因为他每次只能将数据移动一位,希尔排序为了加快插入的速度,让数据移动的时候可以实现跳跃移动,节约时间
  • 希尔排序能够以较大的步伐将小元素往前送,这样大大的减少了需要比较的次数,从而提高了速度
  • 设置跳步数,gap=n/2;每隔gap个数据设为一组数据,给这一组数据完成插入排序,再让gap/=2,

直到gap=0

2、时间复杂度:

  • 平均:O(n1.3)
  • 最坏:O(n2)
  • 最好:O(n)

3、代码:

public void shellSort(int[] arr){
    int n=arr.length;
    int gap=0;
    for(gap=n/2;gap>0;gap/=2){
        for(int i=gap;i<n;i++){//第一轮gap比较晚,比较大的数据都在后面了
            int value=arr[i];
            int j=i-gap;
            for(j=i-gap;j>=0;j-=gap){
                if (value<arr[j]){
                    arr[j+gap]=arr[j];
                    System.out.println("次");
                }else {
                    break;
                }
            }
            arr[j+gap]=value;
        }
    }
}

五、归并排序--稳定

1、思想:

归并算法的核心思想是分治法,就是将一个数组一刀切两半,递归切,直到切成单个元素,然后重新组装合并,单个元素合并成小数组,两个小数组合并成大数组,直到最终合并完成,排序完毕

分治:字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并

2、时间复杂度

  • 平均:O(nlog2n)
  • 最坏:O(nlog2n)
  • 最好:O(nlog2n)

3、代码

public void mergeSort(int[] arr,int l,int r){
    if(l<r){
        int mid=(l+r)/2;
        mergeSort(arr,l,mid);
        mergeSort(arr,mid+1,r);
        merg(arr,l,r,mid+1);
    }
}

private void merg(int[] arr, int l, int r, int mid) {
    int[] temp=new int[arr.length];
    int i=l,j=mid,p=l;
    //i:左序列指针
    //j:右序列指针
    //p:temp数组的指针
    while (i<mid&&j<=r){//左右序列均不为空
        if (arr[i]<arr[j]){
            temp[p++]=arr[i++];
        }else {
            temp[p++]=arr[j++];
        }
    }
    while (i<mid){
        temp[p++]=arr[i++];
    }
    while (j<=r){
        temp[p++]=arr[j++];
    }
    //把临时数组temp再赋值给arr数组
    p=l;i=l;
    while (p<=r){
        arr[i++]=temp[p++];
    }
}

六、快速排序--不稳定

1、思想

排序排序所采用的思想是分治思想。所谓分治,就是指以一个数为基准,将序列中的其他数往它两边扔。比它小的都扔在它的左边,比它大的都扔在它的右边,然后左边两边再分别重复这个操作,不停地分,直至分到每一个分区地基准数地左边或者右边都只剩一个数为之。

2、时间复杂度

  • 平均:O(nlog2n)
  • 最坏:O(n2)
  • 最好:O(nlog2n)

3、步骤

  • 选取基准 pivot,把所有比pivot小的数放在左边,把比pivot大的数放在右边
  • 对于pivot来说,他就处在数组中的有序状态
  • 对pivot左边进行一次快排
  • 对pivot右边进行一次快排

4、代码

public void quickSort(int[] arr,int left,int right){
    if (left<right){
        int mid=getMid(arr,left,right); //第一步
        quickSort(arr,left,mid);        //第二步
        quickSort(arr,mid+1,right);     //第三步
    }
}

private int getMid(int[] arr, int left, int right) {
    int pivot=arr[left];
    //left:检索左边,检查到比pivot大的,arr[right]=arr[left]
    //right:检索右边,检查到比pivot小的,arr[left]=arr[right]
    while (left<right){
        while (arr[right]>pivot&&left<right){
            right--;
        }//出循环地条件:右边找到了比pivot小的
        arr[left]=arr[right];
        while (arr[left]<pivot&&left<right){
            left++;
        }//出循环地条件:左边找到了比pivot大的
        arr[right]=arr[left];
    }
    arr[left]=pivot;
    return left;

七、堆排序--不稳定

1、思想

        我们可以把堆(以下全都默认为最大堆)看成一棵完全二叉树,但是位于堆顶的元素总是整棵树的最大值,每个子节点的值都比父节点小,由于堆要时刻保持这样的规则特性,所以一旦堆里面的数据发生变化,我们必须对堆重新进行一次构建。

  既然堆顶元素永远都是整棵树中的最大值,那么我们将数据构建成堆后,只需要从堆顶取元素不就好了吗? 第一次取的元素,是否取的就是最大值?取完后把堆重新构建一下,然后再取堆顶的元素,是否取的就是第二大的值? 反复的取,取出来的数据也就是有序的数据。

2、时间复杂度

  • 平均:O(nlog2n)
  • 最坏:O(nlog2n)
  • 最好:O(nlog2n)

3、代码

/**
     * 堆排序
     * @param arr 排序数组
     */
    public static void sort(int[] arr) {

        int length = arr.length;

        //构建堆
        buildHeap(arr, length);

        for (int i = length - 1; i > 0; i--) {
            //将堆顶元素与末位元素调换
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;

            //数组长度-1 隐藏堆尾元素
            length--;

            //将堆顶元素下沉 目的是将最大的元素浮到堆顶来
            sink(arr, 0, length);
        }
    }

    /**
     * 构建堆
     * @param arr 数组
     * @param length 数组范围
     */
    private static void buildHeap(int[] arr, int length) {
        for (int i = length / 2; i >= 0; i--) {
            sink(arr, i, length);
        }
    }

    /**
     * 下沉调整
     * @param arr 数组
     * @param index 调整位置
     * @param length 数组范围
     */
    private static void sink(int[] arr, int index, int length) {
        int leftChild = 2 * index + 1;//左子节点下标
        int rightChild = 2 * index + 2;//右子节点下标
        int present = index;//要调整的节点下标

        //下沉左边
        if (leftChild < length && arr[leftChild] > arr[present]) {
            present = leftChild;
        }

        //下沉右边
        if (rightChild < length && arr[rightChild] > arr[present]) {
            present = rightChild;
        }

        //如果下标不相等 证明调换过了
        if (present != index) {
            //交换值
            int temp = arr[index];
            arr[index] = arr[present];
            arr[present] = temp;

            //继续下沉
            sink(arr, present, length);
        }
    }

八、计数排序--稳定

1、思想(图解)

以下以[3,5,8,2,5,4]这组数字来演示。

首先,我们找到这组数字中最大的数,也就是8,创建一个最大下标为8的空数组arr。

遍历数据,将数据的出现次数填入arr中对应的下标位置中。

遍历arr,将数据依次取出即可。

2、时间复杂度

  • 平均:O(n+k)
  • 最坏:O(n+k)
  • 最好:O(n+k)

3、代码

public void jishuSort(int[] arr){
    int max=arr[0];
    for(int i=1;i<arr.length;i++){
        if(arr[i]>max){
            max=arr[i];
        }
    }
    //初始化计数数组
    int[] countArr=new int[max+1];

    //计数
    for(int i=0;i<arr.length;i++){
        countArr[arr[i]]++;
        arr[i]=0;
    }

    //排序
    int index=0;
    for(int i=0;i<countArr.length;i++){
        while (countArr[i]-->0){
            arr[index++]=i;
        }
    }
}

九、基数排序--稳定

1、思想

基数排序是一种非比较型整数排序算法,其原理是将数据按位数切割成不同的数字,然后按每个位数分别比较

2、时间复杂度

  • 平均:O(n*k)
  • 最坏:O(n*k)
  • 最好:O(n*k)

3、代码

public static void sort(int[] arr)
    {
        int length = arr.length;

        //最大值
        int max = arr[0];
        for(int i=0;i<length;i++){
            if(arr[i] > max){
                max = arr[i];
            }
        }
        //当前排序位置
        int location = 1;

        //桶列表
        ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();

        //长度为10 装入余数0-9的数据
        for(int i = 0; i < 10; i++){
            bucketList.add(new ArrayList());
        }

        while(true)
        {
            //判断是否排完
            int dd = (int)Math.pow(10,(location - 1));
            if(max < dd){
                break;
            }

            //数据入桶
            for(int i = 0; i < length; i++)
            {
                //计算余数 放入相应的桶
                int number = ((arr[i] / dd) % 10);
                bucketList.get(number).add(arr[i]);
            }

            //写回数组
            int nn = 0;
            for (int i=0;i<10;i++){
                int size = bucketList.get(i).size();
                for(int ii = 0;ii < size;ii ++){
                    arr[nn++] = bucketList.get(i).get(ii);
                }
                bucketList.get(i).clear();
            }
            location++;
        }
    }

十、桶排序--稳定

1、思想

桶排序可以看成是计数排序的升级版,它将要排的数据分到多个有序的桶里,每个桶里的数据再单独排序,再把每个桶的数据依次取出,即可完成排序。

2、时间复杂度

  • 平均:O(n+k)
  • 最坏:O(n2)
  • 最好:O(n)

3、代码

public static void sort(int[] arr){

        //最大最小值
        int max = arr[0];
        int min = arr[0];
        int length = arr.length;

        for(int i=1; i<length; i++) {
            if(arr[i] > max) {
                max = arr[i];
            } else if(arr[i] < min) {
                min = arr[i];
            }
        }

        //最大值和最小值的差
        int diff = max - min;

        //桶列表
        ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();
        for(int i = 0; i < length; i++){
            bucketList.add(new ArrayList<>());
        }

        //每个桶的存数区间
        float section = (float) diff / (float) (length - 1);

        //数据入桶
        for(int i = 0; i < length; i++){
            //当前数除以区间得出存放桶的位置 减1后得出桶的下标
            int num = (int) (arr[i] / section) - 1;
            if(num < 0){
                num = 0;
            }
            bucketList.get(num).add(arr[i]);
        }

        //桶内排序
        for(int i = 0; i < bucketList.size(); i++){
            //jdk的排序速度当然信得过
            Collections.sort(bucketList.get(i));
        }

        //写入原数组
        int index = 0;
        for(ArrayList<Integer> arrayList : bucketList){
            for(int value : arrayList){
                arr[index] = value;
                index++;
            }
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值