由于即将面临找工作的现实,将八大基本的排序算法进行汇总,为以后的找工作做准备:
八大排序算法主要包括:
1、插入排序(insertSort)
2、希尔排序(shellSort)
3、冒泡排序(BubbleSort)
4、快速排序(quickSort)
5、选择排序(selectSort)
6、堆排序(heapSort)
7、归并排序(mergeSort)
8、基数排序(oddSort)
一、稳定性:
稳定:冒泡排序、插入排序、归并排序和基数排序
不稳定:选择排序、快速排序、希尔排序、堆排序
二、平均时间复杂度
O(n^2):直接插入排序,简单选择排序,冒泡排序。
在数据规模较小时(9W内),直接插入排序,简单选择排序差不多。当数据较大时,冒泡排序算法的时间代价最高。
性能为O(n^2)的算法基本上是相邻元素进行比较,基本上都是稳定的。
O(nlogn):快速排序,归并排序,希尔排序,堆排序。
其中,快排是最好的, 其次是归并和希尔,堆排序在数据量很大时效果明显。
三、排序算法的选择
1.数据规模较小
(1)待排序列基本序的情况下,可以选择直接插入排序;
(2)对稳定性不作要求宜用简单选择排序,对稳定性有要求宜用插入或冒泡
2.数据规模不是很大
(1)完全可以用内存空间,序列杂乱无序,对稳定性没有要求,快速排序,此时要付出log(N)的额外空间。
(2)序列本身可能有序,对稳定性有要求,空间允许下,宜用归并排序
3.数据规模很大
(1)对稳定性有求,则可考虑归并排序。
(2)对稳定性没要求,宜用堆排序
4.序列初始基本有序(正序),宜用直接插入,冒泡
public class SortAlgorithm {
public static void main(String[] args) {
int n[]={1,4,2,5,7,33,3,9,6,11,111};
int m=8;
//insertSort(n, m);
//shellSort(n, m);
//BubbleSort(n, m);
//quickSort(n, 0, 6);
//selectSort(n, m);
//heapSort(n, m);
//mergeSort(n, 0, 7);
oddSort(n);
for (int i : n) {
System.out.println(i);
}
}
/**
1、插入排序:
时间复杂度:
最好的情况下:正序有序(从小到大),这样只需要比较n次,不需要移动。因此时间复杂度为O(n)
最坏的情况下:逆序有序,这样每一个元素就需要比较n次,共有n个元素,因此实际复杂度为O(n2)
平均情况下:O(n2)
稳定性:稳定
*/
public static void insertSort(int[] n,int m){
for(int i=1;i<m;i++){
int j=i-1;
int temp=n[i];
while(j>=0){
if(n[j]>temp){
n[j+1]=n[j];
j--;
}else{
n[j+1]=temp;
break;
}
}
}
}
/**
* 希尔排序:
* 时间复杂度:
* 最好情况:由于希尔排序的好坏和步长d的选择有很多关系,因此,目前还没有得出最好的步长如何选择(现在有些比较好的选择了,但不确定是否是最好的)。所以,不知道最好的情况下的算法时间复杂度。
最坏情况下:O(N*logN),最坏的情况下和平均情况下差不多。
平均情况下:O(N*logN)
稳定性:不稳定
*/
public static void shellSort(int[] n,int m){
int d=m/2;//初始步长
int j=0;
while(d>0){
for(int i=d;i<m;i++){
int temp=n[i];
for( j=i;j>=d;j=j-d){
if(n[j-d]>temp){
n[j]=n[j-d];
}else{
break;
}
}
n[j]=temp;
}
d=d/2;
}
}
/**
* 冒泡排序:
* 最好情况下:正序有序,则只需要比较n次。故,为O(n)
最坏情况下: 逆序有序,则需要比较(n-1)+(n-2)+……+1,故,为O(N*N)
稳定性:稳定
* @return
*/
public static void BubbleSort(int[]n,int m){
int temp=0;
for(int i=0;i<m-1;i++){
for(int j=i+1;j<m;j++){
if(n[j]<n[i]){
temp=n[i];
n[i]=n[j];
n[j]=temp;
}
}
}
}
/**
* 快速排序(思想):以a[0]为初识值定义两个指针i,j分别指向数组的第一个和最后一个元素,先从右往左执行j--找到第一个比初始值小的数a[p],
* 交换位置,此时j=p;再从左往右执行i++找到第一个比初始值大的数a[q],交换位置,此时i=q;重复执行上述步骤直到i=j.
* 时间复杂度:
* 最好的情况下:因为每次都将序列分为两个部分(一般二分都复杂度都和logN相关),故为 O(N*logN)
最坏的情况下:基本有序时,退化为冒泡排序,几乎要比较N*N次,故为O(N*N)
稳定性:不稳定
*
*/
public static void quickSort(int[]n,int start,int end){
int flag=0;
if(start<end){
flag=partition(n, start,end);
quickSort(n, start, flag-1);
quickSort(n, flag+1, end);
}
}
public static int partition(int[]n,int start,int end){
int temp=n[start];
while(start<end){
while(start<end&&temp<=n[end]){
end--;
}
if(start<end){
n[start]=n[end];
start++;
}
while(start<end&&temp>=n[start]){
start++;
}
if(start<end){
n[end]=n[start];
end--;
}
}
//跳出循环时start=end
n[end]=temp;
return end;
}
/**
*选择排序(思想):首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,
* 直到所有元素均排序完毕。具体做法是:选择最小的元素与未排序部分的首部交换,使得序列的前面为有序。
*时间复杂度:最好情况下:交换0次,但是每次都要找到最小的元素,因此大约必须遍历N*N次,因此为O(N*N)。减少了交换次数!
最坏情况下,平均情况下:O(N*N)
稳定性:不稳定
*
*/
public static void selectSort(int[]n,int m){
int flag;
int temp;
for(int i=0;i<m-1;i++){//排序需要的趟数
flag=i;
for(int j=i+1;j<m;j++){
if(n[flag]>n[j]){
flag=j;//flag标记每趟排序中最小元素的位置坐标
}
if(flag!=i){
temp=n[i];
n[i]=n[flag];
n[flag]=temp;
break;
}
}
}
}
/**
* 堆排序:从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。
* 一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
* 时间复杂度:最坏情况下,接近于最差情况下:O(N*logN),因此它是一种效果不错的排序算法。
* 稳定性:不稳定
* 堆排序的最坏时间复杂度为O(nlogn)。堆序的平均性能较接近于最坏性能。由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
*/
//以大顶堆为例
public static void heapSort(int[]n,int m){
//循环建堆
for(int i=0;i<m-1;i++){
buildMaxHeap(n, m-i-1);
//交换堆顶和最后一个元素
swap(n, 0, m-i-1);
System.out.println(Arrays.toString(n));
}
}
//对数组n中的元素从0到lastindex建立大顶堆
public static void buildMaxHeap(int[]n,int lastindex){
//从lastindex节点的父节点开始
for(int i=lastindex/2;i>=0;i--){
//k保存正在判断的节点
int k=i;
//当前节点的子节点存在
while(2*k+1<=lastindex){
//k节点的左子节点的索引
int maxindex=2*k+1;
//如果k节点的右子节点存在
if(maxindex<lastindex){
//左子节点的值小于右子节点的值
if(n[maxindex]<n[maxindex+1]){
//保证maxindex指向子节点中的最大值
maxindex++;
}
}
//如果k节点的值小于子节点的值
if(n[k]<n[maxindex]){
//交换
swap(n, k, maxindex);
//将maxindex的值赋予k,执行下一次循环,保证k节点的值大于其子节点的值
k=maxindex;
}else{
break;
}
}
}
}
//交换数组中的两个元素
public static void swap(int[]n,int i,int j){
int temp;
temp=n[i];
n[i]=n[j];
n[j]=temp;
}
/**
* 归并排序:基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,
* 即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
* 时间复杂度:最好的情况下:一趟归并需要n次,总共需要logN次,因此为O(N*logN)
最坏的情况下,接近于平均情况下,为O(N*logN)
说明:对长度为n的文件,需进行logN 趟二路归并,每趟归并的时间为O(n),故其时间复杂度无论是
在最好情况下还是在最坏情况下均是O(nlgn)。
稳定性:稳定
*/
public static void mergeSort(int[]n,int left,int right){
if(left<right){
int middle=(right+left)/2;
//对左边进行递归
mergeSort(n, left, middle);
//对右边进行递归
mergeSort(n, middle+1, right);
//合并
merge(n, left, middle, right);
}
}
public static void merge(int[]n,int left,int middle,int right){
int[] temp=new int[n.length];
int mid=middle+1;//右边的起始位置
int p=left;
int k=left;
while(left<=middle&&right>=mid){
//从两个数组中选取较小的数放在temp数组中
if(n[left]<=n[mid]){
temp[k++]=n[left++];
}else{
temp[k++]=n[mid++];
}
}
//将剩余部分放入temp数组中
while(left<=middle){
temp[k++]=n[left++];
}
while(mid<=right){
temp[k++]=n[mid++];
}
//将temp数组中的数据复制回原数组
while(p<=right){
n[p]=temp[p++];
}
}
/**
* 基数排序:它是一种非比较排序。它是根据位的高低进行排序的,也就是先按个位排序,然后依据十位排序……以此类推。
* 时间复杂度:分配需要O(n),收集为O(r),其中r为分配后链表的个数,以r=10为例,则有0~9这样10个链表来将原来
* 的序列分类。而d,也就是位数(如最大的数是1234,位数是4,则d=4),即"分配-收集"的趟数。因此时间复杂
* 度为O(d*(n+r))。
*稳定性:稳定
*/
public static void oddSort(int[]n){
//找到最大数,确定要排序的趟数
int max=0;
for(int i=0;i<n.length;i++){
if(max<n[i]){
max=n[i];
}
}
//判断最大数的位数
int times=0;
while(max>0){
max=max/10;
times++;
}
//建立十个队列
List<ArrayList> queue=new ArrayList();
for(int i=0;i<10;i++){
ArrayList queue1=new ArrayList();
queue.add(queue1);
}
//进行times次分配和收集
for(int i=0;i<times;i++){
//分配
for(int j=0;j<n.length;j++){
int a= n[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i);
ArrayList queue2=queue.get(a);
queue2.add(n[j]);
queue.set(a, queue2);
}
//收集
int count=0;
for(int j=0;j<10;j++){
while(queue.get(j).size()>0){
ArrayList<Integer> queue3=queue.get(j);
n[count]=queue3.get(0);
queue3.remove(0);
count++;
}
}
}
}
}