算法学习--排序算法

排序

1.排序算法的比较

排序算法平均时间复杂度最好情况最坏情况空间复杂度稳定性
冒泡排序O(n^2)O(n)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
插入排序O(n^2)O(n)O(n^2)O(1)稳定
希尔排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
快速排序O(nlogn)O(nlogn)O(n^2)O(logn)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
基数排序O(n*k)O(n*k)O(n*k)O(n+k)稳定
计数排序O(n+k)O(n+k)O(n+k)O(k)稳定
桶排序O(n+k)O(n+k)O(n^2)O(n+k)稳定
  • 稳定性:数组中有两个相同的元素a,b;排序前a在前,排序后a仍在前,则为稳定
  • 内部排序:所有排序操作在内存中完成
  • n:数据规模;k:“桶”的个数
  • 时间复杂度:一个算法执行所消耗的时间
  • 空间复杂度:运行完一个程序所需要的内存大小
  • 非稳定的排序:快 希 选 堆
  • 使用场景
  1. 冒泡的效率太低,每一轮将当前最大的放到后面,主要掌握数组中元素的交换
  2. 选择排序的效率较低,但经常用它的内部循环方式来寻找最大/最小值
  3. 插入排序的平均效率低,但当序列基本有序时,插入排序很快,在JDK的Arrays.sort()方法中,当数量较小时,会采用插入排序
  4. 希尔排序是对插入排序的一种改良
  5. 快速排序是软件工业中最常见的排序算法,用于求topk的问题
  6. 归并排序,空间换时间的方法,可以用来求逆序对
  7. 堆排序,使用大顶堆,适用于海量数据的排序(1-7都是基于比较的排序,最好情况是O(nlogn))
  8. 计数排序,可以说是最快的(O(n+k)),若数组中的数字比较稀疏的时候,会造成大量的空间浪费
  9. 桶排序,先分桶,将数据分为几块,再对每一个桶用其他方式排序,最终得到全部排序,要求数据是均匀分布,如果数据全部集中在一个桶,就会导致时间复杂度还是O(nlogn)
  10. 基数排序是整数数值型排序里面又稳又快的,开辟固定的空间

2.排序算法的实现

2.1 冒泡排序

  • 冒泡排序:每一轮选出最大的放到最后
  • 理论上共需要进行arr.length-1次循环
  • 每一趟循环的次数在逐渐减小(一趟少一次比较)
  • 如果某一趟排序种没有进行交换,则证明排序已经完成,无需后续循环
public static void bubbleSort(int[] arr){
    if(arr==null || arr.length==0) return;
    for (int i = 0; i < arr.length-1; i++) {
        boolean flag = false;
        for (int j = 0; j < arr.length-1-i; j++) {
            if(arr[j] > arr[j+1]){
                arr[j] = arr[j]^arr[j+1];
                arr[j+1] = arr[j]^arr[j+1];
                arr[j] = arr[j]^arr[j+1];
                flag = true;
            }
        }
        if(!flag){
            return;
        }
    }
}

2.2 选择排序

  • 选择排序:每一次选择最小的放在前面
  • 第一次从数组中找到最小的跟第一个交换,第二次找出最小的跟第二个交换,以此类推
  • 总共经过n-1次交换(最坏情况)得到一个有序序列
  • 选择排序会优于冒泡排序(交换次数少一些)
public static void selectSort(int[] arr){
    if(arr == null || arr.length == 0) return;
    for (int i = 0; i < arr.length-1; i++) {
        int min = arr[i];
        int minIndex = i;
        for (int j = i+1; j < arr.length; j++) {
            if(arr[j] < min){
                min = arr[j];
                minIndex = j;
            }
        }
        if(minIndex != i){
            arr[minIndex] = arr[i];
            arr[i] = min;
        }
    }
}

2.3 插入排序

  • 把n个待排序元素看成一个有序表和一个无序表
  • 开始时有序表只包含一个元素,无序表包含n-1个元素
  • 对要排序的元素以插入的方式寻找该元素的适当位置
  • 每次从无序表中取出第一个元素,将它依次与有序表中的元素比较,找到位置
public static void insertSort(int[] arr){
    if(arr==null || arr.length==0) return;
    for (int i = 1; i < arr.length; i++) {
        int num = arr[i];
        int j = i - 1;
        //这里是>num而不能是>arr[i],arr[i]此时可能已经变了
        while (j > 0 && arr[j] > num){
            arr[j+1] = arr[j];
            j--;
        }
        //这里是j+1
        arr[j+1] = num;
    }
}

