排序算法总结

先放置一张各大排序的时空复杂度表的情况

在这里插入图片描述

归并排序
算法分析

对于一个数组,先将其进行两两分割成子数组,直到分割成大小为1的子数组,然后对其两两进行有序合并,最终得到的就是一个有序数组了

复杂度分析

时间复杂度:O(n*logn)
空间复杂度:O(n) (使用了一个临时数组存放排序数据)

在这里插入图片描述


package demo.lyq;

import java.util.Scanner;

/**
 * 归并排序
 */
public class MergeSort {

    //将数组分割后两两进行有序合并,递归进行分段合并
    public static void mergeSort(int a[], int start, int end) {

        int len=end-start+1;
        if(start>=end)//分割到大小为1就退出
        {
            return;
        }
        int middle=start+(end-start)/2;
        mergeSort(a,start,middle);
        mergeSort(a,middle+1,end);
        merge(a,start,middle,end);//合并上面分割的两个有序数组
    }

    /**
     * 分段合并有序数组(将数组从中间拆分成两份进行有序合并)
     */
    public static void merge(int a[], int start, int middle,int end) {
        int left = start;//左边数组的开始索引
        int right = middle + 1;//右边数组的开始索引
        int[] temp = new int[end - start + 1];//用于存放临时合并的有序数据的数组
        int i = 0;
        //有序合并子数组
        while (left <= middle && right <= end) {
            if (a[left] < a[right]) {
                temp[i++] = a[left++];
            } else {
                temp[i++] = a[right++];
            }

        }
        //左边数组还有剩下的元素,直接放进临时数组中即可
        while(left<=middle)
        {
            temp[i++]=a[left++];
        }
        //右边剩下
        while(right<=end)
        {
            temp[i++]=a[right++];
        }
        //将临时数组的值放回原数组中
        for (int j = 0; j <temp.length; j++) {
            a[j+start]=temp[j];
        }



    }

    public static void main(String[] args)
    {


        //int a[]={23,12,34,5,3,345,23,143,45,10};
        Scanner in = new Scanner(System.in);
        //输入数组数据,以空格作为分割符
        String input_string=in.nextLine();
        String[] array=input_string.split(" ");//以空格分割
        int[] a=new int[array.length];
        for(int i=0;i<array.length;i++)
        {
            a[i]=Integer.valueOf(array[i]);
        }
        mergeSort(a,0,a.length-1);
        for (int j : a) {
            System.out.print(j+" ");
        }
        System.out.println();
    }

}

快排
算法分析

对于一个数组,指定一个数组元素为基准数,
进行一趟快排:
将数组中大于基准数的元素全部放到基准数的右边,将所有小于基准数的元素全部放置到基准数左边,这样之后,就将原来的数据以选定的基准数为界,分成了两部分

接下来,对于这两部分,重复上面的操作,每选择一个基准数进行一次快排,即最后所有的数据都有序了

在这里插入图片描述
后面依次递归排序即可

复杂度分析

时间复杂度:
由于快速排序对于不同情况的数据,时间复杂度不同,所以快排是不稳定的排序
1.时间复杂度 O(n2)的情况
比如:数据分布不均匀,对于选择的基准数,左边几乎全是大于它的数,右边几乎全是小于它的数据,则基本每两个数据都要交换一次,那么就近似于冒泡排序的时间复杂度了
解决方法,每次在快排范围内随机选择一个元素作为基准数,增大基准数左右的数据随机分布性
2.时间复杂度O(nlogn) 这是一般情况
但O(n2)的情况出现概率较低,
快排的平均时间复杂度为:O(n
logn)

空间复杂度
 快速排序使用的空间是O(1)的,也就是个常数级;而真正消耗空间的就是递归调用了,因为每次递归就要保持一些数据:

最优的情况下空间复杂度为:O(log2n);每一次都平分数组的情况

最差的情况下空间复杂度为:O( n );退化为冒泡排序的情况

package demo.lyq;
import java.util.*;
public class QuickSort {


    /**
     * 多趟快排实现排序
     * @param a
     * @param left
     * @param right
     */
    public static void quickSort(int a[],int left,int right)
    {
        if(left>=right)
        {
            return;
        }
        int middile=parttion(a,left,right);//找到基准数放置的位置
        quickSort(a,left,middile-1);
        quickSort(a,middile+1,right);
    }



    /**
     *对于基准数据进行一趟快排,并返回基准数最后在数组中位置,得到后面的快排的分界位置
     * @param a 进行排序的数组
     * @param left 左边界
     * @param right 有边界
     * @return
     */
    public static int parttion(int a[],int left,int right)
    {
        //通过随机交换当前范围内的数组首个元素,避免快排数据分布不均匀的问题
        swap(a,left,(int)(Math.random()*(right-left+1))+left);
        int v=a[left];//此元素作为基准数

        while(left<right)
        {
            //从右边开始找到一个小于基准数的数,将其与基准数位置交换位置
            while(left<right && v<=a[right])
            {
                right--;
            }
            if(left>=right)//保证左边界小于右边界才交换位置
            {
                break;
            }
            a[left] = a[right];
            //在左边找到一个大于基准数的元素,放置到前面空出来的右边去
            while (left<right&& v>a[left]) {
                left++;

            }
            if(left>=right)
            {
                break;
            }
            a[right]=a[left];

        }
        //上面循环进行完毕之后,相当于基准数左边都小于基准数,右边都大于基准数了
        //最后将基准数放入中间的位置即可
        a[left]=v;
        return left;

    }

