简单排序应该是编程中最基本的,一般在大学课本中有讲解,但是应该有许多同学和我一样没有在意,现在只好返工了。本次分析的简单排序包括冒泡排序、选择排序、插入排序。
1. 取数组第一个数为基数,并将数组最后一个数标记为标志数。
2. 比较基数右边的数,如果基数大于此数,那么就交换两个数,否则不处理。
3. 令基数右边的数为新基数,重复2-3步,直到新基数为标志数为止(此数需执行2-3过程)。
4. 取数组第一个数为基数,,将原标志数左边的数标记为新标志数,重复2-4步,直到新标志数为第一个数为止(此数不执行2-4过程)。
其代码如下:
选择排序是冒泡排序的优化,虽然比较次数上没有改变,但在交换次数上大大减少。其逻辑如下:
1. 在数组取第一个数为基数,令标识位等于基数的下标。
2. 依次比较基数右边的数,如果此数小于标识位所指向的数,则令标识位等于此数的下标,直到数组最后一个为止(此数也要比较)。
3. 交换基数与标志位所指向的数。
4. 取基数右边的数为新基数,令标志数等于新基数的下标,重复2-4过程,直到数组倒数第二个数(此数要比较)。
其代码如下:
1. 在数组中取第二个数为基数,令缓冲数等于基数值。
2. 依次比较基数左边的数,若此数大于基数,则将此数右移(令此数右边的数等于此数。)直到有数小于基数为值(此数不要移动)。
3. 令数组最后右移的数等于缓冲数。
4. 取数组第三个数为新基数,令缓冲数等于基数值,重复2-4过程,直到最后一个数(此数需执行243过程)。
其代码如下:
从上面看,好像插入排序的算法并不比冒泡和选择简单。但是请注意,如果数组局部有序(那怕只有两个数是有序的),情况就大大不同。例如数组{2,3,4,5,6,1,0},数组中2,3,4,5,6属于局部有序。插入排序时,基数为2,3,4,5,6时,都不用处理,当基数为1时,方法只需要移动6次,基数为0时,移动7次。选择排序需要交换7次,冒泡排序需要交换21次,而一次交换至少需要移动2次。
参考《Java数据结构和算法》一书,冒泡排序交换和比较操作次数为 N*(N-1)/2,是与N2成正比(记作O(N2));选择排序交换虽然为O(N),但是比较时间与冒泡排序相同,还是O(N2);插入排序比较时间为 N*(N-1)/4。而移到次数与插入次数大致相同,两者近似O(N2),但是如果数据局部有有序,While循环基本为假,则算法所需的时间就能蜕变成O(N)。
简单排序一般适用于小数据量,大数据量排序还得高级排序(如快速排序等)。如果大家想深入研究可以参考《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数据结构和算法》一书。