测试代码:

public class Main {
	public static void main(String[] args) {
		//int[] a = {1};	//测试用例1
		//int[] a = {};		//测试用例2
		//int[] a = {1,2,3,4,5,6,7,8};	//测试用例3
		int[] a = {8,7,6,5,4,3,2,1};	//测试用例4
		//int[] a = {100,-2,556,89,0,4,-1,3};	//测试用例5
		
		//Main.bubbleSort(a); //测试完成
		//Main.seleSort(a); //测试完成
		//Main.insertSort(a); //测试完成
		//Main.binaryInsertSort(a); //测试完成
		//Main.xier(a);  //测试完成
	}
}

一、时间复杂度为O(n^2)的排序算法

871efceb1c7ecc6bcec7fad64d28da0b.png

**1. 冒泡排序**

wKioL1g9cdmzU4-dAA8zPNtYGh0092.png-wh_50


1)时间空间复杂度

   最好情况下:正序有序,则只需要比较n次。故,为O(n)  
    最坏情况下:  逆序有序,则需要比较(n-1)+(n-2)+……+1,故,为O(N*N)

2)稳定性

    稳定的。

3)代码实现

/**
 * 冒泡排序
 * @param a
 */
private static void bubbleSort(int a[]){
	int i,j,t;
	int len = a.length;
	for (i = 0; i < len; i++){
		for (j = 0; j < (len - i - 1); j++) {
			if (a[j] > a[j + 1]) {
				t = a[j];
				a[j] = a[j + 1];
				a[j + 1] = t;
			}
		}
	}
	for(i=0;i<len;i++)
		System.out.println(a[i]);
}

**2.选择排序**

wKiom1g9ccnB3UoJAA8X8AKQA30414.png-wh_50

1)时间空间复杂度

    时间复杂度为O(n*n)。

2)稳定性

    稳定的。

比如:2 # 2 # 2 # 1,当选择最小的1到最前面,第一个2换到最后,破坏了稳定性。

3)代码实现

/**
 * 选择排序
 * @param a
 */
private static void seleSort(int a[]){
	int i,j,t,k;
	int len = a.length;
	for(i=0;i<len-1;i++)
	{
	    j=i;
	    for(k=i+1;k<len;k++)
	    {
	        if(a[j]>a[k])
	        j=k;
	    }
	    if(j!=i){
	        t=a[j];
	        a[j]=a[i];
	        a[i]=t;
	    }
	}
	for(i=0;i<len;i++)
		System.out.println(a[i]);
}

-----------------------------------------------------------

**3.直接插入排序**

wKiom1g9ccLQo-cvABB4ga4pjjs264.png-wh_50

1)时间空间复杂度

    从空间来看,只需要一个记录的辅助空间;

    从时间来看:最好情况(正序有序,只需要比较n次,不需要移动为O(n));

                        最坏情况(逆序有序,每个都需要移动,为O(n*n))。

2)稳定性

    稳定的。(排序前后两个相同的数字前后顺序没有发生变化)

3)代码实现

/**
 * 直接插入排序
 * @param a
 */
private static void insertSort(int[] a){
	int i,j,t;
	int len = a.length;
	for(i=1;i<len;i++){
	    if(a[i]<a[i-1]){
	        t=a[i];
	        a[i]=a[i-1];
	        for(j=i-2;j>=0&&t<a[j];j--)
	            a[j+1]=a[j];
	        a[j+1]=t;
	    }
	}
	for(i=0;i<len;i++)
		System.out.println(a[i]);
}

--------------------------------------------------

**4.折半插入排序**

wKioL1nIYWbRwvImAAC89k0SGWk255.jpg

1)时间空间复杂度

    折半插入排序与直接插入排序所需额外空间相同,但是移动次数仅比直接插入排序少了关键字的移动次数,因此时间复杂度为O(n*n)。

2)稳定性

    稳定的。

3)特点

    由折半查找来实现,由此进行的排序称为折半插入排序。

4)代码实现

/**
 * 折半插入排序
 * @param a
 */
private static void binaryInsertSort(int[] a){
	int i,j,t;
	int low,high,m;
	int len = a.length;
	for(i=1;i<len;++i){
	    t=a[i];
	    low=0;
	    high=i-1;
	    //折半查找
	    while(low<=high)
	    {
	        m=(low+high)/2;
	        if(t<a[m])
	            high=m-1;
	        else
	            low=m+1;
	    }
	    //从指定位置(low或high的位置)后移
	    for(j=i-1;j>high;--j)
	        a[j+1]=a[j];
	    //将数据插入
	    a[high+1]=t;
	}
	for(i=0;i<len;i++)
		System.out.println(a[i]);
}

4d79526f1a6913c388dee8da85393bfe.png

**5快速排序.**

    一次快速排序过程

01.随机选择一个值作为划分值,然后与最后一个值交换。

