排序算法代码01--归并排序

排序算法代码01

归并排序

归并排序应用分治思想,如对一个整数数组升序排序,
分:将数组从中点一分为二,递归下去继续将子数组一分为二,直到拆不了;
治:将拆分成的最小单位逐个处理组合,在这里就是把元素两两排序,逐层往上合并。

//归并排序
    public void mergeSort(int[] nums, int l, int r){
        int[] tmp = new int[nums.length];
        if(l >= r){
            return;
        }
        int mid = l + ((r - l) >> 1);
        mergeSort(nums, l, mid);
        mergeSort(nums, mid + 1, r);
        //开始合并
        int i = l, j = mid + 1; //两个指针各指向需要合并的两个数组的首元素
        int p = 0; //合并后数组的指针,用于赋值时移动
        while(i <= mid && j <= r){
            if(nums[i] <= nums[j]){
                tmp[p++] = nums[i++];
            }else{
                tmp[p++] = nums[j++];
            }
        }
        //有一个数组到头了
        while(i <= mid){
            tmp[p++] = nums[i++];
        }
        while(j <= r){
            tmp[p++] = nums[j++];
        }
        for(int k = 0; k < r - l + 1; k++){
            nums[k + l] = tmp[k];
        }
    }

再例如对链表的归并排序,如Leetcode 148题

class Solution {
    public ListNode sortList(ListNode head) {
        return sortList(head, null);
    }
    public ListNode sortList(ListNode head, ListNode tail){
        //空链表,不需要排序
        if(head == null){
            return head;
        }
        //分治到最小级别的链表,即长度为1, 直接返回这一个节点
        if(head.next == tail){
            head.next = null;
            return head;
        }
        //快慢指针,定位中点
        ListNode slow = head, fast = head;
        while(fast != tail){
            slow = slow.next;
            fast = fast.next;
            if(fast != tail){
                fast = fast.next;
            }
        }
        ListNode mid = slow;
        //分治
        ListNode list1 = sortList(head, mid);
        ListNode list2 = sortList(mid, tail);
        //合并
        ListNode res = merged(list1, list2);
        return res;
    }
    public ListNode merged(ListNode list1, ListNode list2){
        ListNode dummyNode = new ListNode(-1);
        ListNode prev = dummyNode, prev1 = list1, prev2 = list2;
        while(prev1 != null && prev2 != null){
            if(prev1.val <= prev2.val){
                prev.next = prev1;
                prev1 = prev1.next;
            }else{
                prev.next = prev2;
                prev2 = prev2.next;
            }
            prev = prev.next;
        }
        prev.next = prev1 == null ? prev2 : prev1;
        return dummyNode.next;
    }
}

复杂度分析:
1.归并排序时间复杂度稳定,为O(nlogn)
2.不是原址排序,空间复杂度为O(n)。

快速排序

快排同样利用分治的思想,每次在数组中随机选择一个分区点,然后通过原址交换使分区点左边的元素都小于它,右边的元素都大于它,也就是每次分区都会将分区点的元素排到它的最终位置。每次分区之后再对分区点的左边的数组和右边的数组分别递归分区。

//快排
    public int[] quickSort(int[] nums){
        quickSelect(nums, 0, nums.length - 1);
        return nums;
    }
    //分治
    public void quickSelect(int[] nums, int l, int r){
        if(l < r){
            int pos = randomizedPartition(nums, l, r);
            quickSelect(nums, l, pos - 1);
            quickSelect(nums, pos + 1, r);
        }
    }
    //选取区间中一个随机的数作为基准值
    public int randomizedPartition(int[] nums, int l, int r){
        int i = new Random().nextInt(r - l + 1) + l;
        swap(nums,i, r);
        return partition(nums, l, r);
    }
    //原址快排
    //取最右边的为基准数,从左向右遍历,a[j] > x则i指针不动,a[j] <= x, 则交换++i指向的元素和
    //j遍历到的元素, 这样能保证i指针的左边都是小于基准数的,最后将基准数与i+1位置的数交换
    //就实现了让基准数左边都是小于它的,右边都是大于它的,i+1就是基准数排序最终结果该在的位置
    public int partition(int[] nums, int l, int r){
        int pivot = nums[r];
        int i = l - 1;
        for(int j = 1; j <= r - 1; ++j){
            if(nums[i] <= pivot){
                swap(nums, ++i, j);
            }
        }
        swap(nums, i + 1, r);
        return i + 1;
    }

    public void swap(int[]nums, int l, int r){
        int tmp = nums[l];
        nums[l] = nums[r];
        nums[r] = tmp;
    }

复杂度分析
1.快速排序是原址排序,空间复杂度为O(1);
2.快速排序的时间复杂度不稳定,最好情况和平均时间复杂度为O(nlogn),最坏情况为O(n^2)。
但如果某些场景只要求找到按某个顺序排列的某个位置上的数,比如算法题中要求求数组中的第k个最大值,或者前k个最大的值,这时用快排是效率较高的,如leetcode215.求数组中第k个最大的元素:

class Solution {
    Random random = new Random();
    public int findKthLargest(int[] nums, int k) {
        return quickSelect(nums, 0, nums.length - 1, nums.length - k);
    }
    //l,r:需要递归的区间左右边界,index:目标下标
    public int quickSelect(int[] a, int l, int r, int index){
        int q = randomPartition(a, l, r);//计算划分点,包括原址快排
        if(q == index){
            return a[q];
        }else{
            return q < index ? quickSelect(a, q + 1, r, index) : quickSelect(a, l, q - 1, index);
        }
    }
    //选取区间中一个随机的数作为基准值
    public int randomPartition(int[] a, int l, int r){
        int i = random.nextInt(r - l + 1) + l;//生成[l,r)区间随机位置
        swap(a, i, r);
        return partition(a, l, r);
    }
    //原址快排
    //取最右边的为基准数,从左向右遍历,a[j] > x则i指针不动,a[j] <= x, 则交换++i指向的元素和
    //j遍历到的元素, 这样能保证i指针的左边都是小于基准数的,最后将基准数与i+1位置的数交换
    //就实现了让基准数左边都是小于它的,右边都是大于它的,i+1就是基准数排序最终结果该在的位置
    public int partition(int[] a, int l, int r){
        int x = a[r], i = l - 1; //x为基准数据
        for(int j = l; j < r; ++j){
            if(a[j] <= x){
                swap(a, ++i, j);
            }
        }
        swap(a, i + 1, r);
        return i + 1;
    }
    public void swap(int[] a, int i, int j){
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值