【排序算法】Java版十大经典排序算法实现以及测试结果

    排序算法是程序员必备技能之一,可能大家平时用到的排序算法种类比较少,完全忽视了运行时的时间空间效率问题,不过,作为一个进阶的程序员,了解各排序算法的优缺点以及实现思路很有必要,先列出常用的十大算法的概况:

    算法的优劣性取决于具体需要排序的数组的大小,混乱程度,数组内容,业务需求等等,我们不能离开应用场景去评判一个算法的优劣性,没有最好,只有最适合。

    以下是在Java平台下实现的算法的测试结果,所有数据都是随机生成的,所有的算法都是计算同一套数据进行测试,当然,这些结果说明不了问题,只是一个参考:

   1. 20个数据的结果(为了验证算法的正确性及数据的统一性)

    2. 1000个数据的结果(数据量太大,就不打印数组了, 下同)

    3. 10000个数据的结果

    4. 100000个数据的结果

    5.500000个数据的结果

    可以看到,同样的数据采用不同的算法所耗费的时间差距特别大,所以我们平时在使用的时候一定要选择合适的算法。

    再次说明,这个结果只是一个参考,并不能定义算法的优劣,算法耗费的时间不仅跟计算次数有关,还跟赋值等操作息息相关,跟算法实现方式也有一定的关系,我们要在最合适的场景采用最合适的算法。

    

java代码如下,只是提供一个思路,代码注释已经很清楚了,就不再解释了:

Main:算法的主流程

package com.jaden.sort;


public class Main {
	
	public static void main(String[] args) {
		int count = 500000;
		int[] src = createRandomAry(count); //生成随机数目的数组
		System.out.println("Array count = " + count );
		//printAry(src);
		System.out.println("-------------------------------------------------");
		for(int i=Sort.BUBBLE_SORT; i<=Sort.RADIX_SORT; i++) {
			int[] tmp = copyAry(src); //每次拷贝,便于比较不同算法计算统一数据的时间
			Sort sort = SortFactory.createSort(i); //工厂类创建具体算法
			long begin = System.currentTimeMillis(); //开始时间
			sort.sort(tmp); //计算
			long after = System.currentTimeMillis(); //结束时间
			//printAry(tmp);
			formatPrint(sort, after-begin); //打印算法名称及耗费时间
		}
	}
	
	/*
	 * 创建乱序数组
	 */
	private static int[] createRandomAry(int count) {
		int []ary = new int[count];
		for(int i=0; i<count; i++) {
			ary[i] = (int)(Math.random() * count * 100);
		}
		return ary;
	}
	
	/*
	 * 打印数组
	 */
	private static void printAry(int []ary) {
		for(int i : ary) {
			System.out.print(i);
			System.out.print(" ");
		}
		System.out.println("");
	}
	
	private static String  getClassName(Class classex) {
		return classex.getName();
	}
	
	private static void formatPrint(Sort sort, long spend) {
		String name = getClassName(sort.getClass());
		System.out.println(name + "  |  spend = " + spend + "ms");
	}
	
	
	/*
	 * 复制数组,确保各种排序用到的数组是同一个
	 */
	private static int[] copyAry(int[] src) {
		int size = src.length;
		int[] dest = new int[size];
		for(int i=0; i<size; i++) {
			dest[i] = src[i];
		}
		return dest;
	}
	
}

SortFactory:

package com.jaden.sort;

/*
 * 工厂类,用来创建各种排序算法
 * 
 */
public class SortFactory {
	public static Sort createSort(int type) {
		switch (type) {
		case Sort.BUBBLE_SORT:
			return new BubbleSort();
		case Sort.SELECTION_SORT:
			return new SelectionSort();
		case Sort.INSERTION_SORT:
			return new InsertionSort();
		case Sort.SHELL_SORT:
			return new ShellSort();
		case Sort.MERGE_SORT:
			return new MergeSort();
		case Sort.QUICK_SORT:
			return new QuickSort();
		case Sort.HEAP_SORT:
			return new HeapSort();
		case Sort.COUNTING_SORT:
			return new CountingSort();
		case Sort.BUCKET_SORT:
			return new BucketSort();
		case Sort.RADIX_SORT:
			return new RadixSort();
		default:
			return new BubbleSort();
		}
	}
}

下面是各种算法:

1. 冒泡排序:

package com.jaden.sort;

/*
 * 冒泡排序
 * 思想:每次比较相邻的两个元素,如果相邻元素顺序错误,那么则将其交换,第一次排序完成,最大的值位于数组的最后
 * 时间复杂度:O(n²)
 * 空间复杂度:O(1)
 */