wKioL1nmqILj9HM5AABSjeFHbsY114.png

wKiom1nmqzjwh1P0AAB-Fsm_nKs614.png

02.设置一个小于等于区间,初始长度为0,放在最开始

wKioL1nmqIOxM2k3AACtGmKLYqk986.png

03.从左往右遍历所有的值,如果大于划分值,则继续遍历

wKioL1nmqIPDlmu4AABsJiTSMi8524.png

04.如果遍历到小于等于划分值,则此值与小于等于区间后的第一个值交换,小于等于区间向后移动一个位置。

wKiom1nmqzmTJ6vaAABusoHb3Bk547.png

wKiom1nmqzmC8-h5AAB51XnEgfI338.png

wKioL1nmqITAn6FnAABfmclur2Y098.png

05.当遍历到最后一个的时候,划分值与小于等于区间后的第一个值交换,完成第一次遍历

wKioL1nmqITB7og5AACBw9x8UD0374.png

wKioL1nmq_PQLYpXAADLupY97pw723.png

06.第一次排序完成后,划分值之前的部分和之后的部分再进行快速排序。


1)时间空间复杂度

    最好的情况下:因为每次都将序列分为两个部分(一般二分都复杂度都和logN相关),故为 O(N*logN)  
     最坏的情况下:基本有序时,退化为冒泡排序,几乎要比较N*N次,故为O(N*N)

2)稳定性

    不稳定的。

比如:4 # 3 # 3 # 3 # 1,假如取第二个3作为划分值,则它左右两个三要么全部划分到它的左边,要么划分到它的右边,这样一来就破坏了稳定性。

3)特点

    它是由冒泡排序改进而来的。在待排序的n个记录中任取一个记录(通常取第一个记录),把该记录放入适当位置后,数据序列被此记录划分成两部分。所有关键字比该记录关键字小的记录放置在前一部分,所有比它大的记录放置在后一部分,并把该记录排在这两部分的中间(称为该记录归位),这个过程称作一趟快速排序。

4)代码实现

public class Main {
	public static void main(String[] args) {
		int[] a = {1};	//测试用例1
		//int[] a = {};		//测试用例2
		//int[] a = {1,2,3,4,5,6,7,8};	//测试用例3
		//int[] a = {8,7,6,5,4,3,2,1};	//测试用例4
		//int[] a = {100,-2,556,89,0,4,-1,3};	//测试用例5
		
		int p = 0,r = a.length - 1;
		Main.QuickSort(a, p, r);
		for(int i=0;i<=r;i++)
			System.out.println(a[i]);
	}
	
	/**
	 * 快速排序
	 * @param a
	 * @param p
	 * @param r
	 */
	private static void QuickSort(int[] a,int p,int r){
		int q;  
	    if(p<r)   
	    {  
	        q = Partition(a,p,r);  
	        QuickSort(a,p,q-1);  
	        QuickSort(a,q+1,r);  
	    }  
	}
	private static int Partition(int[] a,int p,int r){
		int x,i,j,temp;  
		  
	    x = a[r];
	    i = p-1;
	    for( j=p; j<=r-1; j++)  
	    {  
	        if(a[j]<=x)  
	         {  
	            i +=1;  
	            temp = a[i]; 
	            a[i] = a[j];  
	            a[j] = temp;  
	        }  
	    }  
	    temp = a[i+1]; 
	    a[i+1] = a[r];  
	    a[r] = temp;  
	    return i+1;
	}
}

**6.希尔排序**

wKioL1g9cbrTB3pmAAn9YdI8Anc811.png-wh_50

具体过程

01.假如初始步长设为3,前三个数字是不考虑的。

1a8bc4f7200a0fca91c1f530023d01ae.png

02.从1开始,往前跳三位与6比较,小于,进行交换;1再往前跳三位数组越界,停止交换,指针指向下一个数字8,进行调整。

51ac01f72e58a0cae9056bc91e7a29f7.png

42346755bde45576490ec719ab1069db.png

03.8往前跳三位为5,大于,停止交换过程,进行下一个数字的调整。下一个为7,7往前跳三位,大于3,停止交换过程,进行下一个数字的调整。

f2e9a656497d1e79d001da312cfdc994.png

04.指针现在指向2,向前跳三位,小于6,交换;2再向前跳三位,大于1,停止交换过程,进行下一个数字的调整。

728ecab660536f366f2854d5ec840262.png

27f546b302aa906af20990edceb0c05c.png

05.指针现在指向4,4向前跳三位小于8,交换;4再向前跳三位小于5,交换;4再向前跳三位,数组越界,停止交换。4往下没有数字了,所以步长为3的步骤结束。

47f49971deb45458954c3969f93ea55a.png

2b8cb6d5533e2fdcb150db646a9c5dfa.png

