排序

1 模板

1.1 辅助与测试函数

1.1.1 交换函数 swap()

public static void swap(int[] arr, int i, int j) {
    int temp;
    temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
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];
}

1.1.2 测试函数

public static void test(){
    int testTime = 500000;
    int maxSize = 100;
    int maxValue = 100;
    boolean succeed = true;
    for (int i = 0; i < testTime; i++) {
        int[] original = generateRandomArray(maxSize, maxValue);
        int[] arr1 = copyArray(original);
        int[] arr2 = copyArray(original);
        sortMethod(arr1);
        comparator(arr2);
        if (!isEqual(arr1, arr2)) {
            succeed = false;
            print(original);
            print(arr1);
            print(arr2);
            break;
        }
    }
    System.out.println(succeed ? "pass!" : "failed!");
}

public static int[] generateRandomArray(int maxSize, int maxValue) {
    int[] arr = new int[(int) (Math.random() * (maxSize - 1))];
    for (int i = 0; i < arr.length; i++) {
        arr[i] = (int) (Math.random() * maxValue);
    }
    return arr;
}

public static void comparator(int[] arr) {
    Arrays.sort(arr);
}

public static int[] copyArray(int[] arr) {
    if (arr == null) {
        return arr;
    }
    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 print(int[] arr) {
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i] + " ");
    }
    System.out.println();
}

1.2 排序模板

1.2.1 冒泡排序

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

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);
            }
        }
    }
}

1.2.2 插入排序

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

思想:
无序部分插入有序部分,例如:

有序部分 ] 无序部分
3]5 8 4 7
3 5]8 4 7
3 5 8]4 7
3 4 5 8]7
3 4 5 7 8]

public static void insertionSort(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);
        }
    }
}

1.2.3 选择排序

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

思想:
找到最小的放入有序部分。

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

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

1.2.4 堆排序

时间复杂度:O(nlogn)
稳定性:不稳定
空间复杂度:O(1)

特点:1、堆是一个完全二叉树。2、每个结点大于或者等于它的任意一个结点。
完全二叉树:除最后一层每一层都被填满,如果最后一层没有填满那么它的叶节点全部是偏向左的。

存储:如果堆的大小是已知的那么可以将堆存储在ArrayList里面,位置i处的节点左孩子是2i+1,右孩子是2i+2,父节点是(i-1)/2。

过程:将需要排序的数据全部加入到堆中,然后迭代地返回根结点。根是最大数的节点。

/**
 * 堆排序
 * <p>
 * 大根堆 -> 升序
 * 小根堆 -> 降序
 * <p>
 * 堆不需要二叉树,应该用数组存储
 * (i - 1) / 2 : parentNode
 * i * 2 + 1 : leftChild
 * i * 2 + 2 : rightChild
 * <p>
 * heapInsert 插入数据
 * heapify 交换位置后调整堆
 */
public static void heapSort(int[] nums) {
    if (nums == null || nums.length == 0) {
        return;
    }

    for (int i = 0; i < nums.length; i++) {
        heapInsert(nums, i);
    }

    int size = nums.length;
    while (size > 0) {
        swap(nums, 0, size - 1);
        size--;
        heapify(nums, 0, size);
    }
}

private static void heapInsert(int[] nums, int index) {
    while (nums[index] > nums[(index - 1) / 2]) {
        swap(nums, index, (index - 1) / 2);
        index = (index - 1) / 2;
    }
}

private static void heapify(int[] nums, int index, int size) {
    int left = (index * 2 + 1);
    while (left < size) {
        int maxIndex = left + 1 < size && nums[left] < nums[left + 1] ? left + 1 : left;
        maxIndex = nums[index] > nums[maxIndex] ? index : maxIndex;
        if (maxIndex == index) {
            break;
        }
        swap(nums, maxIndex, index);
        index = maxIndex;
        left = index * 2 + 1;
    }

}

1.2.5 快速排序

时间复杂度:O(nlogn),长期期望
稳定性:不稳定
空间复杂度:在这里插入图片描述

最坏情况下归并排序效率大于快速排序,平均情况下两者的效率时间相同。归并排序需要的更多的额外空间。

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 size = arr.length;
    while (size > 0){
        swap(arr,0,size - 1);
        size--;
        heapify(arr,0,size);
    }
}


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;
    }
}

