常见的排序算法总结与实现-Java版

排序算法的时间复杂度与稳定性

https://gss1.bdstatic.com/9vo3dSag_xI4khGkpoWK1HF6hhy/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=38d2455bb13533fae1bb9b7cc9ba967a/9f2f070828381f30c82e41fca9014c086e06f0b4.jpg

稳定指相同值是否会被打乱。

1.关于稳定性:

不稳定:快选堆希(快速排序、选择排序、堆排序、希尔排序)

稳    定:插冒归计基(简单插入排序、冒泡排序、归并排序、计数排序、基数排序)

2.关于移动次数和关键字顺序无关的排序

顺口溜:一堆(堆排序)海龟(归并排序)选(选择排序)基(基数排序)友

 

 

冒泡排序(时间:O(n2), 空间:O(1))

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

https://i-blog.csdnimg.cn/blog_migrate/675ffdbbf0db66bde34aaca3df088552.png

时间复杂度:最好O(n),最坏O(n2)。

package Algorithm.sortex;

public class Maopao1 {
	public static void sort(int a[]){
		if(a.length<=1){
			return;
		}
		 for(int i =0;i<a.length-1;i++) { 
	            for(int j=0;j<a.length-i-1;j++) {//-1为了防止溢出
	                if(a[j]>a[j+1]) {
	                    int temp = a[j];	                     
	                    a[j]=a[j+1];	                     
	                    a[j+1]=temp;
	            }
	            }    
	        }
	}
	public static void main(String[] args) {
		int a[]={5,8,4,1,5};
		sort(a);
	}
}

 

 

选择排序(时间O(n2),空间O(1))

选择i位置后最小的元素与i位置交换。

找最小元素时,只要记录下标,避免了重复的值交换,比冒泡要快。

package Algorithm.sortex;

import java.util.Arrays;

public class StraightSelectSorting {
	public static void sort(int a[]){
		if(a.length<=1){
			return;
		}
		for(int i=0;i<a.length;i++){
			int minindex=i;//无序区的最小数据数组下标
			for(int j=i+1;j<a.length;j++){//在无序区中找到最小数据并保存其数组下标
				if(a[j]<a[minindex]){
					minindex=j;
				}
			}
			//将最小元素放到本次循环的前端
			int temp=a[i];
			a[i]=a[minindex];
			a[minindex]=temp;
		}
	}
	public static void main(String[] args){
		int a[]={5,8,4,1,5};
		sort(a);
		System.out.println(Arrays.toString(a));
	}
}

 

 

插入排序(时间O(n2),空间O(1))

直接插入排序

https://i-blog.csdnimg.cn/blog_migrate/732d1bc954547dfdf6cf9980421135b8.png

其过程如插扑克。

package Algorithm.sortex;

