排序

2020-03-26  本文已影响0人  yaco

常见排序算法


冒泡排序

    // 冒泡排序
   public static void bubbleSort(int[] arr) {
       if (arr == null || arr.length < 2) {
           return;
       }
       for (int i = arr.length - 1; i > 0; i--) {
           for (int j = 0; j < i; j++) {
               if (arr[j] > arr[j + 1]) {
                   swap(arr, j, j + 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 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) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

选择排序

    // 选择排序
   public static void selectSort(int[] arr) {
       if (arr == null || arr.length < 2) return;
       for (int i = 0; i < arr.length - 1; i++) {
           int minIndex = i;
           for (int j = i + 1; j < arr.length; j++) {
               minIndex = arr[j] >= arr[minIndex] ? minIndex : j;
           }
           swap(arr,minIndex,i);
       }
   }
   
       // 交换元素
   public static void swap(int[] arr, int i, int j) {
       int temp = arr[i];
       arr[i] = arr[j];
       arr[j] = temp;
   }


快速排序(重要)

  1. 基本思想: 在数组中随机挑选出一个数,大于该数的放在数组右边,小于此数的数的放在数组左边,等于该数组的元素放在中间
  2. 时间复杂度:N* logN 不稳定排序,(可以做成稳定的,成本太大) ,空间复杂度LogN
  3. 注意: 快速排序可以做到稳定性问题,但是非常难,不需要掌握,可以搜“01 stable sort”
  4. 代码:
    // 快排
   public static void quickSort2(int[] arr) {
       if(arr.length > 1){
           sortHelp(arr,0,arr.length - 1);
       }
   }

   // 对指定位置元素进行排序,构建递归
   public static void sortHelp(int[] arr, int l, int r) {
       // 当arr排序范围有意义时才执行
       if(l < r){
           // 从l到r的位置随机找出一个值
           int temp = arr[l + (int)Math.random() * (r - l + 1)];
           // 将temp加入快排主程序,小于temp放左边,等于放中间,大于放右边
           int[] p = sortProcess(arr,l,r,temp);
           // 对余下的左边进行排序(左边一定都小于右边)
           sortHelp(arr,l,p[0]);
           // 对余下的右边进行排序
           sortHelp(arr,p[1],r);
       }
   }

   // 快排主函数
   private static int[] sortProcess(int[] arr, int l, int r, int temp) {
       // 定义左右指针,表明已经加入了多少
       int less = l - 1;
       int more = r + 1;
       // 如果l指针与more指针相交,结束循环
       while(l < more){
           if(arr[l] < temp){
               // 如果当前的元素小于temp,那么放l位置的元素,扔到前面去
               swap(arr,++less,l++);
           }else if(arr[l] > temp){
               // 如果当前的元素大于temp,那么放l位置的元素,扔到后面去
               swap(arr,--more,l);
           }else{
               // 如果等于,不操作,直接移动l指针
               l++;
           }
       }
       // 将左右两个边界返回
       return new int[]{less,more};
   }
   
   // 交换元素
   private static void swap(int[] arr, int i, int j) {
       int temp = arr[i];
       arr[i] = arr[j];
       arr[j] = temp;
   }

快排应用

荷兰国旗问题:
给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边

// 荷兰国旗问题
public static void netherlandsFlags(int[] arr, int num) {
    if (arr == null || arr.length < 2) return;
    int less = -1;
    int more = arr.length;
    int cur = 0;
    while (cur < more) {
        if (arr[cur] < num) {
            swap(arr, ++less, cur++);
        } else if (arr[cur] > num) {
            swap(arr, cur, --more);
        } else {
            cur++;
        }
    }
}

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


归并排序

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

   // 分治
   private static void mergeSort(int[] arr, int l, int r) {
       if (l == r) return;
       int mid = l + ((r -l) >> 1);
       mergeSort(arr, l, mid);
       mergeSort(arr, mid + 1, r);
       merge(arr,l,mid,r);
   }

   // 归并
   private static void merge(int[] arr, int l, int mid, int r) {
       int[] helper = new int[r - l + 1];
       int i = 0;
       int p1 = l;
       int p2 = mid + 1;
       while(p1 <= mid && p2 <= r) {
           helper[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
       }
       while(p1 <= mid) {
           helper[i++] = arr[p1++];
       }
       while(p2 <= r) {
           helper[i++] = arr[p2++];
       }
       //将辅助的数组拷贝回原数组中
       for (int j = 0; j < helper.length; j++) {
           arr[l + j] = helper[j];
       }
   }

归并排序应用

一、小和问题
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组
的小和
例子:
[1,3,4,2,5]
1左边比1小的数,没有;
3左边比3小的数,1;
4左边比4小的数,1、3;
2左边比2小的数,1;
5左边比5小的数,1、3、4、2;
所以小和为1+1+3+1+1+3+4+2=16

// 分治归并的思想求解
public static int smallSum(int[] arr) {
    if (arr == null || arr.length < 2) return 0;
    return mergeSum(arr, 0, arr.length - 1);
}

// 分治(分治的时候干了两件事,一是记录小和,二是排序)
private static int mergeSum(int[] arr, int l, int r) {
    if (l == r) return 0;
    int mid = l + ((r - l) >> 1);
    return mergeSum(arr, l, mid) + mergeSum(arr, mid + 1, r) + merge(arr, l, mid, r);
}

// 归并
private static int merge(int[] arr, int l, int mid, int r) {
    int helper[] = new int[r - l + 1];
    int i = 0;
    int p1 = l;
    int p2 = mid + 1;
    int ans = 0;
    // 如果p1位的元素小于p2位的元素,那么p2位之后所有元素都比p1位大
    while (p1 <= mid && p2 <= r) {
        ans += arr[p1] < arr[p2] ? (arr[p1] * (r - p2 + 1)) : 0;
        helper[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
    }
    while (p1 <= mid) {
        helper[i++] = arr[p1++];
    }
    while (p2 <= r) {
        helper[i++] = arr[p2++];
    }
    for (int j = 0; j < helper.length; j++) {
        arr[l + j] = helper[j];
    }
    return ans;
}

二、数组中的逆序对(leecode面试题51(hard))
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。


// 逆序对问题
public static List<List<Integer>> reversePairs(int[] arr){
    List<List<Integer>> ans = new ArrayList<>();
    if(arr.length > 1) {
        ans = getAns(arr, 0, arr.length - 1);
    }
    return ans;
}

// 分治
private static List<List<Integer>> getAns(int[] arr, int l, int r) {
    List<List<Integer>> ans = new ArrayList<>();
    if(l == r) return ans;
    int mid = l + ((r - l) >> 1);

    List<List<Integer>> left = getAns(arr,l,mid);       // 求出左边的所有逆序对
    List<List<Integer>> right = getAns(arr,mid+1,r);  // 求出右边的所有逆序对
    List<List<Integer>> middle = merge(arr,l,r,mid);    // 合并之后的逆序对
    // 将三个部分分别加入结果集
    if(left.size() > 0){
        ans.addAll(left);
    }
    if(right.size() > 0){
        ans.addAll(right);
    }
    if(middle.size() > 0){
        ans.addAll(middle);
    }
    return ans;
}

// 合并所有的逆序对
private static List<List<Integer>> merge(int[] arr, int l, int r, int mid) {
    List<List<Integer>> ans = new ArrayList<>();
    int[] help = new int[r - l + 1];
    int p1 = l;
    int p2 = mid + 1;
    int index = 0;
    while(p1 <= mid && p2 <= r){
        if(arr[p1] < arr[p2]){
            // 此时p2后面的所有元素,都比p1大
            for (int i = p2; i <= r; i++) {
                List<Integer> temp = new ArrayList<>();
                temp.add(arr[p1]);
                temp.add(arr[i]);
                ans.add(temp);
            }
            help[index++] = arr[p1++];
        }else{
            help[index++] = arr[p2++];
        }
    }
    while(p1 <= mid){
        help[index++] = arr[p1++];
    }
    while(p2 <= r){
        help[index++] = arr[p2++];
    }
    for (int i = 0; i < help.length; i++) {
        arr[l + i] = help[i];
    }
    return ans;
}

// 编写测试类
public static void main(String[] args) {
    int[] arr = {1,7,3,9,5,6};
    List<List<Integer>> res = reversePairs(arr);
    for (List<Integer> re : res) {
        System.out.println(re.toString());
    }
}

堆排序

    // 堆排序
   public static void heapSort(int[] arr) {
       if (arr == null || arr.length < 2) {
           return;
       }
       // 建堆过程  O(N)时间复杂度
       for (int i = 0; i < arr.length; i++) {
           heapInsert(arr,i);
       }
       // 交换数组头尾的元素位置,使得最大的元素沉入数组尾部
       int size = arr.length;
       swap(arr,0,--size);
       // 循环遍历调整--heapSize长度的数组成为大顶堆
       while(size > 0){
           heapFind(arr,size);
           swap(arr,0,--size);
       }
   }

   // 重新调整为大顶堆
   private static void heapFind(int[] arr, int size) {
       int left = 1;
       int index = 0;
       int largest = 0;
       while(left < size) {
           largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
           largest = arr[largest] > arr[index] ? largest : index;
           if(largest == index) break;
           swap(arr, largest, index);
           index = largest;
           left = (index * 2) + 1;
       }
   }

   // 向数组中插入元素构造称为大顶堆
   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;
       }
   }

   // 交换两个元素的位置
   private static void swap(int[] arr, int i, int j) {
       int temp = arr[i];
       arr[i] = arr[j];
       arr[j] = temp;
   }

桶排序

public static void bucketSort(int[] arr) {
    if(arr == null || arr.length < 2) {
        return;
    }
    // 找出数组中元素的最大值
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < arr.length; i++) {
        max =  Math.max(max,arr[i]);
    }
    // 创建max + 1个桶,用数组表示
    int[] bucket = new int[max + 1];
    // 将数组元素填入桶中
    for (int i = 0; i < arr.length; i++) {
        bucket[arr[i]]++;
    }
    // 将桶中元素取回,填回数组元素中
    int j = 0;
    for (int i = 0; i < bucket.length; i++) {
        while(bucket[i]-- > 0){
            arr[j++] = i;
        }
    }
}

桶排序的实际应用

一、 相邻元素最大间距问题 (leeCode164. 最大间距)
给定一个数组,求如果排序之后,相邻两个数的最大差值,要求时间复杂度N,且要求不能用非基于比较的排序方法

分析: 典型的桶排序案例, 一共有n个数,创建n+1个桶,让第一个桶和最后一个桶保证有元素,则最大的间隔一定回在有空桶的情况下出现。

    // 桶思想解决最大间距问题
public static int maxGap(int[] arr) {
    if(arr == null || arr.length < 2) {
        return 0;
    }
    // 找出数组中的最大值和最小值,用于确定数据所在的桶号
    int min = Integer.MAX_VALUE;
    int max = Integer.MIN_VALUE;
    int len = arr.length;
    for (int i = 0; i < len; i++) {
        min = Math.min(min,arr[i]);
        max = Math.max(max,arr[i]);
    }
    if(min == max) return 0;
    boolean[] hasNum = new boolean[len + 1]; //用于检测数组是否已经存在以元素
    int[] maxs = new int[len + 1];  //用于存放每个桶中最大的数
    int[] mins = new int[len + 1];  //用于存放每个桶中最小的数
    int bid = 0; //计算桶数
    for (int i = 0; i < len; i++) {
        bid = getBucket(arr[i],len,min,max);
        mins[bid] = hasNum[bid] ? Math.min(mins[bid],arr[i]) : arr[i];
        maxs[bid] = hasNum[bid] ? Math.max(maxs[bid],arr[i]) : arr[i];
        hasNum[bid] = true;
    }
    int res = 0; //计算结果
    int lastMax = maxs[0];  //第一个桶中肯定有元素
    int i = 1;
    for (; i < len + 1; i++) {
        if(hasNum[i]) {  // 如果桶不为空格
            res = Math.max(res, mins[i] - lastMax);
            lastMax = maxs[i];
        }
    }
    return res;
}

private static int getBucket(long num, long len, long min, long max) {
    return (int)((num - min) * len / (max - min));
}

补充

关于排序的几点补充内容:

  1. 排序的使用情况分析

    • 当样本量小于60时,一般采用插入排序,因为他的常熟项最低。
    • 当大样本量的基本数组类型进行排序时,默认使用快排,他是一种不稳定排序,对基本数组类型不会产生影响。
    • 当对大样本的自定义的对象进行排序时,默认使用归并排序,可以保证空间的稳定性。
  2. master公式的理解:(求解递归程序的时间复杂度)
    如果 T(N) = a * T(N / b) + O(N^d);那么时间复杂的计算公式如下:

    • log(b,a) > d -> 复杂度为O(N^log(b,a))
    • log(b,a) = d -> 复杂度为O(N^d * logN)
    • log(b,a) < d -> 复杂度为O(N^d)

排序算法的稳定性及汇总

排序算法稳定性说明

对数器的使用

    // Arrays提供的方法
    public static void comparator(int[] arr) {
        Arrays.sort(arr);
    }

    // 获取随机数组(提供最大长度,和最大值)
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
        }
        return arr;
    }

    // 拷贝数组
    public static int[] copyArray(int[] arr) {
        if (arr == null) {
            return null;
        }
        int[] res = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            res[i] = arr[i];
        }
        return res;
    }

    // 判断两个数组是否相等
    public static boolean isEqual(int[] arr1, int[] arr2) {
        if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
            return false;
        }
        if (arr1 == null && arr2 == null) {
            return true;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    // 打印数组
    public static void printArray(int[] arr) {
        if (arr == null) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

      // 冒泡排序
    public static void bubbleSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        for (int e = arr.length - 1; e > 0; e--) {
            for (int i = 0; i < e; i++) {
                if (arr[i] > arr[i + 1]) {
                    swap(arr, i, i + 1);
                }
            }
        }
    }

    // 测试冒泡排序
    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 100;
        int maxValue = 100;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
            bubbleSort(arr1);
            comparator(arr2);
            if (!isEqual(arr1, arr2)) {
                succeed = false;
                break;
            }
        }
        System.out.println(succeed ? "Nice!" : "Fucking fucked!");
    }

上一篇下一篇

猜你喜欢

热点阅读