    /**
     * 交换数组中两个元素的位置
     * @param a
     * @param i
     * @param j
     */
    public static void swap(int a[], int i, int j)
    {
        int temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String input=in.nextLine();//输入测试数据,以空格隔开两个数字
        String[] input_array=input.split(" ");
        int[] a=new int[input_array.length];
        for (int i = 0; i <input_array.length ; i++) {
            a[i]=Integer.valueOf(input_array[i]);
        }
        quickSort(a,0,a.length-1);
        System.out.println("排序之后的数据为:");
        for (int i : a) {
            System.out.print(i+" ");
        }
    }
}

堆排序
算法分析

算法描述(以最大堆为例)

  • 将初始待排序关键字序列构建成最大堆,此堆为初始的无序区;
  • 选择最大的元素(堆顶元素)放到无序序列最后面,此时得到新的无序区和新的有序区,在新的无序区重新构造最大堆,每次在无序序列中选择最大的的元素放到无序序列最后面
  • 直到最后叶子结点,不再向下调整,堆排序完成

在这里插入图片描述

复杂度分析

时间复杂度:O(n*logn)
空间复杂度:O(1)

这里演示top-k的代码作为实例,展示堆排序的过程

  • (top-k仅仅是只维护了k次最大堆,正常排序维护n次 [ n是无序序列的初始长度 ] )


import java.util.*;


/**
 * top-k问题
 * 找出数组中第k大的数据
 */
public class topk
{
    public static void sort(int[] a,int k)
    {
        //从最大的非叶子结点索引开始,将它作为一个根节点开始维护一个最大堆
        for(int i=(a.length-1)/2;i>=0;i--)
        {
            heap(a,i,a.length-1);
        }

        //将当前最大堆的首元素(也就是数组中的a[0])交换到数组末尾去,保证最大值放在后面
        //交换之后从0到j-1位置,又不是最大堆了,重新维护一个最大堆,重复上面的操作k次,就可以得到
        //a[length-k]位置元素即是第k大的元素
        for(int j=a.length-1;j>=a.length-k;j--)
        {
            swap(a,0,j);
            heap(a,0,j-1);

        }
    }

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

    public static void heap(int[] a,int root,int length)
    {
        //j是当前根节点root的左子树节点的索引坐标
        for(int j=root*2+1;j<=length;j=root*2+1)
        {

            //找到当前根节点的左右子树节点中的最大值索引
            if(a[j]<a[j+1]&&j+1<=length)
            {
                j=j+1;
            }

            //若是当前根节点已经大于两个子树节点值了,即当前根节点以及它的子树已经是一个大顶堆了,直接退出即可
            if(a[root]>a[j])
            {
                break;
            }
            //否则,交换两个数据的位置,使得当前根节点为最大值
            swap(a,root,j);
            //交换之后,当前根节点的子树可能不是最大堆了(上一次的交换可能破坏了这种结构,即重新继续构建即可)
            root=j;
        }
    }


    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        //int a = in.nextInt();
        //System.out.println(a);
        System.out.println("请输入测试数据: ");
        String input=in.nextLine();
        System.out.println("请输入k的值: ");
        int k=in.nextInt();
        String[] inputs=input.split(" ");
        int[] a=new int[inputs.length];
        for(int i=0;i<inputs.length;i++)
        {
            a[i]=Integer.parseInt(inputs[i]);
        }


        sort(a,k);
        int res=a[a.length-k];
        System.out.println("当前数据中第k大的数据为:"+res);

    }


}




topK问题解法:

n个数据中找最大(最小)的m个数
比如1亿个数据中找10个数

构建一个大小为m的小顶堆(时间复杂度:O(m)),将前m个数据放入其中
对于n-m个元素遍历

  • 若是当前元素小于堆顶元素,则遍历下一个元素
  • 若是当前元素大于堆顶元素,将堆顶元素和当前元素进行交换,重新构建小顶堆~(调整堆时间复杂度-O(log m))
    直到遍历完所有元素。。。
    建堆时间复杂度应该是O(m),不是O(mlogm)。堆调整的时间复杂度是O(logm) 最终时间复杂度等于,1次建堆时间+n次堆调整时间=O(m+nlogm)=O(nlogm)
    总时间复杂度:nlog m;

注:这里有t一位博主topk问题的解析

附:找到了大神的几种解法的代码
~~未完待续~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值