介绍一些排序基础算法

  • 相关
  • 一、排序链表
  • 二、数组中的第K个最大元素
  • 2.1 快排优化方式
  • 2.2 堆排序方式


相关

一、排序链表

  • 排序链表,输入:head = [4,2,1,3] 输出:[1,2,3,4]
  • 很容易想到将链表转为集合(比如List),然后排序,这不符合学习算法的目的,为了理解掌握而不是只为了通过。
  • 此时会联想常用的排序算法,快排和堆排是O(nlogn),但为原址排序,考虑归并算法天然的合适。如果对于归并不太熟悉的,可以查看上一篇 Java排序算法(一)。
  • 程序入口
public static void main(String[] args) {
		
        ListNode head = new ListNode(4);
        ListNode curNode = head;

        curNode.next = new ListNode(2);
        curNode = curNode.next;
        curNode.next = new ListNode(1);
        curNode = curNode.next;
        curNode.next = new ListNode(3);

        ListNodeHelper.show("head", head);
        ListNode sortedHead = sortList(head);
        ListNodeHelper.show("sortedHead", sortedHead);
    }
  • 1.2 链表归并代码
/*
    归并
    4->2->1->3->null
     */
    public static ListNode sortList(ListNode head) {
        if (head == null) {
            return null;
        }
        return mergeSort(head);
    }

    /*
    目的:处理到只剩余一个节点
    4->2->1->3->null
        4->2->null
            4->null ----
                       |--> 2->4->null ------
            2->null ----                    |
                                            |----> 1->2->3->4->null
        1-3->null                           |
            1->null ----                    |
                       |--> 1->3->null ------
            3->null ----
     */
    private static ListNode mergeSort(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }

        // 1->null leftNode不为空 mid一定不为空
        ListNode mid = findMidListNode(head, null);
        ListNode tmp = mid.next;
        mid.next = null; // 断开
        ListNode left = mergeSort(head);
        ListNode right = mergeSort(tmp);
        return merge(left, right);
    }

    // 合并两个有序链表
    public static ListNode merge(ListNode leftNode, ListNode rightNode) {
        ListNode dummy = new ListNode(0);
        ListNode curNode = dummy;
        while (leftNode != null && rightNode != null) {
            if (leftNode.val < rightNode.val) {
                curNode.next = leftNode;
                leftNode = leftNode.next;
            } else {
                curNode.next = rightNode;
                rightNode = rightNode.next;
            }
            curNode = curNode.next;
        }
        if (leftNode != null) {
            curNode.next = leftNode;
        }
        if (rightNode != null) {
            curNode.next = rightNode;
        }
        return dummy.next;
    }

    private static ListNode findMidListNode(ListNode left, ListNode right) {
        ListNode slow = left;
        // 4->2->null // 这种寻找中间节点是错误的方式,会陷入死循环 => left=4->2->null  right=null
        // ListNode fast = left;
        ListNode fast = left.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

二、数组中的第K个最大元素

  • 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
  • 输入: [3,2,1,5,6,4] 和 k = 2 输出: 5
  • 只为了寻找第K个最大元素, 其实并不需要全局有序 。
  • 快速排序会利用划分元将数组分为左右两部分,左子部分小于等于划分元,右子部分大于划分元。通过比较划分元和目标顺位的位置关系。可以更快的找到结果。
  • 堆排序将原始堆划分为有序和无序两个部分,寻找第K个最大元素,也就是发生了K次堆顶元素的取用。

2.1 快排优化方式

  • 代码实现
public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        quickSort(nums, 0, len - 1, len - k);
        return nums[len - k];
    }

    private void quickSort(int[] nums, int left, int right, int target) {
        if (left < right) {
            int p = partition(nums, left, right);
            // 调试
            // ArrayHelper.show(nums);
            if (p == target) {
                return;
            } else if (p > target) { // 划分元在目标下标值右侧
                quickSort(nums, left, p - 1, target);
            } else {
                quickSort(nums, p + 1, right, target);
            }
        }
    }

    private int partition(int[] nums, int left, int right) {
        int i = left - 1;
        randomPickPartitionNum(nums, left, right);
        int x = nums[right];
        for (int j = left; j < right; j++) {
            if (nums[j] <= x) {
                i++;
                swap(nums, i, j);
            }
        }
        // 交换划分元位置
        swap(nums, i + 1, right);
        return i + 1;
    }

    /* 随机划分元
    [3 2 1 5 6 4]
    randPos = 1
    [3 4 1 5 6 2]
     */
    private void randomPickPartitionNum(int[] nums, int left, int right) {
        int range = right - left + 1;
        // 随机产生一个在[left,right]下标范围内的整数
        int randPos = left + random.nextInt(range);
        // System.out.println(String.format("[randPos:%s][randNum:%s]", randPos, nums[randPos]));
        swap(nums, randPos, right);
    }

    private void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
  • 程序入口
