先放置一张各大排序的时空复杂度表的情况
归并排序
算法分析
对于一个数组,先将其进行两两分割成子数组,直到分割成大小为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(nlogn)
空间复杂度
快速排序使用的空间是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;
附:找到了大神的几种解法的代码
~~未完待续~