2.4 希尔排序

  • 插入排序时如果插入的数较小,会进行大量的移动
  • 希尔排序也是一种插入排序,是直接插入的改进,也称缩小增量排序
  • 把记录按下标的一定增量分组,对每组进行直接插入排序(gap=length/2)
  • 一趟一个增量,用增量进行分组,对每种进行插入排序
  • 改变增量,随着增量的逐渐减少,每组包含的关键词越来越多(gap=gap/2)
  • 当增量减小到1时,整个文件恰好被分成一组
//交换法
public static void shellSort(int[] arr){
    for(int gap=arr.length/2; gap>0; gap/=2){
        for (int i = gap; i < arr.length; i++) {
			//j -= gap是为了跟这个分组前面的几个数进行比较
            for (int j = i - gap; j >= 0; j -= gap) {
                if (arr[j] > arr[j + gap]) {
                    arr[j] = arr[j] ^ arr[j+gap];
                    arr[j+gap] = arr[j] ^ arr[j+gap];
                    arr[j] = arr[j] ^ arr[j+gap];
                }
            }
        }
    }
}
//插入法
public static void shellSort2(int[] arr){
    for(int gap=arr.length/2; gap>0; gap/=2){
    	//将插入排序中的1变为gap即可
        for (int i = gap; i < arr.length; i++) {
            int j = i;
            int temp = arr[j];
            if(arr[j] < arr[j-gap]){
               while(j-gap>=0 && temp<arr[j-gap]){
                    arr[j] = arr[j-gap];
                    j -= gap;
               }
               arr[j] = temp;
            }
        }
    }
}

2.5 快速排序

  • 快速排序是对冒泡排序的改进
  • 通过一趟排序,将排序的数组分成两个部分,一部分的所有数据比另一部分的所有数据小
  • 继续将两个部分再分别分成两个部分,依次类推,最终达到数组有序
  • Arrays.sort()方法底层是用的快速排序
public static void quickSort(int[] arr){
    quickSort(0, arr.length-1, arr);
}
private static void quickSort(int left, int right, int[] arr){
    int pivot = arr[(left+right)/2];
    int l = left;
    int r = right;
    while(l < r){
        while(arr[r] > pivot){
            r--;
        }
        while(arr[l] < pivot){
            l++;
        }
        if(l >= r) break;
        //交换arr[l]和arr[r]
        arr[l] = arr[r] ^ arr[l];
        arr[r] = arr[r] ^ arr[l];
        arr[l] = arr[r] ^ arr[l];
        if(arr[l] == pivot) r--;
        if(arr[r] == pivot) l++;
    }
    if(l == r){
        l++;
        r--;
    }
    if(left < r) quickSort(left, r, arr);
    if(right > l) quickSort(l, right, arr);
}

2.6 归并排序

  • 归并排序是利用归并的思想和分治策略
  • 分:将问题分成一些小问题,然后递归求解
  • 治:将分阶段得到的答案“修补“在一起,即分而治之
  • 合并的次数为n-1次
  • 需要额外的空间
public static void mergeSort(int[] arr){
    mergeSort(arr, 0, arr.length-1, new int[arr.length]);
}
private static void mergeSort(int[] arr, int left,  int right, int[] temp){
    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);
    }
}
private static void merge(int[] arr, int left, int mid, int right, int[] temp){
    int i = left;
    int j = mid +1;
    int t = 0;
    while (i <= mid && j <= right){
        int i1 = arr[i] <= arr[j] ? (temp[t++] = arr[i++]) : (temp[t++] = arr[j++]);
    }
    while (i <= mid){
        temp[t++] = arr[i++];
    }
    while (j <= right){
        temp[t++] = arr[j++];
    }
    t = 0;
    while (left <= right){
        arr[left++] = temp[t++];
    }
}

2.7 基数排序

  • 基数排序属于分配式排序,又称”桶子法“,是桶排序的扩展
  • 0-9一共10个桶,一个桶看作一个一维数组
  • 将每个元素的对应位数取出放在对应的桶(从个位开始直到最高位)
  • 按桶的顺序将数据取出放回原数组
  • 将整数按位数切割成不同的数字,然后按每位数分别比较
  • 将所有待比较的数值统一为同样的数位,前面补0
  • 从最低位开始,依次进行一次排序
  • 是效率高的稳定性排序方法,但会消耗额外的内存空间
  • 当数据足够大时,可能会出现 OutOfMemoryError
  • 数组中最好不要有负数
