简单排序应该是编程中最基本的,本次分析的简单排序包括冒泡排序、选择排序、插入排序。
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数据结构和算法》一书。