public static void heapify(int[] arr, int index, int size) {
    int left = index * 2 + 1;
    while (left < size) {
        int 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 quickSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    quickSort(arr, 0, arr.length - 1);
}

public static void quickSort(int[] arr, int l, int r) {
    if (l < r) {
        int p = arr[(int) (l + Math.random() * (r - l + 1))];
        int partition[] = partition(arr, l, r, p);
        quickSort(arr, l, partition[0] - 1);
        quickSort(arr, partition[1] + 1, r);
    }
}

public static int[] partition(int[] arr, int l, int r, int p) {
    int less = l - 1;
    int more = r + 1;
    while (l < more) {
        if (arr[l] < p) {
            swap(arr, ++less, l++);
        } else if (arr[l] > p) {
            swap(arr, l, --more);
        } else {
            l++;
        }
    }
    return new int[]{less + 1, more - 1};
}

1.2.6 归并排序

时间复杂度:O(nlogn)
稳定性:稳定
空间复杂度:O(n)

思想:
两个有序数组的合并。

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

public static void mergeSort(int[] arr, int l, int r) {
    if (l == r) {
        return;
    }
    int m = l + (r - l) / 2;
    mergeSort(arr, l, m);
    mergeSort(arr, m + 1, r);
    merge(arr, l, m, r);
}

public static void merge(int[] arr, int l, int m, int r) {
    int[] help = new int[r - l + 1];

    int i = 0;
    int p1 = l;
    int p2 = m + 1;

    while (p1 <= m && p2 <= r){
        help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    }

    while (p1 <= m){
        help[i++] = arr[p1++];
    }

    while (p2 <= r){
        help[i++] = arr[p2++];
    }

    for (i = 0;i < help.length;i++){
        arr[l + i] = help[i];
    }
}

1.2.7 计数排序(特殊的桶排序)

时间复杂度:O(n+N)
空间复杂度:O(n+N)
稳定性:不稳定
适用类型:小整数的排序,整数过大使用基数排序。

public static void countingSort(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]);
    }

    int[] bucket = new int[max + 1];
    for (int i = 0; i < arr.length; i++) {
        bucket[arr[i]]++;
    }

    int i = 0;
    for (int j = 0; j < bucket.length; j++) {
        while (bucket[j] > 0){
            arr[i++] = j;
            bucket[j]--;
        }
    }
}

1.2.8 基数排序

时间复杂度:O(dn)基数位置的最大值。例如213基数位置的最大值是3,相当于进行了d次桶排序。
稳定性:稳定
空间复杂度:O(dn)
特点:使用十个桶,桶排序是稳定的。

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

/**
 * 获得数列中最大数的位数
 */
public static int maxbits(int[] arr) {
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < arr.length; i++) {
        max = Math.max(max, arr[i]);
    }
    int res = 0;
    while (max != 0) {
        max = max / 10;
        res++;
    }
    System.out.println(res);
    return res;
}

public static void radixSort(int[] arr, int begin, int end, int digit) {
    final int radix = 10;
    int i = 0, j = 0;
    int[] count = new int[radix];
    int[] bucket = new int[end - begin + 1];

    for (int d = 1; d <= digit; d++) {
        for (i = 0; i < radix; i++) {
            count[i] = 0;
        }

        for (i = begin; i <= end; i++) {
            j = getDigit(arr[i],d);
            count[j]++;
        }

        
        for (i = 1; i < radix; i++) {
            count[i] = count[i] + count[i - 1];
        }

        for (i = end; i >= begin; i--) {
            j = getDigit(arr[i], d);
            bucket[count[j] - 1] = arr[i];
            count[j]--;
        }

        for (i = begin, j = 0; i <= end; i++, j++) {
            arr[i] = bucket[j];
        }
    }
}

public static int getDigit(int x, int d) {
    return ((x / (int) Math.pow(10, d - 1)) % 10);
}

2 排序相关例题

2.1 LeetCode 88. 合并两个有序数组

题目描述:
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

用例:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3

输出: [1,2,2,3,5,6]

实现思路:
归并排序。

参考代码:

public void merge(int[] nums1, int m, int[] nums2, int n) {
    for(int i = m - 1 ; i >= 0 ; i--){
        nums1[i + nums1.length - m] = nums1[i];
    }
    
    int i = 0;
    int p1 = nums1.length - m;
    int p2 = 0;

    while(p1 < nums1.length && p2 < n){
        nums1[i++] = nums1[p1] <= nums2[p2] ? nums1[p1++] : nums2[p2++];
    }

    while(p1 < nums1.length){
        nums1[i++] = nums1[p1++];
    }

    while(p2 < n){
        nums1[i++] = nums2[p2++];
    }
}

2.2 剑指 Offer 45. 把数组排成最小的数

题目描述:
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

用例:

示例 1:

输入: [10,2]
输出: “102”

示例 2:

输入: [3,30,34,5,9]
输出: “3033459”

实现思路:

  1. 全排列,再对String进行排序。组合数为 (N!),复杂度较大。
  2. 定义StrComparetor,然后排序。时间复杂度O(nlogn)。

参考代码:

public String minNumber(int[] nums) {
    String[] strs = new String[nums.length];
    for (int i = 0; i < nums.length; i++) {
        strs[i] = String.valueOf(nums[i]);
    }
    Arrays.sort(strs,new StrComparetor());
    StringBuilder res = new StringBuilder();
    for (int i = 0; i < strs.length; i++) {
        res.append(strs[i]);
    }
    return res.toString();
}

public static class StrComparetor implements Comparator<String>{
    @Override
    public int compare(String m, String n) {
        String mn = m + n;
        String nm = n + m;
        return mn.compareTo(nm);
    }
}

2.3 LeetCode 75. 颜色分类(荷兰国旗问题)

题目描述:
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

用例:

示例:

