Acwing快速排序和归并排序-排序算法 java讲解

排序算法

插入:插入、希尔
交换:冒泡、快速
选择:简单选择,堆
归并:归并
排序算法图


一、快速排序

题目描述

给定你一个长度为 n 的整数数列。

请你使用快速排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式
输入共两行,第一行包含整数 n。

第二行包含 n 个整数(所有整数均在 1∼109 范围内),表示整个数列。

输出格式
输出共一行,包含 n 个整数,表示排好序的数列。

数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5

1.1、题解

1.快速排序——分治

分三步
1、确定分界点 随机确定的 q[l] q[(l+r)/2] q[r]
2、调整 (重点) 在<= x 和>= x 两部分中
3、 递归处理左右两段 (l,j) 与 (j+1,r)

-如果偏暴力做法---------------------------------------
1、弄两个小数组 a[] b []
2、q[l-r] 当q[i] <= x x-> a[];当q[i] >= x x-> b[];
3、a[] - > q[] b [] -> q[]

-正常做法(不需要开额外空间,就是开两个指针)---------------------------

代码如下(示例):

 import java.io.*;
import java.util.*;
public class Main{
    static int N = 100010 ;
    static int[] a = new int[N];
    
    public static void main(String[] args) throws IOException{
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        int  n = Integer.parseInt(input.readLine());
        String[] arr = input.readLine().split(" ");
        for(int i = 0 ; i < n ; i++){
            a[i] = Integer.parseInt(arr[i]);
        }
        quick(a , 0 , n-1 );
        for(int i = 0 ; i < n ; i++){
            System.out.print(a[i] + " ");
            
        }
    }
    public static void quick(int[] q , int l , int r ){
        if(l >= r ) return ;
        int x = q [(l+r) >> 1] , i = l - 1, j = r + 1 ;//两个指针会交换之后 进行移动  所以直接先移动  后判断交换
        while(i < j){//两指针在两侧 是因为后面指针调整  指针先加  所以这时候在左右两侧
            while (q[++i] < x);//相当于 do i++ ; while(q[i]<x);
            while(q[--j] >x);//相当于 do j-- ; while(q[j]<x);
            if(i<j){//交换二者的值
                int t = q[i];
                q[i] = q[j];
                q[j] =t ;
            }
        }
        quick(q,l,j);
        quick(q,j+1,r);
 
    }
    
}

第K个数

给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k 个数。

输入格式
第一行包含两个整数 n 和 k。

第二行包含 n 个整数(所有整数均在 1∼109 范围内),表示整数数列。

输出格式
输出一个整数,表示数列的第 k 小数。

数据范围
1≤n≤100000,
1≤k≤n

输入样例:
5 3
2 4 1 5 3
输出样例:
3

这个是上一个的稍微变形 可以传统方法先排序之后在取出来第k个数 数组中对应的是a[k-1]

 
import java.util.*;
public class Main{
    static int N = 100010;
    static int[] a = new int[N];
    public static void main(String[] args)  {
    Scanner scan = new Scanner(System.in);
    int n = scan.nextInt();
    int k = scan.nextInt();
    for(int i = 0 ; i < n ; i++){
        a[i] = scan.nextInt();
    }
        quick( a , 0 , n-1 );
        
        System.out.println(a[k-1]);
    }
   public static void quick(int[] q , int l , int r ){
        if(l >= r ) return ;
        int x = q [(l+r) >> 1] , i = l - 1, j = r + 1 ;//两个指针会交换之后 进行移动  所以直接先移动  后判断交换
        while(i < j){//两指针在两侧 是因为后面指针调整  指针先加  所以这时候在左右两侧
            while (q[++i] < x);//相当于 do i++ ; while(q[i]<x);
            while(q[--j] >x);//相当于 do j-- ; while(q[j]<x);
            if(i<j){//交换二者的值
                int t = q[i];
                q[i] = q[j];
                q[j] =t ;
            }
        }
        quick(q,l,j);
        quick(q,j+1,r);
 
    }
    
}

不过这样时间复杂度会高 因为是先排序之后再取 我们可以从修改快排的函数

在这里插入图片描述

 
import java.util.*;
public class Main{
    static int N = 100010;
    static int[] a = new int[N];
    public static void main(String[] args)  {
    Scanner scan = new Scanner(System.in);
    int n = scan.nextInt();
    int k = scan.nextInt();
    for(int i = 0 ; i < n ; i++){
        a[i] = scan.nextInt();
    }
      int p =   quick( a , 0 , n-1 , k);
        
        System.out.println( p);
    }
   public static int quick(int[] q , int l , int r ,int k ){
        if(l >= r ) return q[l];
        int x = q [(l+r) >> 1] , i = l - 1, j = r + 1 ;//两个指针会交换之后 进行移动  所以直接先移动  后判断交换
        while(i < j){//两指针在两侧 是因为后面指针调整  指针先加  所以这时候在左右两侧
            while (q[++i] < x);//相当于 do i++ ; while(q[i]<x);
            while(q[--j] >x);//相当于 do j-- ; while(q[j]<x);
            if(i<j){//交换二者的值
                int t = q[i];
                q[i] = q[j];
                q[j] =t ;
            }
        }
        int sl = j - l + 1 ;//左边的个数
        if( k <= sl ) return  quick(q,l,j,k);
       else return   quick(q,j+1,r,k-sl);
 
    }
    
}

