简单排序的Java实现与效率分析

简单排序应该是编程中最基本的,一般在大学课本中有讲解,但是应该有许多同学和我一样没有在意,现在只好返工了。本次分析的简单排序包括冒泡排序、选择排序、插入排序。

首先我们准备一个要排序的数组,当然还有一些方法,基本如下:

	private int[] sortInts;//排序数组

	// 初始化sortInts
	public SimpleSorts(int[] sortInts) {
		this.sortInts = sortInts;
	}

	// getter
	public int[] getSortInts() {
		return sortInts;
	}

	// setter
	public void setSortInts(int[] sortInts) {
		this.sortInts = sortInts;
	}

	// 显示数组
	public void display() {
		System.out.println("数组:" + Arrays.toString(sortInts));
	}

	// 交换元素
	public void exchange(int first, int last) {
		int temp = sortInts[first];
		sortInts[first] = sortInts[last];
		sortInts[last] = temp;
	}

冒泡排序是最简单的排序方法,基本上每个程序员都能不假思索,信手拈来。其算法简单,基本逻辑如下:
1. 取数组第一个数为基数,并将数组最后一个数标记为标志数。
2. 比较基数右边的数,如果基数大于此数,那么就交换两个数,否则不处理。
3. 令基数右边的数为新基数,重复2-3步,直到新基数为标志数为止(此数需执行2-3过程)。
4. 取数组第一个数为基数,,将原标志数左边的数标记为新标志数,重复2-4步,直到新标志数为第一个数为止(此数不执行2-4过程)。
其代码如下:
	// 冒泡排序
	public void bubbleSort() {
		for (int i = sortInts.length - 1; i > 0; i--) { // 元素遍历
			for (int j = 0; j < i; j++) { // 元素比较,比i大的已经排好
				if (sortInts[j] > sortInts[j + 1]) // 比较大小,交换
				{
					exchange(j, j + 1);
				}
			}
		}
	}

选择排序是冒泡排序的优化,虽然比较次数上没有改变,但在交换次数上大大减少。其逻辑如下:
1. 在数组取第一个数为基数,令标识位等于基数的下标。
2. 依次比较基数右边的数,如果此数小于标识位所指向的数,则令标识位等于此数的下标,直到数组最后一个为止(此数也要比较)。
3. 交换基数与标志位所指向的数。
4. 取基数右边的数为新基数,令标志数等于新基数的下标,重复2-4过程,直到数组倒数第二个数(此数要比较)。
其代码如下:
	// 选择排序
	public void selectSort() {
		for (int i = 0; i < sortInts.length; i++)// 元素遍历
		{
			int temp = i;
			for (int j = i + 1; j < sortInts.length; j++)// 元素比较
			{
				if (sortInts[temp] > sortInts[j])// 比较大小,缓存下标
				{
					temp = j;
				}
			}
			if (temp != i)// 若下标有变,交换
			{
				exchange(i, temp);
			}
		}
	}
插入排序可以说是三者中最好的算法,尤其是在数据局部有序的情况下,其算法逻辑如下:
1. 在数组中取第二个数为基数,令缓冲数等于基数值。
2. 依次比较基数左边的数,若此数大于基数,则将此数右移(令此数右边的数等于此数。)直到有数小于基数为值(此数不要移动)。
3. 令数组最后右移的数等于缓冲数。
4. 取数组第三个数为新基数,令缓冲数等于基数值,重复2-4过程,直到最后一个数(此数需执行243过程)。
其代码如下:
	//插入排序
	public void InsertSort()
	{
		for (int i = 1; i < sortInts.length; i++) //元素遍历
		{
			int temp =  sortInts[i];
			int j = i;
			while(j > 0 && temp < sortInts[j-1]) //元素比较,直到比temp小,停止循环
			{
				sortInts[j] = sortInts[j-1];
				j--;
			}
			if( j != i)//若下标有变,交换
			{
				sortInts[j] = temp;//交换停止处元素与sortInts[i]
			}
		}
	}

从上面看,好像插入排序的算法并不比冒泡和选择简单。但是请注意,如果数组局部有序(那怕只有两个数是有序的),情况就大大不同。例如数组{2,3,4,5,6,1,0},数组中2,3,4,5,6属于局部有序。插入排序时,基数为2,3,4,5,6时,都不用处理,当基数为1时,方法只需要移动6次,基数为0时,移动7次。选择排序需要交换7次,冒泡排序需要交换21次,而一次交换至少需要移动2次。

为了测试效率,我们可以添加一些代码,整个类的代码如下:

import java.util.Arrays;
import java.util.Random;

public class SimpleSorts {
	// 排序数组、比较次数、复制次数
	private int[] sortInts;
	private Long compareNum = 0L;
	private Long copyNum = 0L;

	// 初始化sortInts、setter、
	public SimpleSorts(int[] sortInts) {
		this.sortInts = sortInts;
	}