6d9c74dec5a4e8a85072a0897b5cae38.png

06.希尔排序最终结束的步长为1,也就是经典的插入排序。希尔排序的关键是步长的选择,步长决定希尔排序的性能。

e20b50a03aaf682a7759448c5606e678.png


1)时间空间复杂度

    由于所取"增量"的问题,最好的时间复杂度尚未得出。

    最坏的跟平均时间复杂度为:O(N*logN)

2)稳定性

    不稳定

比如:5 # 1 # 1 # 5,当步长为2,则第二个1向前跳两位,与5交换: 1 # 1 # 5 # 5,破环了稳定性。

3)特点

    希尔排序可以说是一种分组插入排序。

    希尔排序分析是一个复杂的问题,因为它的时间是所取“增量”序列的函数,这也涉及到一些数学方面尚未解决的难题。但是应该注意:应使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须等于1。

4)代码实现

/**
 * 希尔排序
 * @param a
 */
private static void xier(int a[]) {
	int size = a.length;
	int i, j, k, temp;
	for (i = size / 2; i > 0; i /= 2) {
		for (j = i; j < size; j++) {
			temp = a[j];
			for (k = j - i; k >= 0 && temp < a[k]; k -= i) {
				a[k + i] = a[k];
			}
			a[k + i] = temp;
		}
	}
	for(i=0;i<size;i++)
		System.out.println(a[i]);
}

-------------------------------------------------------------

**7.归并排序**

wKiom1g9dfHDT_9SAA3ETjrw6UU622.png-wh_50

16d986e6668ab9109c9552b7516e5d01.png

243c42f8feaf6f8c3b4faef2f831bea3.png

ff719a4bd6ada91c8f9f094f6ee46bf2.png

1)时间空间复杂度

    对长度为n的文件,需进行logN 趟二路归并,每趟归并的时间为O(n),故其时间复杂度无论是在最好情况下还是在最坏情况下均是O(nlgn)。

2)稳定性

    稳定

3)特点

    缺点是需要O(n)的额外空间,适合多链表排序。

4)代码实现

public class Main {
	public static void main(String[] args) {
		int[] a = {1};	//测试用例1
		//int[] a = {};		//测试用例2
		//int[] a = {1,2,3,4,5,6,7,8};	//测试用例3
		//int[] a = {8,7,6,5,4,3,2,1};	//测试用例4
		//int[] a = {100,-2,556,89,0,4,-1,3};	//测试用例5
		
		int low = 0,high = a.length - 1;
		Main.MergeSort(a, low, high);
		for(int i=0;i<a.length;i++)
			System.out.println(a[i]);
		
	}
	/**
	 * 归并排序
	 * @param a
	 * @param low
	 * @param high
	 */
	private static void MergeSort(int a[],int low,int high)  
	{      
	    int mid;  
	    if(low<high)
	    {
	        mid=(low+high)/2;
	        MergeSort(a,low,mid);
	        MergeSort(a,mid+1,high);
	        Merge(a,low,mid,high);
	    }
	    
	}
	private static void Merge(int a[],int low,int mid,int high) 
	{   
	    int i=low,j=mid+1,p=0;     
	    int[] newarr = new int[high-low+1];//用来暂存排序好的数据   
	    
	    while(i<=mid && j<=high){  //以下过程很类似两个有序字符串合并成一个有序字符串     
	        if(a[i] < a[j])       
	        newarr[p++] = a[i++];     
	        else     
	        newarr[p++] = a[j++];   
	    }
	    
	    while(i<=mid)    
	        newarr[p++] = a[i++]; 
	    while(j<=high)     
	        newarr[p++] = a[j++];
	    for(i=low,p=0;p<(high-low+1);i++,p++)  //将结果复制到原数组当中   
	        a[i] = newarr[p]; 
	}
}

-------------------------------------------------

**8.堆排序**

01.构建一个大小为n的大根堆

360effb84303f4697e01b0b327b7ad14.png

02.将堆顶元素与堆的最后一个数交换,然后将堆中最后一个数移出堆结构,放在数组的最后位置,作为数组的有序部分保存下来。

1a902faedf90ef028110ea01e0d46e82.png

56dab11d3592ca95b3f3a09f7d3b6b5a.png

03.然后将n-1大小的堆进行大根堆的调整,还是将堆顶位置元素与堆的最后位置元素交换,堆数量减一;将最大值放在数组最后也是,作为数组的有序部分。

ba9053b0cd5f63d1f970af5b7c015372.png

04.依次进行上述操作,当堆的大小减为1的时候整个数组就有序了。

ee2440f2e026c28749fd3c15ff4066a0.png


wKioL1g9cn2gt1UEAA-qGu9n3p0343.png-wh_50

1)时间空间复杂度

    O(N*logN)

2)稳定性

    稳定