二、归并排序

2.1 原题

给定你一个长度为 n 的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式
输入共两行,第一行包含整数 n。

第二行包含 n 个整数(所有整数均在 1∼109 范围内),表示整个数列。

输出格式
输出共一行,包含 n 个整数,表示排好序的数列。

数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5

2.2 题解

归并排序分为三部分
1、 确定分界点 一般都是中心点
2、 递归 left right 和归并数组的指针 三个指针
3、合并为一类 合二为一 这个是重点
时间复杂度分析:
归并排序的时间复杂度为O(nlogn)
代码如下(示例):

 import java.io.*;

class Main{
    static int N=100010;
    static int[] a=new int[N];
    static void merge_sort(int[] nums,int l,int r){
        if(l >= r) return;//如果超出界限
        int mid=l+r>>1;//找到分界点
        merge_sort(nums,l,mid);//开始递归
        merge_sort(nums,mid+1,r);
        int[] temp=new int[r-l+1];//合并的那个数组
        int k=0;//合并数组的那个指针
        int i=l;//左数组的i
        int j=mid+1;//右数组的mid+1
        while(i<=mid&&j<=r){
            if(nums[i]<=nums[j]) temp[k++] = nums[i++];//合并数组中存放的是最小的
            else  temp[k++] = nums[j++];
        }

        while(i<=mid)  temp[k++] = nums[i++];//左侧小集合还有剩余,依次放入大集合尾部
        while(j<=r)  temp[k++] = nums[j++];//右侧小集合还有剩余,依次放入大集合尾部

        for(i= l, j = 0 ; i <= r ; i++ , j++){//合并操作 i 从left起
            num[i] = temp[j];
        }
    }

    public static void main(String[]args)throws IOException{
        BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
        int n=Integer.parseInt(in.readLine());

        String[]arr=in.readLine().split(" ");
        for(int i=0;i<n;i++) a[i]=Integer.parseInt(arr[i]);

        merge_sort(a,0,n-1);
        for(int i=0;i<n;i++) System.out.print(a[i]+" ");
    }
}

 

逆序对数量(acwing 788)

原题链接

给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。

输入格式
第一行包含整数 n,表示数列的长度。

第二行包含 n 个整数,表示整个数列。

输出格式
输出一个整数,表示逆序对的个数。

数据范围
1≤n≤100000 ,
数列中的元素的取值范围 [1,109]。

输入样例:
6
2 3 4 5 6 1
输出样例:
5

题解

在这里插入图片描述

本题是归并排序的变型
逆序对可以分成三种情况,所求的逆序对数量为三种情况之和,也就是上面Sm的总和:

  • 1、一个是都在左边 此时逆序对数量

  • 2、一个是都在右边,此时逆序对数量
    在这里插入图片描述

  • 3、一个在左边,一个在右边,在左边的数只要大于他就构成逆序对,我们只能要统计出这些数,
    1、2的情况 归并排序都是排好的 a[i] > a[j] 这样构成逆序对 而a[i]后面的也大于a[j] 是因为两个区间排序号的 从小到大排序 这样的逆序对个数的话 就是mid-i + 1

if (arr[i] <= arr[j])
    tmp[k++] = arr[i++];
else
    tmp[k++] = arr[j++];

else中的语句即代表左边数列中的数大于右边中的数了,我们可以在此时将
mid-i+1加到总答案中去。
那么3的问题就解决了。
考虑最多的情况的话 5 * 10^9 会爆Int 所以这个结果数要用res来

在这里插入图片描述

if (a[i] <= a[j])
    tem[k++] = a[i++];
else {
    res += mid - i + 1;
    tem[k++] = a[j++];
}

代码如下

import java.util.Scanner;
public class Main{
    static int N = 100010 ;
    static int [] a = new int[N];
    static long res = 0;
    public static void main(String[] args){
        Scanner san = new Scanner(System.in);
        int n = san.nextInt();
        for(int i = 0 ; i< n ;i ++){
            a[i] = san.nextInt();
        }
        //总的逆序对的数量 如果有n个数 最大为 1-  n-1 个  大约n^2 / 2 int会溢出 
        System.out.println( merge_sort(a,0,n-1));
    }
    static long merge_sort(int[] a , int l , int r){
        if(l >= r ) return 0 ;
        int mid = l + r >> 1;
        res = merge_sort(a , l , mid)+ merge_sort(a, mid +1 ,r); //两个区间都要加上
        int k = 0 , i = l , j =mid+1 ; 
        int [] temp = new int [r-l+1];
        while(i <= mid && j <= r){
            if(a[i] <= a [j]) temp[k++] = a[i++];
            else {
                res += mid - i +1 ;//每次都累加 这相当于求S1 ... Sm的和
                temp[k++] = a[j++] ; 
            }
        }
        while( i <= mid) temp[k++] = a [i++];
        while(j <= r ) temp[k++] = a[j++];
        for(i = l , j = 0 ; i <= r ; i++,j++){
            a[i]=temp[j];
        }
        return res;
    }
}

总结

主要讲了快速排序和归并排序 归并排序是稳定的 而快速排序不稳定

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

依嘫_吃代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值