public class BubbleSort implements Sort {

	@Override
	public int[] sort(int[] src) {
		int size = src.length;
		int tmp;
		for(int i=0; i<size-1; i++) {
			for(int j=0; j<size-i-1; j++) {
				//交换值,将最大值放到后面
				if(src[j] > src[j+1]) {
					tmp = src[j];
					src[j] = src[j+1];
					src[j+1] = tmp;
				}
			}
		}
		return src;
	}

}

2. 选择排序: 

package com.jaden.sort;


/*
 * 选择排序
 * 思想:从index=0遍历数组,选择最小的元素,与第一个元素(index=0)交换,那么第一个元素即排好序的,
 *     然后从index=1遍历数组,再选择剩余数组最小元素,与第二个元素(index=1)交换,那么前两个也排好序了。
 *     然后从index=2开始。。。。
 *     不停遍历,最终能够得到一个排好序的数组
 * 时间复杂度:O(n²)
 * 空间复杂度:O(1)
 */
public class SelectionSort implements Sort {

	@Override
	public int[] sort(int[] src) {
		int size = src.length;
		int tmp, min;
		for(int i=0; i<size-1; i++) {
			min = i;
			for(int j=i+1; j<size; j++) {
				if(src[min] > src[j]) {
					min = j;
				}
			}
			//最小值,跟i交换
			tmp = src[i];
			src[i] = src[min];
			src[min] = tmp;
		}
		return src;
	}

}

3. 插入排序:

package com.jaden.sort;

/*
 * 插入排序
 * 思想:原数组第一个元素可以认为是已经排好序的新数组,第二个元素与新数组的元素(第一个元素)进行比较,如果比第一个元素小,那么放到该元素前面,如果比第一个元素大,那么不用改变位置,加入到新数组后面。新数组是从小到大排序好的数组,元素个数为2
 *     然后第三个元素同新数组的元素分别比较,假如第三个元素是最小的,插入到最前,假如第三个元素是第二小的,插入到新数组的中间,假如最大,那么直接放到新数组的后面,新数组元素个数为3
 *     依次遍历整个老数组,将值插入到合适的位置,遍历完成,数组就排序完成。。
 *     虽然用新的数组理解起来方便,但是实际上是没有新的数组的,是用索引值去区分的,index以前,是有序,后面是无序
 *     插入排序涉及到数组的整体后移,比如说,前面5个是有序,但是第6个值大小正好比第3个大,比第4个小,那么将第6个插入到第4个的时候,原先的4要到第5,原先的5要到第6位,注意处理这个逻辑就好
 * 时间复杂度:O(n²)
 * 空间复杂度:O(1)
 */
public class InsertionSort implements Sort {

	@Override
	public int[] sort(int[] src) {
		int size = src.length;
		int tmp;
		int p; //指针插入位置
		for(int i=0; i<size-1; i++) {
			tmp = src[i+1];
			p = i;
			while (p >=0 && tmp < src[p]) {
				src[p+1] = src[p];
				p--;
			}
			src[p+1] = tmp;
			
		}
		return src;
	}

}

4. Shell排序

package com.jaden.sort;

/*
 * 希尔排序
 * 思想:按照某个步长对元素进行排序,假如步长为4,那么index=0,index=4,index=8.。。。的元素进行比较排序,index=1,index=5,index=9.。。。的元素进行比较排序,
 *     index=2,index=6,index=10.。。。的元素进行比较排序,index=3,index=7,index=11.。。。的元素进行比较排序
 *     第一次排序完成,减小步长,同理按照上面的规则进行排序
 *     确保最后一次的步长为1
 *     
 *     假如只进行一次一个步长为1的排序,那么就是插入排序了
 * 时间复杂度:O(n^(1.3—2))
 * 空间复杂度:O(1)
 */
public class ShellSort implements Sort {

	@Override
	public int[] sort(int[] src) {
		int size = src.length;
		int gap = 1;
		int interval = 3; //动态设置间隔
		int tmp, p;
		while (gap < size / interval) {
			gap = gap * interval + 1;
		}
		for(; gap>0; gap=gap/interval) {
			
			for(int i=gap; i<size; i++) {
				tmp = src[i];
				p = i - gap;
				while (p >= 0 && tmp < src[p] ) {
					src[p+gap] = src[p];
					p -= gap;
				}
				src[p+gap] = tmp;
			}
		}
		return src;
	}

}

 5. 归并排序