public static void main(String[] args) {
        int[] nums = new int[]{3, 2, 1, 5, 6, 4};
        // int[] nums = new int[]{3,1,2,4};
        int k = 2;
        // ArrayHelper.show("输入数组", nums);

        FindKthLargest dis = new FindKthLargest();
        int res = dis.findKthLargest(nums, k);
        System.out.println(String.format("第k个最大元素为: " + res));
    }
  • 结果预览
输入数组: 3 2 1 5 6 4 
 划分元: 6 ==> 划分后的数组: 3 2 1 5 4 6 
 划分元: 5 ==> 划分后的数组: 3 2 1 4 5 6 
 第k个最大元素为: 5

2.2 堆排序方式

  • 代码实现
// 堆排序 大根堆
    public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        // 初始化时指定无序堆最后一个元素下标
        int heapSize = len - 1;
        buildHeap(nums, heapSize);

        // 区k次堆顶元素,表示第k大的数
        for (int i = 1; i <= k; i++) {
            // ArrayHelper.show("第" + i + "次交换堆顶元素前", nums);
            swap(nums, 0, heapSize);
            // ArrayHelper.show("第" + i + "次交换堆顶元素后", nums);
            heapSize--;
            adjustHeap(nums, heapSize, 0);
        }

        return nums[len - k];
    }

    // 建堆 heapSize标识无序堆的之后一个元素下标
    private void buildHeap(int[] nums, int heapSize) {
        int parent = getParent(heapSize); // 最后一个非叶子节点
        for (int p = parent; p >= 0; p--) {
            adjustHeap(nums, heapSize, p);
        }
    }

    // 整堆
    private void adjustHeap(int[] nums, int heapSize, int idx) {
        int left = getLeftChild(idx);
        int right = getRightChild(idx);
        int largest = idx;
        if (left <= heapSize && nums[left] > nums[largest]) {
            largest = left;
        }
        if (right <= heapSize && nums[right] > nums[largest]) {
            largest = right;
        }
        if (largest != idx) {
            swap(nums, largest, idx);
            adjustHeap(nums, heapSize, largest);
        }
    }

    private int getLeftChild(int i) {
        return 2 * i + 1;
    }

    private int getRightChild(int i) {
        return 2 * i + 2;
    }

    private int getParent(int i) {
        return (i - 1) / 2;
    }

    private void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
  • 程序入口
public static void main(String[] args) {
        int[] nums = new int[]{3, 2, 1, 5, 6, 4};
        int k = 2;
        // ArrayHelper.show("输入数组", nums);

        FindKthLargest02 dis = new FindKthLargest02();
        int res = dis.findKthLargest(nums, k);
        System.out.println(String.format("第k个最大元素为: " + res));
    }
  • 结果预览
输入数组: 3 2 1 5 6 4 
 第1次交换堆顶元素前: 6 5 4 3 2 1 
 第1次交换堆顶元素后: 1 5 4 3 2 6 
 第2次交换堆顶元素前: 5 3 4 1 2 6 
 第2次交换堆顶元素后: 2 3 4 1 5 6 
 第k个最大元素为: 5