在算法系列(二)查找算法 一文中,主要介绍了二分查找。这篇文章来介绍一下排序算法。
排序算法概述
排序算法比较多,分两个篇幅来讲。由易道难。
先总体看一下都有哪些排序算法
冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序,计数排序,基数排序,希尔排序,桶排序。
排序算法分类
根据排序过程中待排序文件存放的位置不同,可以把排序分为内部和外部排序两大类。在排序过程中,所有需要排序的数都在内存,并在内存中调整它们的存储顺序,称为内排序;在排序过程中,只有部分数被调入内存,并借助内存调整数在外存中的存放顺序排序方法称为外排序。内部排序适用于记录个数不很多的较小待排序文件的排序;外部排序则适用于记录个数太多不能一次全部放入内存的较大待排序文件的排序。
内部排序分类:
交换排序:常用的交换排序方法有冒泡排序和快速排序。
选择排序:常用的选择排序方法有直接选择排序、树型选择排序和堆排序。
插入排序:主要的插入排序方法有直接插入排序、希尔排序、二分法插入排序、二路插入排序和共享栈插入排序等。
归并排序
基数排序
交换排序:常用的交换排序方法有冒泡排序和快速排序。
选择排序:常用的选择排序方法有直接选择排序、树型选择排序和堆排序。
插入排序:主要的插入排序方法有直接插入排序、希尔排序、二分法插入排序、二路插入排序和共享栈插入排序等。
归并排序
基数排序
PS:我们只分析内部排序,暂时不对外部排序进行分析
待排序的记录序列中可能存在两个或两个以上关键字相等的记录。排序前的序列中Ri领先于Rj(即i<j).若在排序后的序列中Ri仍然领先于Rj,则称所用的方法是稳定的
如:插入排序 ,基数排序 ,归并排序 ,冒泡排序 ,计数排序
不稳定的排序算法有:快速排序,希尔排序,简单选择排序,堆排序。
冒泡排序
原理
原理是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换,
这样一趟过去后,最大或最小的数字被交换到了最后一位,
然后再从头开始进行两两比较交换,直到倒数第二位时结束
这样一趟过去后,最大或最小的数字被交换到了最后一位,
然后再从头开始进行两两比较交换,直到倒数第二位时结束
代码实现
- package com.algorithm.sort;
- /**
- * 冒泡排序算法
- *
- * @author chao
- *
- */
- public class Bubblesort {
- /**
- * 简单冒泡排序
- */
- public static void sort(int[] num) {
- int temp;
- int len = num.length;
- for (int i = 1; i < len; i++)
- for (int j = 0; j < len - i; j++) {
- if (num[j] > num[j + 1]) {
- temp = num[j];
- num[j] = num[j + 1];
- num[j + 1] = temp;
- }
- }
- }
- public static void main(String[] args) {
- int[] num = { 1, 5, 3, 2 };
- sort(num);
- for (int i = 0; i < num.length; i++)
- System.out.print(num[i] + " ");
- }
- }
下面开始考虑优化,如果对于一个本身有序的序列,或则序列后面一大部分都是有序的序列,上面的算法就会浪费很多的时间开销,这里设置一个标志flag,如果这一趟发生了交换,则为true,否则为false。明显如果有一趟没有发生交换,说明排序已经完成。
/**
* 设置一个标志,如果这一趟发生了交换,则为true,否则为false。明显如果有一趟没有发生交换,说明排序已经完成。
* @param a
* @param n
*/
public static void bubbleSort2(int [] a, int n){
int j, k = n;
boolean flag = true;//发生了交换就为true, 没发生就为false,第一次判断时必须标志位true。
while (flag){
flag=false;//每次开始排序前,都设置flag为未排序过
for(j=1; j<k; j++){
if(a[j-1] > a[j]){//前面的数字大于后面的数字就交换
//交换a[j-1]和a[j]
int temp;
temp = a[j-1];
a[j-1] = a[j];
a[j]=temp;
//表示交换过数据;
flag = true;
}
}
k--;//减小一次排序的尾边界
}//end while
}//end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
运行测试main函数结果:
0,1,1,2,3,3,4,7,8,9,12,22,65,
- 1
再进一步做优化。比如,现在有一个包含1000个数的数组,仅前面100个无序,后面900个都已排好序且都大于前面100个数字,那么在第一趟遍历后,最后发生交换的位置必定小于100,且这个位置之后的数据必定已经有序了,也就是这个位置以后的数据不需要再排序了,于是记录下这位置,第二次只要从数组头部遍历到这个位置就可以了。如果是对于上面的冒泡排序算法2来说,虽然也只排序100次,但是前面的100次排序每次都要对后面的900个数据进行比较,而对于现在的排序算法3,只需要有一次比较后面的900个数据,之后就会设置尾边界,保证后面的900个数据不再被排序。
public static void bubbleSort3(int [] a, int n){
int j , k;
int flag = n ;//flag来记录最后交换的位置,也就是排序的尾边界
while (flag > 0){//排序未结束标志
k = flag; //k 来记录遍历的尾边界
flag = 0;
for(j=1; j<k; j++){
if(a[j-1] > a[j]){//前面的数字大于后面的数字就交换
//交换a[j-1]和a[j]
int temp;
temp = a[j-1];
a[j-1] = a[j];
a[j]=temp;
//表示交换过数据;
flag = j;//记录最新的尾边界.
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
这种方法是我看到的最优化的冒泡排序了。
运行测试例子结果:
0,1,1,2,3,3,4,7,8,9,12,22,65,
- 1
可知运行结果正确。
选择排序
原理
它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
代码实现
- package com.algorithm.sort;
- /**
- * 选择排序算法
- *
- * @author chao
- *
- */
- public class SelectionSort {
- /**
- * 选择排序算法简单实现
- *
- * @param num
- */
- public static void sort(int num[]) {
- int min, index, tmp;
- int len = num.length;
- for (int i = 0; i < len; i++) {
- min = num[i];
- index = i;
- for (int j = i + 1; j < len; j++) {
- if (num[j] < min) {
- min = num[j];
- index = j;
- }
- }
- if (index != i) {
- tmp = num[i];
- num[i] = num[index];
- num[index] = tmp;
- }
- }
- }
- public static void main(String[] args) {
- int[] num = { 1, 5, 3, 2 };
- sort(num);
- for (int i = 0; i < num.length; i++)
- System.out.print(num[i] + " ");
- }
- }
插入排序
原理
插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据
代码实现
- package com.algorithm.sort;
- /**
- * 插入排序
- *
- * @author chao
- *
- */
- public class InsertionSort {
- /**
- * 简单插入排序
- *
- * @param num
- */
- public static void sort(int num[]) {
- int temp;
- int len = num.length;
- for (int i = 1; i < len; i++) {
- if (num[i - 1] > num[i]) {
- temp = num[i];
- int j = i;
- while (j > 0 && num[j - 1] > temp) {
- num[j] = num[j - 1];
- j--;
- }
- num[j] = temp;
- }
- }
- }
- public static void main(String[] args) {
- int[] num = { 1, 5, 3, 2 };
- sort(num);
- for (int i = 0; i < num.length; i++)
- System.out.print(num[i] + " ");
- }
- }
算法分析
以上三种算法的时间复杂度都为O(n^2)
插入排序
最好的情况下:正序有序(从小到大),这样只需要比较n次,不需要移动。因此时间复杂度为O(n)最坏的情况下:逆序有序,这样每一个元素就需要比较n次,共有n个元素,因此实际复杂度为O(n^2)
选择排序
最好情况下:交换0次,但是每次都要找到最小的元素,因此大约必须遍历N*N次,因此为O(N^2)。
选择排序的优点是移动次数少,仅移动n次就可以了,插入排序在有序的情况下运行时间会好些,插入排序移动次数多达n^2次。如果移动一个元素相当耗时,最好运行选择排序。插入排序更适合链表结构的数据排序。