public class InsertSort {//插入排序
	public static void sort(int[] a){
		for(int i=1;i<a.length;i++){
			for(int j=i;j>0;j--){
				if(a[j]<a[j-1]){
					int temp=a[j];
					a[j]=a[j-1];
					a[j-1]=temp;
				}else{
					break;
				}
			}
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int a[]={3,1,4,2,7,8,6,5};
		sort(a);
	}

}

二分插入排序

链表插入排序

希尔排序

 

 

归并排序(时间:O(n log n) 空间:O(n))

 

速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列

思想可用于小和,逆序对。

package Algorithm.sortex;

public class MergeSort {//该思想可用于解小和问题,逆序对问题
	/-归并排序-/
	public static void gb(int a[]){
		if(a.length<2||a==null){
			return;
		}
		gb(a,0,a.length-1);//要注意边界
	}
	public static void gb(int a[], int L, int R) {
		if(R==L) return;//递归一定要判断返回,不然出错
		int mid = L + ((R -L) >> 1);//安全,不溢出
		gb(a, L, mid);
		gb(a, mid + 1, R);
		merge(a, L, R, mid);
	}
	public static void merge(int a[], int L, int R, int mid) {
		int help[] = new int[R - L + 1];//引入help数组
		int p1 = L;
		int p2 = mid + 1;
		int i = 0;
		while ((p1 <= mid) && (p2 <= R)) {
			help[i++] = a[p1] < a[p2] ? a[p1++] : a[p2++];
		}

		while (p2 <= R) {
			help[i++] = a[p2++];
		}

		while (p1 <= mid) {
			help[i++] = a[p1++];
		}

		for (int j = 0; j < help.length; j++) {
			a[L + j] = help[j];
		}
	}
//-小和问题-///
	public static int xh(int a[]){
		if(a.length<2||a==null){
			return 0;
		}
		return xh(a,0,a.length-1);//要注意边界
	}
	public static int xh(int a[], int L, int R) {
		if(R==L) return 0;
		int mid = L + ((R -L) >> 1);
		return xh(a, L, mid)+xh(a, mid + 1, R)+Xiaohe(a, L, R, mid);
	}
	public static int Xiaohe(int a[], int L, int R, int mid) {
		int help[] = new int[R - L + 1];
		int p1 = L;
		int p2 = mid + 1;
		int i = 0;
		int res=0;
		while ((p1 <= mid) && (p2 <= R)) {
			res+=a[p1]<a[p2]?a[p1]*(R-p2+1):0;
			help[i++] = a[p1] > a[p2] ? a[p1++] : a[p2++];
		}

		while (p2 <= R) {
			help[i++] = a[p2++];
		}

		while (p1 <= mid) {
			help[i++] = a[p1++];
		}

		for (int j = 0; j < help.length; j++) {
			a[L + j] = help[j];
		}
		return res;
	}

	
	public static void main(String[] args) {
		int res=0;
		int a[]=new int[]{4,2,7,3,1};
		System.out.println(xh(a));
		gb(a);// TODO Auto-generated method stub
		for (int i = 0; i < a.length; i++) {
			System.out.print(a[i] + " ");
		}

	}

}

 

 

快速排序(时间:O(nlogn),最好为O(nlogn),最差为O(logn2),空间:O(logN)?)

https://gss0.bdstatic.com/94o3dSag_xI4khGkpoWK1HF6hhy/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=157d647823a446236ac7ad30f94b196b/574e9258d109b3dee4ddfa6acfbf6c81800a4c55.jpg

选取最后一个,第一个,或者随机一个数作为分割点。

随机选取数时,可以和最后一个数交换。

可用递归实现。

空间复杂度:

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

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

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

package Algorithm.sortex;

public class Quiksort {

	public static void sort(int a[]){
		if(a.length<2||a==null){
			return;
		}
		sort(a, 0, a.length-1);
	}
	public static void sort(int a[],int L,int R){
		if(L>=R) return;
		int rI=(int)(Math.random()*(R-L+1));//随机选取一个数
		swap(a,L+rI,R);
		//System.out.println(rI);
		int p[]=partition(a,L,R);
		sort(a,L,p[0]);
		sort(a,p[1],R);
	}
	public static int[] partition(int a[], int L, int R) {
		int less = L - 1;//表示所有小于比较位a[r]的最后一位
		int more = R;//表示所有大于比较位a[r]的最前一位
		int index = L;//表示需要比较的位置
		int p[]=new int[2];
		while (index < more) {
			if (a[index] == a[R]) {//a[R]为用于比较的数
				index++;//若等于,则不动
			}
			else if (a[index] < a[R]) {//若小于,则当前数与最后的小于数的后一位(该数等于or就是当前数)交换
				swap(a, ++less, index++);
			}
			else if (a[index] > a[R]) {//若大于,当前数与more前一个数交换,当前比较数下表不变(换回来的是没判断过的数)
				swap(a, --more, index);
			}
		}
		swap(a,more,R);
		p[0]=less;
		p[1]=more+1;//把R排出去
		return p;
	}

	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) {
		int a[]=new int[]{2,5,3,6,1,4,6,9,2,3};
		//partition(a,0,9,4);// TODO Auto-generated method stub
		sort(a);
		for(int i=0;i<a.length;i++){
			System.out.print(a[i]);
		}

	}

}

 

 

堆排序

https://www.cnblogs.com/chengxiao/p/6129630.html

  1. 把数组变为大根堆(任何一棵子树最大值都为子树根)

 

转换为大根堆:

 

 

2.堆顶和最后一个位置做交换

3.前n个数做heapify

4.逐步排序(重复1,2,3)

 

Heapify:结点值变小了,找到孩子中最大的与之交换

