排序整合

我们都知道排序是非常重要的,排序的方法也是有很多,这里面有很多思想,所以应该重点掌握以下。

1.冒泡排序: 依次比较两个相邻的位置的大小,每次将最大的值放到最后一个位置;

时间复杂度: O(n^2)

public static void bubbleSort(int[] arr) {
        if(arr == null || arr.length <2) {
            return;
        }
        //首先说冒泡排序会进行n次,每一次都会确定最后一个位置的数
        for(int end = arr.length; end>0; end--) {
            //每一次遍历都要比较end个数的大小
            for(int i=0; i<end-1; i++) {
                //如果前面的比后面大,则交换
                if (arr[i] > arr[i+1]) {
                    swap(arr, i, i+1);
                }
            }
        }
    }
     //交换一个数组中的任意两个位置
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    

注意:交换两个数的位置还可以通过位运算:

//用位运算进行数字的交换
    public static void swap2(int[] arr, int i, int j) {
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];

2.选择排序: 每次都将第一个位置的数字与其他位置的数字作比较,将最小值放到第一个位置

时间复杂度: O(n^2)

public static void selectSort(int[] arr) {
        if(arr == null || arr.length<2) {
            return;
        }

        //冒泡是确定最后一个位置,所以从最后一个开始,而选择排序则是确定第一个位置,所以从第一个位置开始
        for (int i = 0; i<arr.length; i++) {
            int minIndex = i;
            //找到当前数中最小值的索引
            for (int j=i; j<arr.length; j++) {
               minIndex = (arr[j]<arr[minIndex]? j: minIndex);
            }
            swap(arr, i, minIndex);
        }
    }

    //交换一个数组中的任意两个位置
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

3.插入排序: 要插入第i个位置,首先前i-1个位置是肯定已经比较好的,比较arr[i-1]和arr[i]的大小

时间复杂度:与数据状况有关:若是已经排好的情况,则为O(n),若是全无序,则是O(n^2)
但是一般时间复杂度都按照最差的结果,所以插入排序的事件复杂度为O(n^2)

 public static void insertSort(int[] arr) {
        if(arr == null || arr.length <2) {
            return;
        }
        //因为第一个数刚开始不需要比较,所以都从第二个数开始进行比较
        for(int i=1; i<arr.length; i++) {
            for(int j=i-1; j>=0 && arr[j]>arr[j+1]; j--) {
                swap(arr, j, j+1);
            }
        }

    }

    //用位运算进行数字的交换
    public static void swap(int[] arr, int i, int j) {
        arr[i] = arr[i] ^ arr[j];
        arr[j] = arr[i] ^ arr[j];
        arr[i] = arr[i] ^ arr[j];
    }

4.归并排序: 首先将数组分成两组,将左边排好序,将右边排好序,然后中间合并

时间复杂度可以根据master公式进行计算,为: O(n*log(n))

public static void mergeSort(int[] arr) {
        if(arr == null || arr.length<2) {
            return;
        }
        mergeProcess(arr, 0, arr.length-1);
    }

    public static void mergeProcess(int[] arr, int L, int R) {
       if(L == R) {
           return;
       }
        int mid = (L+R)/2; //等价: mid = L+((R-L) >> 1)
        mergeProcess(arr, L, mid);
        mergeProcess(arr, mid+1, R);
        merge(arr, L, mid, R);
    }

    //当arr数组的左右都排好顺序以后,该方法将左右整合成一个完全有序数组
    public static void merge(int[] arr, int L, int mid, int R) {
        int[] help = new int[R-L+1];
        int i = 0;
        int p1 = L;
        int p2 = mid+1;
        while (p1 <= mid && p2 <= R) {
            help[i++] = arr[p1] < arr[p2]? arr[p1++]: arr[p2++];
        }
        //两个必有且只有一个越界
        while (p1 <= mid) {
            help[i++] = arr[p1++];
        }
        while (p2 <= R) {
            help[i++] = arr[p2++];
        }
        //此时帮助数组中的数就是排好顺序的数组,将该数组复制到数组arr中就可以了.
        for(i=0; i<help.length; i++) {
            arr[L+i] = help[i];
        }
    }

5.快速排序
其实就是按照荷兰国旗的思想进行排序:给定一个数组,将最后一个数作为参数,将小于该数字的放到左边,,大于该数字的放到右边,等于该数字的放到中间,然后一直递归,就可以得到一个排好序的数组。
快排分为 经典快排随机快排
经典快排就是一直将数组的最后一个数作为分界点
随机快排是将数组中的任意一个位置的值与最后一个数字进行交换,使其时间复杂度转变为一个随机问题。
随机快排增加的代码:swap(arr, L+(int) (Math.random() * (R-L+1)), R);