package com.jaden.sort;

/*
 * 归并排序
 * 思想:将数组由中间拆分成2个数组,然后再分别将2个数组拆分为4个数组,依次递归,那么最后会将原数组拆分为一个个元素
 *     1个元素的数组可以看成是有序数组,然后分别将两个相邻有序数组按顺序合并,合并完的数组仍然是有序的,然后依次递归,每次都可以看成是两个有序数组的合并
 *     有序数组的合并,所用的时间复复杂度是O(n)
 *     递归到最终和并完成,得到的新的数组就是有序的了
 *     归并排序,可以看成是分割和合并的递归,注意处理好各边界逻辑
 * 时间复杂度:O(nlgn)
 * 空间复杂度:O(n)
 */
public class MergeSort implements Sort {

	@Override
	public int[] sort(int[] src) {
		int size = src.length;
		if(size < 2) {
			return src;
		}
		slice(src, 0, size-1);
		
		return src;
	}
	
	/*
	 * 分割成最小单位
	 */
	private void slice(int[] src, int start, int end) {
		int middle = (end + start) / 2;
		if(end - start  > 0) {
			slice(src, start, middle);
			slice(src, middle+1, end);
			merge(src, start, middle, end);
		}
		
	}
	
	/*
	 * 合并有序数组
	 */
	private void merge(int[] src, int start, int middle, int end) {
		int[] tmp = new int[end - start + 1];
		int p1 = start;  //指向第一段顺序数组的低位
		int p2 = middle + 1; //指向第二段顺序数组的低位
		for(int i=0; i<end-start+1; i++) {
			if(p1 <= middle && p2 <= end) {
				if(src[p1] < src[p2]) {
					tmp[i] = src[p1];
					p1++;
				}else {
					tmp[i] = src[p2];
					p2++;
				}
			}else if(p1 > middle) { //符合此条件,代表第一段数组全部合并完,直接将剩余的第二段数组合并
				tmp[i] = src[p2];
				p2++;
			}else if(p2 > end) { //符合此条件,代表第二段数组全部合并完,直接将剩余的第一段数组合并
				tmp[i] = src[p1];
				p1++;
			}
		}
		//将临时数组的值存回到src中
		for(int i=start; i<end+1; i++) {
			src[i] = tmp[i-start];
		}
	}

}

6. 快速排序

package com.jaden.sort;

/*
 * 快速排序
 * 思想:取第一个元素做为基准值,把比他大的放到右边,把比他小的放到左边,第一次循环完成,那么左边都是比他小的,右边都是比他大的,那么,这个值在最终排序完成的数组中的位置将不会改变
 *     那么新的数据可以分成三个部分:所有比他小的左边数组(未排序)上一个基准值,所有比他大的右边数组(未排序),用递归的思想,左边的数组可以按上面的算法来排序基准值,右边的数组同理也可以得到基准值
 *     这一次循环完毕,那么就得到了:未排序-基准左边-未排序-基准1-未排序-基准右边-未排序。然后再次递归,直至把所有的基准值的位置选对,那么整个数组就排序好了。。
 *     注意各个边界值的处理
 * 时间复杂度:O(nlgn)
 * 空间复杂度:O(nlgn)
 */
public class QuickSort implements Sort {

	@Override
	public int[] sort(int[] src) {
		int size = src.length;
		if(size < 2) {
			return src;
		}
		int left = 0;
		int right = size - 1;
		quickSort(src, left, right);
		return src;
	}
	
	private void quickSort(int[] src, int left, int right) {
		if(right - left > 0) {
			int p = left + 1;
			int level = src[left];
			for(int i=left+1; i<=right; i++) {
				if(src[i] < level) {
					swip(src, p, i);
					p++;
				}
			}
			swip(src, p-1, left);
			//左边递归
			quickSort(src, left, p-2);
			//右边递归
			quickSort(src, p, right);
		}
	}
	
	private void swip(int[] src, int x, int y) {
		int tmp = src[x];
		src[x] = src[y];
		src[y] = tmp;
	}

}

7. 堆排序:

package com.jaden.sort;