	// getter
	public int[] getSortInts() {
		return sortInts;
	}

	// setter
	public void setSortInts(int[] sortInts) {
		compareNum = 0L;
		copyNum = 0L;
		this.sortInts = sortInts;
	}

	// 显示数组
	public void display() {
		System.out.println("数组:" + Arrays.toString(sortInts));
	}

	// 显示比较信息
	public void displayNum() {
		System.out.println("统计: = 比较 " + compareNum + " 次, 复制 = " + copyNum
				+ "次");
		System.out.println();
	}

	// 复制次数
	public void addCopyNum() {
		copyNum++;
	}

	// 比较次数
	public void addCompareNum() {
		compareNum++;
	}

	// 交换元素
	public void exchange(int first, int last) {
		int temp = sortInts[first];
		addCopyNum();
		sortInts[first] = sortInts[last];
		addCopyNum();
		sortInts[last] = temp;
		addCopyNum();
	}

	// 冒泡排序
	public void bubbleSort() {
		for (int i = sortInts.length - 1; i > 0; i--) { // 元素遍历
			for (int j = 0; j < i; j++) { // 元素比较,比i大的已经排好
				if (sortInts[j] > sortInts[j + 1]) // 比较大小,交换
				{
					exchange(j, j + 1);
				}
				addCompareNum();
			}
		}
	}

	// 选择排序
	public void selectSort() {
		for (int i = 0; i < sortInts.length; i++)// 元素遍历
		{
			int temp = i;
			for (int j = i + 1; j < sortInts.length; j++)// 元素比较
			{
				if (sortInts[temp] > sortInts[j])// 比较大小,缓存下标
				{
					temp = j;
				}
				addCompareNum();
			}
			if (temp != i)// 若下标有变,交换
			{
				exchange(i, temp);
			}
		}
	}

	// 插入排序
	public void InsertSort()// 元素遍历
	{
		for (int i = 1; i < sortInts.length; i++) {
			int temp = sortInts[i];
			addCopyNum();
			int j = i;
			addCompareNum();
			while (j > 0 && temp < sortInts[j - 1]) // 元素比较,直到比temp小,停止循环
			{
				if (compareNum != 1) {
					addCompareNum();
				}
				sortInts[j] = sortInts[j - 1];
				addCopyNum();
				j--;
			}
			if (j != i)// 若下标有变,交换
			{
				sortInts[j] = temp;// 交换停止处元素与sortInts[i]
				addCopyNum();
			}
		}
	}

	// 产生随机数组
	public static int[] randomInts(int initNum) {
		int[] ri = new int[initNum];
		for (int i = 0; i < ri.length; i++) {
			Random rd = new Random();
			ri[i] = rd.nextInt(10000);
		}
		return ri;
	}

	// 主方法
	public static void main(String[] args) {
		int[] a = randomInts(1000);
		int[] b = a.clone();
		int[] c = a.clone();

		// 冒泡
		SimpleSorts ss = new SimpleSorts(a);
		ss.bubbleSort();
		ss.displayNum();

		// 选择
		ss.setSortInts(b);
		ss.selectSort();
		ss.displayNum();

		// 插入
		ss.setSortInts(c);
		ss.InsertSort();
		ss.displayNum();
	}
}

当测试数为10时,结果如下:

统计: 比较 = 45 次, 复制 = 66次
统计: 比较 = 45 次, 复制 = 24次
统计: 比较 = 31 次, 复制 = 36次

当测试数为100时:结果如下:

统计: 比较 = 4950 次, 复制 = 6489次
统计: 比较 = 4950 次, 复制 = 291次
统计: 比较 = 2261 次, 复制 = 2356次

当测试数为1000时:结果如下:

统计: 比较 = 499500 次, 复制 = 773589次
统计: 比较 = 499500 次, 复制 = 2991次
统计: 比较 = 258861 次, 复制 = 259853次

当测试数为10000时:结果如下:

统计: = 比较 49995000 次, 复制 = 74298648次
统计: = 比较 49995000 次, 复制 = 29979次
统计: = 比较 24776215 次, 复制 = 24786200次
通过比较我们可以看出,大数据下冒泡排序效率最低,选择排序虽然移动次数最少,但是比较次数高,而插入排序是三者中综合效率最好的。
参考《Java数据结构和算法》一书,冒泡排序交换和比较操作次数为 N*(N-1)/2,是与N2成正比(记作O(N2));选择排序交换虽然为O(N),但是比较时间与冒泡排序相同,还是O(N2);插入排序比较时间为 N*(N-1)/4。而移到次数与插入次数大致相同,两者近似O(N2),但是如果数据局部有有序,While循环基本为假,则算法所需的时间就能蜕变成O(N)。
简单排序一般适用于小数据量,大数据量排序还得高级排序(如快速排序等)。如果大家想深入研究可以参考《Java数据结构和算法》一书。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值