时间复杂度:
经典快排: O(n^2)
随机快排: O(n*logn)

//快速排序,其实就是按照荷兰国旗的思想进行排序;
    //给定一个数组,将最后一个数作为参数,将小于该数字的放到左边,,大于该数字的放到右边,等于该数字的放到中间,然后一直递归,就可以得到一个排好序的数组
public static void quickSort(int[] arr, int L, int R) {
        if(L < R) {
            //随机快排,就是将任意一个位置与最后一个位置进行交换,长期期望的时间复杂度位O(nlog(n)),空间复杂度位O(logn)
            swap(arr, L+(int) (Math.random() * (R-L+1)), R);
            int[] p = partition(arr, L, R);
            quickSort(arr, L, p[0]-1);
            quickSort(arr, p[1]+1, R);
        }
    }
//将小于该数组的最后一个位置的值放到前面,将大于该数组的最后一个位置的值放到后面,
    // 等于该数字的值放到中间,并返回中间位置的两个边界的索引
	 public static int[] partition(int[] arr, int L, int R) {
        int less = L-1;
        int more = R;
        while (L < more) {
            if (arr[L] < arr[R]) {
                swap(arr, ++less, L++);
            } else if (arr[L] > arr[R]) {
                swap(arr, more--, L);
            } else {
                L++;
            }
        }
        return new int[]{less+1, more};
    }
    //交换顺序的方法
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

6.堆排序:
步骤:

  1. 将数组变为一个大根堆 heapInsert()

  2. 将大根堆的第一个数字与最后一个交换位置,并将堆的大小-1

  3. 将交换后的堆转变为继续转变为大根堆(heapfy())方法,然后返回2,直到堆的大小为0,该数组的大小就排好位置了

时间复杂度: O(n*logn)

//堆排序:
    // 1.将数组变为一个大根堆 heapInsert()
    //2.将大根堆的第一个数字与最后一个交换位置,并将堆的大小-1
    //3.将交换后的堆转变为继续转变为大根堆(heapfy())方法,然后返回2,知道堆的大小为0,该数组的大小就排好位置了
    public static void heapSort(int[] arr) {
        if(arr == null || arr.length < 2) {
            return;
        }
        //将数组变为一个大根堆
        for (int i=0; i< arr.length; i++) {
            heapInsert(arr, i);
        }
        int heapSize = arr.length;
        swap(arr, 0, --heapSize);
        while (heapSize > 0) {
            heapfy(arr, 0, heapSize);
            swap(arr, 0, --heapSize);
        }
    }
    //将一个数组中的前index个数变成一个大顶堆(完全二叉树):任何一颗子树的最大值都是这颗子树的头部
    public static void heapInsert(int[] arr, int index) {
        while (arr[index] > arr[(index-1)/2]) {
            swap(arr, index, (index-1)/2);
            index = (index-1)/2;
        }
    }
    //若大顶堆中任意一个位置变小了,则需要调整该大顶椎
    //@param: index:  表示当前数组中第index个数的值变小了
    //@param heapSize: 表示当前大顶堆的总个数
    //其实就是比较当前的值与左右孩子的值的大小,如果比当前左右孩子的值都大,则不需要做调整,否则,就要变化该大顶堆
    //左孩子: 2*index+1  右孩子: 2*index+2
    public static void heapfy(int[] arr, int index, int heapSize) {
        int left = 2*index+1;
        //首先必须保证当前的节点有左孩子
        while (left< heapSize) {
            //计算出左右孩子较大的那个值,并且其右孩子存在
            int largest = (left+1 < heapSize && arr[left+1] > arr[left])? left+1: left;
            //判断当前左右孩子的最大值是否比该节点的值大,
            // 如果大,则将largest转变为当前左右孩子中最大节点的坐标,并将左孩子的值转变为下一级
            //如果小,则不需要做转变,直接退出
            largest = arr[largest]>arr[index]? largest: index;
            if (largest == index) {
                break;
            }
            swap(arr, index, largest);
            left = 2*index + 1;
        }
    }

    //实现一个数组中任意两个位置交换
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

在以上的排序方法中,

  1. 具有稳定性的排序方法: 冒泡、插入、归并
  2. 不稳定的排序方法:选择排序、快速排序、堆排序

补充:
(1)可以用归并排序内部缓存法,使得归并排序的S(n)做到 O(1)
(2)快排一般不稳定,单也可以通过 01 stable sort 文章的思想实现稳定

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值