/*
 * 堆排序
 * 思想:堆,是一个完全二叉树结构。与数组不同,假设下标是从1开始,那么最后一个根节点的索引是个数index = n/2,它的左右叶子是index*2和index*2+1(这个是完全二叉树的特性)
 * 	           排序的原理是,从最后一个根节点开始,比较该根节点与它的左右节点值的大小,如果谁大,那么将该节点值交换为最大的值。
 *     从最后一个根节点往第一个节点依次遍历,第一个遍历完成,那么最大的值肯定位于第一个节点也就是array[0],将该值与数组最后一个值交换,那么最大的值放到最后了,
 *     第一次排序完成,已经排序的元素是最后1个。为了方便理解,将最后一个值从树中移除,那么新的树,就比之前少了一个元素。
 *     第二次,依然按照上面的规律来遍历,遍历完成,找到了新树的最大值,也就是原数组的第二大值,放到第一次的最大值前面,同理从树移除该元素,新的树又少了一个元素
 *     如此下去,每次遍历都能找到当前树中的最大值,遍历完成后,顺序也就排序好了
 * 时间复杂度:O(nlgn)
 * 空间复杂度:O(1)
 */
public class HeapSort implements Sort {

	@Override
	public int[] sort(int[] src) {
		int size = src.length;
		int unsortSize = size;
		for(int i=unsortSize; i>0; i--) {
			for(int j=i/2; j>0; j--) {
				heapUp(src, j, i);
			}
			swip(src, 0, i-1);
		}
		return src;
	}
	
	/*
	 * 堆上浮,从一个根节点与他的左右叶子(如果都存在)对比,将最大的值与根进行交换
	 */
	private void heapUp(int[] src, int index, int unsortSize) {
		int max = index - 1;
		int right  = index*2; 
		int left = index*2 - 1;
		if(right < unsortSize && src[max] < src[right]) {
			max = right;
		}
		if(left < unsortSize && src[max] < src[left]) {
			max = left;
		}
		swip(src, index - 1, max);
	}
	
	
	private void swip(int[] src, int x, int y) {
		int tmp = src[x];
		src[x] = src[y];
		src[y] = tmp;
	}

}

8. 计数排序

package com.jaden.sort;

/*
 * 计数排序
 * 思想:适用于数组全部是正整数的情况,假如数据最大值是100,那么可以新建一个数组newAry,数组个数是101个,分别对应对应0-100的索引,将所有索引对应的值初始化为0,假如一个数出现一次,那么对应索引的值加1,
 *     如:出现了一次50,那么新数组newAry[50]=0+1=1,如果再次出现50,那么newAry[50]=1+1=2, 遍历原数组,将所有的值映射到新的数组中。
 *     那么最后,newAry中值大于0的下标,对应的是原数组的值,newAry的值,对应的是原数组中该值出现的次数。
 *     最后,遍历newAry,以上面的规则,将数组排序
 * 时间复杂度:O(n+k)
 * 空间复杂度:O(n+k)
 */
public class CountingSort implements Sort {

	@Override
	public int[] sort(int[] src) {
		int size = src.length;
		int max = findMax(src);
		
		int []countAry = new int[max + 1];
		//统计每个值出现的次数
		for(int i=0; i<size; i++) {
			countAry[src[i]]++;
		}
		
		//将每个值从小到大赋予给src数组
		int size2 = countAry.length;
		int index = 0;
		for(int i=0; i<size2; i++) {
			for(int j=0; j<countAry[i]; j++) {
				src[index++] = i;
			}
		}
		return src;
	}
	
	
	/*
	 * 找出数组中的最大值
	 */
	private int findMax(int[] src) {
		int size = src.length;
		int maxIndex = 0;
		for(int i=0; i<size; i++) {
			if(src[maxIndex] < src[i]) {
				maxIndex = i;
			}
		}
		return src[maxIndex];
	}

}

9. 桶排序

package com.jaden.sort;

import java.util.ArrayList;
import java.util.Collections;

/*
 * 桶排序
 * 思想:将数组分成若干个数据范围(桶),比如,一个数组最小值为0,最大值为100,假定有10个桶,那么数组0-10的为一个桶,10-20为第二个桶, 依次类推。根据范围将数据放入对应的桶。
 *     那么,从前到后,前面桶的值肯定都小于后面桶的值,只要把每个桶内部的数据排序完成,那么再从第一个桶到最后一个桶,依次取数据,那么新的数据顺序的数组即排列好的数组
 *     桶内的排序可以自选,考虑到桶内的元素个数不是定长,直接采用ArrayList进行存储,利用Collections进行排序,这里只是为了说明桶排序的思想
 * 时间复杂度:O(n+k)
 * 空间复杂度:O(n+k)
 */
public class BucketSort implements Sort {
	int BucketCount = 10; //定义十个桶
	int maxIndex = 0;
	int minIndex = 0;