public static void radixSort(int[] arr){
    //定义一个二维数组,表示10个桶
    int[][] bucket = new int[10][arr.length];
    //定义一个一维数组,记录每个桶中每次放入数据的个数
    int[] bucketElementCounts = new int[10];
    //得到最大数的数的位数,即要进行的轮数
    int max = arr[0];
    for (int i = 1; i < arr.length; i++) {
        if(arr[i] > max){
            max = arr[i];
        }
    }
    int maxLength = (max + "").length();
    for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
        //针对不同位数进行排序
        for (int j = 0; j < arr.length; j++) {
            //取出对应位的值
            int digitOfElement = arr[j] / n % 10;
            //放入对应桶中
            bucket[digitOfElement][bucketElementCounts[digitOfElement]++] = arr[j];
        }
        //按照桶的顺序依次取出桶中的元素
        int index = 0;
        for (int k = 0; k < bucket.length ; k++) {
            //如果桶中有数据,才取出放入原数组
            if(bucketElementCounts[k] != 0){
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    arr[index++] = bucket[k][l];
                }

            }
            //每一轮结束后,需要清空bucketElementCounts[]
            bucketElementCounts[k] = 0;
        }
        System.out.println("第" + (i+1) + "轮后:" +Arrays.toString(arr));
    }
}

2.8 堆排序

  • 利用堆这种数据结构设计的一种选择排序
  • 先将待排序的序列构造成一个大顶堆,此时整个序列的最大值就是堆顶的根节点
  • 将其与末尾元素交换,此时末尾元素就是最大值
  • 将剩余的n-1个元素重新构造成一个大顶堆,依次类推,便得到一个有序序列
  • 大顶堆:每个节点的值都大于或等于其左右子节点的值,左右子节点的值不要求相对大小arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
  • 小顶堆:每个节点的值都小于或等于其左右子节点的值arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
public static void heapSort(int[] arr){
    int temp = 0;
    for (int i = arr.length/2-1; i >= 0; i--) {
        makeHeap(arr, i, arr.length-1);
    }
    for (int j = arr.length-1; j > 0; j--) {
        //交换
        temp = arr[j];
        arr[j] = arr[0];
        arr[0] = temp;
        makeHeap(arr, 0, j);
    }
    System.out.println(Arrays.toString(arr));
}

/*
 * @Description   完成将以i对应的非叶子节点的树调整成大顶堆
 * @param arr     待调整的数组
 * @param i       非叶子节点在数组中的索引
 * @param length  调整的元素的个数,在逐渐减少
 * @return void   将二叉树数组调整成大顶堆
 **/
private static void makeHeap(int[] arr, int i, int length){
    //取出当前元素的值
    int temp = arr[i];
    for (int k = (2*i+1); k < length; k=k*2+1) {
        if(arr[k] < arr[k+1] && (k+1) < length){//左子节点的值小于右子节点的值
            k++; //k指向右子节点
        }
        if(arr[k] > temp){//子节点的值大于父节点的值
            arr[i] = arr[k]; //将大的值赋给父节点/当前节点
            i = k; //i指向k继续循环比较
        }else{
            break;
        }
    }
    //for循环结束后,已经将以i为父节点的树的最大值放在了i的位置
    //将temp的值指向调整过后的位置
    arr[i] = temp;
}

2.9 计数排序

  • 一种典型的空间换时间的方法,要求数据不能太稀疏,不然会造成大量的空间浪费
  • 假设所有数据都小于1024,则创建一个int[] help = new int[1024],遍历原始数组,数组中的元素值为k,则就将help[k]的值加1,遍历完成后输出help中不为0的数据即可
public static void countSort(int[] arr){
    //假设所有的数据都小于1024
    int[] help = new int[1024];
    for (int i = 0; i < arr.length; i++) {
        help[arr[i]] += 1;
    }
    for (int i = 0; i < help.length; i++) {
        int t = help[i];
        if(t != 0){
            for (int j = 0; j < t; j++) {
                System.out.print(i + " ");
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值