3)特点

   利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或者最小)的记录。也就是说,以最小堆为例,根节点为最小元素,较大的节点偏向于分布在堆底附近。

4)代码实现

void HeapAdjust(int *a,int i,int size)  //调整堆 
{
    int lchild=2*i;       //左孩子节点序号 
    int rchild=2*i+1;     //右孩子节点序号 
    int max=i;            //临时变量 
    if(i<=size/2)          //如果i不是叶节点就不用进行调整 
    {
        if(lchild<=size&&a[lchild]>a[max])
        {
            max=lchild;
        }    
        if(rchild<=size&&a[rchild]>a[max])
        {
            max=rchild;
        }
        if(max!=i)
        {
            swap(a[i],a[max]);
            HeapAdjust(a,max,size);    //避免调整之后以max为父节点的子树不是堆 
        }
    }        
}

void BuildHeap(int *a,int size)    //建立堆 
{
    int i;
    for(i=size/2;i>=1;i--)    //非叶节点最大序号值为size/2 
    {
        HeapAdjust(a,i,size);    
    }    
} 

void HeapSort(int *a,int size)    //堆排序 
{
    int i;
    BuildHeap(a,size);
    for(i=size;i>=1;i--)
    {
        swap(a[1],a[i]);           //交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面 
       //将余下元素重新建立为大顶堆 
          HeapAdjust(a,1,i-1);      //重新调整堆顶节点成为大顶堆
    }
} 

int main(int argc, char *argv[])
{
    int a[100];
    int size;
    while(scanf("%d",&size)==1&&size>0)
    {
        int i;
        for(i=1;i<=size;i++)
            cin>>a[i];
        HeapSort(a,size);
        for(i=1;i<=size;i++)
            cout<<a[i]<<" ";
        cout<<endl;
    }
    return 0;
}


时间复杂度为O(N)的排序算法

    不是基于比较的算法,思想来自桶排序,经典的就是基数排序和计数排序。

**9.基数排序**

01.假设一串十进制数,有0-9号桶。

5dfac27a224e03a5f46efd15e88ed8ee.png

02.第一步根据个位数数值进桶。

2bfbef7ec170245f59a6f820712331cd.png

03.然后从0号桶到9号桶依次到处数据,形成一个次序。然后再根据十位上的数值依次进桶,然后倒出;最终根据最高位上数值进桶 倒出后形成的次序就是一个有序次序。

61ac985a40f759049633cfe805cebb03.png


**10.计数排序**

比如:对员工的身高进行排序,成年人的身高在一米到三米之间。

47ecf3093b09cbeebb0dc3af3fef246c.png

093bc9a2eab491117ae5d4dbe369164a.png

22590d8aef8fab71997c0f35179ff6d0.png

例:对公司所有员工按照年龄排序,可以有O(n)的辅助内存。

说明要排序的数在一个较小的范围内。

void SortAges(int ages[],int length){
    if(age==NULL||length<=0)
        return;
    const int oldestAge=99;
    int timesOfAge[oldestAge+1];
    for(int i=0;i<=oldestAge;++i)
        timesOfAge[i]=0;
    for(int i=0;i<length;++i){
        int age=ages[i];
        if(age<0||age>oldestAge)
            throw new std::exception("age out of range.");
        ++timesOfAge[age];
    }
    int index=0;
    for(int i=0;i<=oldestAge;++i){
        for(int j=0;j<timesOfAge[i];++j){
            age[index]=i;
            ++index;
        }
    }
}

各个排序算法使用的时间复杂度总结:

871efceb1c7ecc6bcec7fad64d28da0b.png

4d79526f1a6913c388dee8da85393bfe.png


时间复杂度为O(N)的排序算法

    不是基于比较的算法,思想来自桶排序,经典的就是基数排序和计数排序。


各个排序算法使用的空间复杂度总结:

b558237fb6bcb7f1e03ebb20ba9fe531.png


各个排序算法稳定性总结:

657d535dae73bba5327968a2d3044eb5.png


补充说明一:排序算法无绝对优劣

    通常不能随便说哪种排序算法好。这个和要排序的元素相关。例如对人的年龄排序或者身高排序,因为这种数据范围通常比较小,可以考虑采用计数排序。但是对于均匀分布的整数,计数排序就不合适了。一般情况下没有特殊说明的话,认为要排序的数据范围是均匀分布的。

补充说明二:为什么叫快速排序

    快速排序之所以叫快速排序,并不代表它比堆排序和归并排序优良。在最好的情况下,它的渐进复杂度与堆排序和归并排序是相同的。只是快速排序的常量系数比较小而已。

补充说明三:工程上的排序

    1、工程上的排序是综合排序。

    2、数组较小时,插入排序。

    3、数组较大时、快速排序或其他O(N*logN)的排序。

各种排序比较:http://blog.csdn.net/li563868273/article/details/51200876