	@Override
	public int[] sort(int[] src) {
		int size = src.length;
		findMaxAndMin(src);
		int min = src[minIndex];
		int max = src[maxIndex];
		//计算每个桶的范围
		int perCount = (int)Math.ceil(1.0f * (max - min + 1) / BucketCount);
		
		//初始化ArrayList,因为其可以动态增加,目前并不知道每个桶的大小
		ArrayList<ArrayList<Integer>> bucket  = new ArrayList<>(BucketCount);
		for(int i=0; i<BucketCount; i++) {
			bucket.add(new ArrayList<Integer>());
		}
		//通过每个值的范围,将值加入到对应的桶里面
		for(int i=0; i<size; i++) {
			bucket.get((src[i] - min) / perCount).add(src[i]);
		}
		
		//对每个桶进行排序
	    for(int i = 0; i < BucketCount; i++){
	        Collections.sort(bucket.get(i));
	    }
	    
	    //排序完成,按顺序取出每个桶的值
	    int index = 0;
	    for(int i=0; i<BucketCount; i++) {
	    	for(int x : bucket.get(i)) {
	    		src[index++] = x;
	    	}
	    }
	    
	    return src;
	}
	
	
	/*
	 * 找出数组中的最大值,最小值
	 */
	private void findMaxAndMin(int[] src) {
		int size = src.length;		
		for(int i=0; i<size; i++) {
			if(src[maxIndex] < src[i]) {
				maxIndex = i;
			}
			if(src[minIndex] > src[i]) {
				minIndex = i;
			}
		}
	}

}

10. 基数排序:

package com.jaden.sort;

import java.util.ArrayList;

/*
 * 基数排序
 * 思想:适用于正整数的排序,先找出整个数组中的最大值,然后再判断该值一共有多少位,假如是5位数,那么从个位开始,遍历整个数组,将整个数组分成10个部分,个位数为0的存到第一个部分,个位数为1的存在第二个部分,以此类推
 *     第一次循环完成,然后从第1部分到第10部分(也就是从个位数为0到个位数为9)依次给数组赋值,新的数组就是按个位数排序的数组了。然后开始第二次循环,从十位开始,还是一样,将十位数的数值分成十个部分,然后再生成新的数组
 *     然后是百位,千位,万位依次重复,那么最后得到的数组就排好序了
 *     仔细思考,假如我们要比较两个数的大小,那么先看最高位,如果最高位一样,那么看次高位,。。。依次向下,直到个位,那么就能区分两个值的大小了,这也是基数排序的思想
 * 时间复杂度:O(n*k)
 * 空间复杂度:O(n+k)
 */
public class RadixSort implements Sort {

	@Override
	public int[] sort(int[] src) {
		
		//初始化list,用来存储每次比较后的临时变量,因为list可以动态增加
		ArrayList<ArrayList<Integer>> tmpList = new ArrayList<>(10);
		for(int i=0; i<10; i++) {
			tmpList.add(new ArrayList<>());
		}
		
		int size = src.length;
		int max = findMax(src);
		int maxDigit = getDigit(max); //最大位数
		int index;
		
		for(int digit=1; digit <=maxDigit; digit++) {
			for(int i=0; i<size; i++) {
				tmpList.get(src[i] % (int)Math.pow(10, digit) / (int)Math.pow(10, digit-1)).add(src[i]); //分别存储在0-9的list中
			}
			index = 0;
			for(int i=0; i<10; i++) {
				for(int x : tmpList.get(i)) {
					src[index++] = x; //每次排序完成后把数组改序
				}
				tmpList.get(i).clear(); //清除ArrayList
			}
	
		}
		
		return src;
	}
	
	
	
	/*
	 * 找出数组中的最大值
	 */
	private int findMax(int[] src) {
		int size = src.length;
		int maxIndex = 0;
		for(int i=0; i<size; i++) {
			if(src[maxIndex] < src[i]) {
				maxIndex = i;
			}
		}
		return src[maxIndex];
	}
	
	/*
	 * 获取一个整数的位数
	 */
	private int getDigit(int a) {
		if(a < 0) {
			return 0;
		}
		int digit = 0;
		int c = a;
		do {
			c = c / 10;
			digit++;
		}while(c > 0);
		
		return digit;
	}

}

代码差不多已经贴完了,网上有一些动图或者视频能够快速理解各个排序的思路,如果有条件的话,那么可以在当前链接上查看各个排序的视频:

https://www.youtube.com/watch?v=kPRA0W1kECg

 

Demo github地址: 点此获取

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值