HeapInsert:新节点加入到堆,同时往上调整的过程

package Algorithm.sortex;

public class Heapsort {

	public static void sort(int a[]){
		if((a==null)||(a.length<2)){
			return;
		}
		for(int i=0;i<a.length;i++){
		HeapInsert(a,i);//生成大根堆
		}
		for(int i=a.length-1;i>0;i--){
			swap(a,i,0);//最后叶节点与堆顶交换
			Heapify(a,0,i);//数组大小为i
		}
	}
	public static void HeapInsert(int a[], int index) {//新节点加入到堆,同时往上调整的过程
		while ((a[index] > a[(index - 1) / 2]) && (index > 0)) {//父节点=(子节点-1)/2
			swap(a, index, (index - 1) / 2);
			index = (index - 1) / 2;
		}
	}

	public static void Heapify(int a[], int index, int size) {//节点值变小了,找到孩子中最大的与之交换
		int left =  1;
		while (left < size - 1) {
			int temp = ((a[left] > a[left + 1]) || (left + 1 == size - 1)) ? left : left + 1;
			if (a[temp] <= a[index])
				break;
			swap(a, index, temp);
			index = temp;
			left = index * 2 + 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 print(int a[]) {
		for (int i = 0; i < a.length; i++) {
			System.out.println(a[i]);
		}
	}

	public static void main(String[] args) {
		int a[] = new int[]{7,4,5,2,6,10,8,3,1,9};// TODO Auto-generated method stub
		sort(a);
		print(a);

	}

}

 堆排复杂度计算

        堆排序的时间复杂度,主要在初始化堆过程和每次选取最大数后重新建堆的过程;

          初始化建堆过程时间:O(n)

        推算过程:

        首先要理解怎么计算这个堆化过程所消耗的时间,可以直接画图去理解;

        假设高度为k,则从倒数第二层右边的节点开始,这一层的节点都要执行子节点比较然后交换(如果顺序是对的就不用交换);倒数第三层呢,则会选择其子节点进行比较和交换,如果没交换就可以不用再执行下去了。如果交换了,那么又要选择一支子树进行比较和交换;

        那么总的时间计算为:s = 2^( i - 1 )  *  ( k - i );其中 i 表示第几层,2^( i - 1) 表示该层上有多少个元素,( k - i) 表示子树上要比较的次数,如果在最差的条件下,就是比较次数后还要交换;因为这个是常数,所以提出来后可以忽略;

        S = 2^(k-2) * 1 + 2^(k-3)*2.....+2*(k-2)+2^(0)*(k-1)  ===> 因为叶子层不用交换,所以i从 k-1 开始到 1;

        这个等式求解,我想高中已经会了:等式左右乘上2,然后和原来的等式相减,就变成了:

        S = 2^(k - 1) + 2^(k - 2) + 2^(k - 3) ..... + 2 - (k-1)

        除最后一项外,就是一个等比数列了,直接用求和公式:S = {  a1[ 1-  (q^n) ] }  / (1-q);

        S = 2^k -k -1;又因为k为完全二叉树的深度,所以 (2^k) <=  n < (2^k  -1 ),总之可以认为:k = logn (实际计算得到应该是 log(n+1) < k <= logn );

        综上所述得到:S = n - longn -1,所以时间复杂度为:O(n)

 

        更改堆元素后重建堆时间:O(nlogn)

        推算过程:

       1、循环  n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 logn,总时间:logn(n-1) = nlogn  - logn ;

    

       综上所述:堆排序的时间复杂度为:O(nlogn)

 

 

计数排序,基数排序,桶排序

计数排序

 

基数排序(O (nlog(r)m),其中r为所采取的基数,而m为堆数)

 

桶排序

基本思想

桶排序的基本思想是将一个数据表分割成许多buckets,然后每个bucket各自排序,或用不同的排序算法,或者递归的使用bucket sort算法。也是典型的divide-and-conquer分而治之的策略。它是一个分布式的排序,介于MSD基数排序和LSD基数排序之间。

基本流程

建立一堆buckets 
遍历原始数组,并将数据放入到各自的buckets当中; 
对非空的buckets进行排序; 
按照顺序遍历这些buckets并放回到原始数组中即可构成排序后的数组。

桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。当然桶排序的空间复杂度为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值