输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]

实现思路:

  1. 计数排序。
  2. 快速排序。

参考代码:
计数排序

public void sortColors(int[] nums) {
    int[] count = new int[3];

    for(int i = 0; i < nums.length ;i++){
        count[nums[i]]++;
    }

    int i = 0;
    for(int j = 0; j < count.length ; j++){
        while(count[j] > 0){
            nums[i++] = j;
            count[j]--;
        }
    }
}

快速排序

public void sortColors(int[] nums) {
    int l = 0;
    int r = nums.length - 1;
    int less = l - 1;
    int more = r + 1;
    int p = 1;
    while(l < more){
        if(nums[l] < p){
            swap(nums,++less,l++);
        }else if(nums[l] > p){
            swap(nums,l,--more);
        }else{
            l++;
        }
    }
}

public void swap(int[] nums,int i,int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

2.4 n数和问题

2.4.1 LeetCode 1. 两数之和

题目描述:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

用例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

实现思路:
Hash记录

参考代码:

public int[] twoSum(int[] nums, int target) {
	HashMap<Integer,Integer> map = new HashMap();
	int[] res = new int[2];
	
	for(int i = 0 ; i < nums.length ; i++){
	    if(map.containsKey(target - nums[i])){
	        res[0] = map.get(target - nums[i]);
	        res[1] = i;
	        return res;
	    }
	    map.put(nums[i],i);
	}
	return res;
}

2.4.2 LeetCode 15. 三数之和

题目描述:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

用例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]

实现思路:
排序,双指针,将3和问题转变为2和问题。注意去重。

参考代码:

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> resList = new LinkedList();
    Arrays.sort(nums);
    int i = 0;
    int pre;
    while (i < nums.length) {
        int s = i + 1;
        int e = nums.length - 1;
        int t = 0 - nums[i];
        while (s < e) {
            int sum = nums[s] + nums[e];
            if (sum < t) {
                s++;
            } else if (sum > t) {
                e--;
            } else {
                List<Integer> res = new LinkedList();
                res.add(nums[i]);
                res.add(nums[s]);
                res.add(nums[e]);
                resList.add(res);
                pre = nums[s];
                while(s < e && pre == nums[s]){
                    s++;
                }
            }
        }
        pre = nums[i];
        while (i < nums.length && pre == nums[i]) {
            i++;
        }
    }
    return resList;
}

2.4.3 LeetCode 18. 四数之和

题目描述:
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

用例:

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]

实现思路:
同三数和。

参考代码:

public List<List<Integer>> fourSum(int[] nums, int target) {
    Arrays.sort(nums);
    List<List<Integer>> resList = new LinkedList();
    int i = 0;
    int pre;
    while(i < nums.length){
        int j = i + 1;
        while(j < nums.length){
            int s = j + 1;
            int e = nums.length - 1;
            int t = target - nums[i] - nums[j];
            while(s < e){
                int sum = nums[s] + nums[e];
                if(sum < t){
                    s++;
                }else if(sum > t){
                    e--;
                }else{
                    List<Integer> res = new LinkedList();
                    res.add(nums[i]);
                    res.add(nums[j]);
                    res.add(nums[s]);
                    res.add(nums[e]);
                    resList.add(res);
                    pre = nums[s];
                    while(s < e && pre == nums[s]){
                        s++;
                    }
                }
            }
            pre = nums[j];
            while(j < nums.length && pre == nums[j]){
                j++;
            }
        }
        pre = nums[i];
        while(i < nums.length && pre == nums[i]){
            i++;
        }
    }
    return resList;
}

2.5 优先级队列

题目描述:
实现优先级队列。

实现思路:
堆排序。

参考代码:

public class MyPriorityQueue {

    private int size;

    private int[] queue;

    private int capacity;

    public MyPriorityQueue(int capacity) {
        this.capacity = capacity;
        queue = new int[capacity];
        size = 0;
    }

    public boolean offer(Integer x) {
        if (size == capacity) {
            capacity = capacity * 2;
            queue = Arrays.copyOf(queue, capacity);
        }
        queue[size] = x;
        shiftUp(size);
        size++;
        return true;
    }

    public Integer poll(){
        if (size == 0){
            return null;
        }
        int res = queue[0];
        swap(0, size - 1);
        size--;
        shiftDown(0);
        return res;
    }

    private void shiftUp(int index) {
        while (queue[index] > queue[(index - 1) / 2]) {
            swap(index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    private void shiftDown(int index){
        int left = index * 2 + 1;
        while (left < size){
            int maxIndex = left + 1 < size && queue[left + 1] > queue[left] ? left + 1 : left;
            maxIndex = queue[index] > queue[maxIndex] ? index : maxIndex;
            if(maxIndex == index){
                break;
            }
            swap(index,maxIndex);
            index = maxIndex;
            left = index * 2 + 1;
        }

    }

    public boolean isEmpty(){
        return size == 0;
    }

    public int getSize(){
        return size;
    }

    private void swap(int i, int j) {
        int t = queue[i];
        queue[i] = queue[j];
        queue[j] = t;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值