插入排序
1.直接插入排序
原理:将数组分为无序区和有序区两个区,然后不断将无序区的第一个元素按大小顺序插入到有序区中去,最终将所有无序区元素都移动到有序区完成排序。
要点:设立哨兵,作为临时存储和判断数组边界之用。
实现:
1. /**
2. * 直接插入排序
3. * @author ming
4. *
5. */
6. public class InsertSort {
7. /**
8. * @param args
9. */
10. public static void main(String[] args) {
11. int[] a = {12,3,41,23,6,9,45,23,315,19,0};
12.
13. InsertSort sort = new InsertSort();
14. sort.Sort(a);
15.
16. for (int i = 0; i < a.length; i++) {
17. System.out.print(a[i]);
18. System.out.print(" ");
19. }
20. }
21.
22. public int Sort(int[] arr){
23. int rst = 0;
24. int tmp = 0 ;
25. for (int i = 1; i < arr.length; i++) {
26.
27. tmp = arr[i];
28.
29. int j = i-1;
30.
31. while (j>=0 && tmp < arr[j]) {
32. arr[j+1] = arr[j];
33. j--;
34. }
35. arr[j+1] = tmp;
36.
37. }
38.
39.
40. return rst;
41. }
42. }
2.希尔排序
原理:又称增量缩小排序。先将序列按增量划分为元素个数相同的若干组,使用直接插入排序法进行排序,然后不断缩小增量直至为1,最后使用直接插入排序完成排序。
要点:增量的选择以及排序最终以1为增量进行排序结束。
实现:
1. public static int[] shellSort(int []R){
2.
3. int gap = R.length/2;
4. int temp;
5. while(gap>0){
6. for(int i=gap;i<R.length;i++){
7. temp = R[i];
8. int j = i - gap;
9. while(j>=0 && temp < R[j]){
10. R[j+gap] = R[j];
11. j = j - gap;
12. }
13. R[j+gap] = temp;
14. }
15. gap = gap/2;
16. }
17. return R;
18. }
19.
20. public static void display(int[] R){
21. System.out.println();
22. for(int i=0;i<R.length;i++){
23. System.out.print(R[i]+" ");
24. }
25. }
26.
27. public static void main(String[] args) {
28.
29. final int M = 50;//定义数组大小为50
30. int []R = new int[M];
31. for(int i=0;i<M;i++){
32. R[i] = new Random().nextInt(100);//生成100以内的随机数
33. }
34. display(R);
35. R = shellSort(R);
36. display(R);
37.
38. }
39.
40. }
交换排序
1.冒泡排序
原理:将序列划分为无序和有序区,不断通过交换较大元素至无序区尾完成排序。
要点:设计交换判断条件,提前结束以排好序的序列循环。
实现:
1. public class bubbleSort {
2. public bubbleSort(){
3. int a[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,99,98,54,56,17,18,23,34,15,35,25,53,51};
4. int temp=0;
5. for(int i=0;i<a.length-1;i++){
6. for(int j=0;j<a.length-1-i;j++){
7. if(a[j]>a[j+1]){
8. temp=a[j];
9. a[j]=a[j+1];
10. a[j+1]=temp;
11. }
12. }
13. }
14. for(int i=0;i<a.length;i++)
15. System.out.println(a[i]);
16. }
17. }
2.快速排序
原理:不断寻找一个序列的中点,然后对中点左右的序列递归的进行排序,直至全部序列排序完成,使用了分治的思想。
要点:递归、分治
实现:
1. public class quickSort {
2.
3. inta[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,99,98,54,56,17,18,23,34,15,35,25,53,51};
4.
5. public quickSort(){
6.
7. quick(a);
8.
9. for(int i=0;i<a.length;i++)
10.
11. System.out.println(a[i]);
12.
13. }
14.
15. publicint getMiddle(int[] list, int low, int high) {
16.
17. int tmp = list[low]; //数组的第一个作为中轴
18.
19. while (low < high) {
20.
21. while (low < high && list[high] >= tmp) {
22.
23. high--;
24.
25. }
26.
27. list[low] = list[high]; //比中轴小的记录移到低端
28.
29. while (low < high && list[low] <= tmp) {
30.
31. low++;
32.
33. }
34.
35. list[high] = list[low]; //比中轴大的记录移到高端
36.
37. }
38.
39. list[low] = tmp; //中轴记录到尾
40.
41. return low; //返回中轴的位置
42.
43. }
44.
45. publicvoid _quickSort(int[] list, int low, int high) {
46.
47. if (low < high) {
48.
49. int middle = getMiddle(list, low, high); //将list数组进行一分为二
50.
51. _quickSort(list, low, middle - 1); //对低字表进行递归排序
52.
53. _quickSort(list, middle + 1, high); //对高字表进行递归排序
54.
55. }
56.
57. }
58.
59. publicvoid quick(int[] a2) {
60.
61. if (a2.length > 0) { //查看数组是否为空
62.
63. _quickSort(a2, 0, a2.length - 1);
64.
65. }
66.
67. }
68.
69. }
选择排序
1.直接选择排序
原理:将序列划分为无序和有序区,寻找无序区中的最小值和无序区的首元素交换,有序区扩大一个,循环最终完成全部排序。
要点:
实现:
package com.test;
/**
* Theselection sorting demo in Java.
*<a href="http://my.oschina.net/arthor" class="referer"target="_blank">@author</a> Belin Wu
*@version 1.0 2012-07-29
*/
public class SelectionSort {
/**
* Sort the int array by selection sorting.
* @param values the int array for sorting.
*/
publicstatic void selectionSort(int[] values) {
inttemp = 0; // 数组元素交换时的临时变量
//进行数组排序
for(int i = 0; i < values.length - 1; i++) {
//认为数组第i个元素是第i趟中的最小值,记录该索引。
intminValueIndex = i;
for(int j = i + 1; j < values.length; j++) {
if(values[minValueIndex] > values[j]) {
minValueIndex= j; // 之前的假设不成立,重新记录数组中最小值的索引值。
}
}
//当minValueIndex值发生了变化才进行数组元素的交换
if(minValueIndex != i) {
temp= values[i];
values[i]= values[minValueIndex];
values[minValueIndex]= temp;
}
}
}
/**
* Test the selection sorting.
* @param args the console arguments as astring array.
*/
publicstatic void main(String[] args) {
//初始化数组
int[]values = {1, -1, 3, 3, 2, 9, -10, 7, 6, 5};
//调用方法
selectionSort(values);
//打印数组
for(int i = 0; i < values.length; i++) {
System.out.println("values["+ i + "] = " + values[i]);
}
/*输出结果
values[0]= -10
values[1]= -1
values[2]= 1
values[3]= 2
values[4]= 3
values[5]= 3
values[6]= 5
values[7]= 6
values[8]= 7
values[9]= 9
*/
}
}
2.堆排序
原理:利用大根堆或小根堆思想,首先建立堆,然后将堆首与堆尾交换,堆尾之后为有序区。
要点:建堆、交换、调整堆
实现:
1. public class HeapSort {
2. public static int heap_size;
3. //双亲编号
4. public static int parent(int i){
5. return i/2;
6. }
7. //左孩子编号
8. public static int leftChild(int i){
9. return 2*i;
10. }
11. //右孩子编号
12. public static int rightChild(int i){
13. return 2*i + 1;
14. }
15. /**
16. * 保持最大堆的性质
17. * @param a,堆中的数组元素
18. * @param i,对以该元素为根元素的堆进行调整,假设前提:左右子树都是最大堆
19. *
20. * 由于左右孩子都是最大堆,首先比较根元素与左右孩子,找出最大值,假如不是根元素,则调整两个元素的值;
21. * 由于左孩子(右孩子)的值与根元素交换,有可能打破左子树(右子树)的最大堆性质,因此继续调用,直至叶子元素。
22. */
23. public static void max_heapify(int[] a, int i){
24. int left = leftChild(i);
25. int right = rightChild(i);
26. int largest = 0;
27. if(left < heap_size && a[i]<a[left]){
28. largest = left;
29. }else{
30. largest = i;
31. }
32. if(right < heap_size && a[right] > a[largest]){
33. largest = right;
34. }
35. if(largest == i){
36. return ;
37. }else{
38. int temp = a[i];
39. a[i] = a[largest];
40. a[largest] = temp;
41. max_heapify(a, largest);
42. }
43. }
44. /**
45. * 建立最大堆。在数据中,a.length/2+1一直到最后的元素都是叶子元素,也就是平凡最大堆,因此从其前一个元素开始,一直到
46. * 第一个元素,重复调用max_heapify函数,使其保持最大堆的性质
47. * @param a
48. */
49. public static void build_max_heap(int[] a){
50. for(int i = a.length/2; i>=1; i--){
51. max_heapify(a, i);
52. }
53. }
54. /**
55. * 堆排序:首先使用建立最大堆的算法建立好最大堆,然后将堆顶元素(最大值)与最后一个值交换,同时使得
56. * 堆的长度减小1 ,调用保持最大堆性质的算法调整,使得堆顶元素成为最大值,此时最后一个元素已被排除在外、
57. */
58. public static void heapSort(int[] a){
59. build_max_heap(a);
60. for(int i = a.length - 1; i>=2; i--){
61. int temp = a[1];
62. a[1] = a[i];
63. a[i] = temp;
64. heap_size--;
65. max_heapify(a, 1);
66. }
67. }
68. public static void main(String[] args) {
69. int a[] = {0, 4, 1, 3, 2, 16, 9, 10,14, 8, 7};
70. heap_size = a.length;
71. heapSort(a);
72. for(int i = 0; i< a.length; i++){
73. System.out.print(a[i] + " ");
74. }
75. }
76. }
归并排序
原理:将原序列划分为有序的两个序列,然后利用归并算法进行合并,合并之后即为有序序列。
要点:归并、分治
实现:
1. public class MergeSortTest {
2.
3. public static void main(String[] args) {
4. int[] data = new int[] { 5, 3, 6, 2, 1, 9, 4, 8, 7 };
5. print(data);
6. mergeSort(data);
7. System.out.println("排序后的数组:");
8. print(data);
9. }
10.
11. public static void mergeSort(int[] data) {
12. sort(data, 0, data.length - 1);
13. }
14.
15. public static void sort(int[] data, int left, int right) {
16. if (left >= right)
17. return;
18. // 找出中间索引
19. int center = (left + right) / 2;
20. // 对左边数组进行递归
21. sort(data, left, center);
22. // 对右边数组进行递归
23. sort(data, center + 1, right);
24. // 合并
25. merge(data, left, center, right);
26. print(data);
27. }
28.
29. /**
30. * 将两个数组进行归并,归并前面2个数组已有序,归并后依然有序
31. *
32. * @param data
33. * 数组对象
34. * @param left
35. * 左数组的第一个元素的索引
36. * @param center
37. * 左数组的最后一个元素的索引,center+1是右数组第一个元素的索引
38. * @param right
39. * 右数组最后一个元素的索引
40. */
41. public static void merge(int[] data, int left, int center, int right) {
42. // 临时数组
43. int[] tmpArr = new int[data.length];
44. // 右数组第一个元素索引
45. int mid = center + 1;
46. // third 记录临时数组的索引
47. int third = left;
48. // 缓存左数组第一个元素的索引
49. int tmp = left;
50. while (left <= center && mid <= right) {
51. // 从两个数组中取出最小的放入临时数组
52. if (data[left] <= data[mid]) {
53. tmpArr[third++] = data[left++];
54. } else {
55. tmpArr[third++] = data[mid++];
56. }
57. }
58. // 剩余部分依次放入临时数组(实际上两个while只会执行其中一个)
59. while (mid <= right) {
60. tmpArr[third++] = data[mid++];
61. }
62. while (left <= center) {
63. tmpArr[third++] = data[left++];
64. }
65. // 将临时数组中的内容拷贝回原数组中
66. // (原left-right范围的内容被复制回原数组)
67. while (tmp <= right) {
68. data[tmp] = tmpArr[tmp++];
69. }
70. }
71.
72. public static void print(int[] data) {
73. for (int i = 0; i < data.length; i++) {
74. System.out.print(data[i] + "\t");
75. }
76. System.out.println();
77. }
78.
79. }
基数排序
原理:将数字按位数划分出n个关键字,每次针对一个关键字进行排序,然后针对排序后的序列进行下一个关键字的排序,循环至所有关键字都使用过则排序完成。
要点:对关键字的选取,元素分配收集。
实现:
1. ackage com.algorithm.sort;
2.
3. import java.util.Arrays;
4.
5. public class RadixSort {
6.
7. //基于计数排序的基数排序算法
8. private static void radixSort(int[] array,int radix, int distance) {
9. //array为待排序数组
10. //radix,代表基数
11. //代表排序元素的位数
12.
13. int length = array.length;
14. int[] temp = new int[length];//用于暂存元素
15. int[] count = new int[radix];//用于计数排序
16. int divide = 1;
17.
18. for (int i = 0; i < distance; i++) {
19.
20. System.arraycopy(array, 0,temp, 0, length);
21. Arrays.fill(count, 0);
22.
23. for (int j = 0; j < length; j++) {
24. int tempKey = (temp[j]/divide)%radix;
25. count[tempKey]++;
26. }
27.
28. for (int j = 1; j < radix; j++) {
29. count [j] = count[j] + count[j-1];
30. }
31.
32. //个人觉的运用计数排序实现计数排序的重点在下面这个方法
33. for (int j = length - 1; j >= 0; j--) {
34. int tempKey = (temp[j]/divide)%radix;
35. count[tempKey]--;
36. array[count[tempKey]] = temp[j];
37. }
38.
39. divide = divide * radix;
40.
41. }
42.
43. }
44.
45.
46. /**
47. * @param args
48. */
49. public static void main(String[] args) {
50.
51. int[] array = {3,2,3,2,5,333,45566,2345678,78,990,12,432,56};
52. radixSort(array,10,7);
53. for (int i = 0; i < array.length; i++) {
54. System.out.print(" " + array[i]);
55. }
56.
57.
58. }
59.
60. }
1快速排序(QuickSort)
快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。
(1) 如果不多于1个数据,直接返回。
(2) 一般选择序列最左边的值作为支点数据。
(3) 将序列分成2部分,一部分都大于支点数据,另外一部分都小于支点数据。
(4) 对两边利用递归排序数列。
快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。
2 归并排序(MergeSort)
归并排序先分解要排序的序列,从1分成2,2分成4,依次分解,当分解到只有1个一组的时候,就可以排序这些分组,然后依次合并回原来的序列中,这样就可以排序所有数据。合并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组。
3 堆排序(HeapSort)
堆排序适合于数据量非常大的场合(百万数据)。
堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。
堆排序会将所有的数据建成一个堆,最大的数据在堆顶,然后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。
4 Shell排序(ShellSort)
Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。现在多用D.E.Knuth的分组方法。
Shell排序比冒泡排序快5倍,比插入排序大致快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢很多。但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。它对于数据量较小的数列重复排序是非常好的。
5 插入排序(InsertSort)
插入排序通过把序列中的值插入一个已经排序好的序列中,直到该序列的结束。插入排序是对冒泡排序的改进。它比冒泡排序快2倍。一般不用在数据大于1000的场合下使用插入排序,或者重复排序超过200数据项的序列。
6 冒泡排序(BubbleSort)
冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升。它是O(n^2)的算法。
7 交换排序(ExchangeSort)和选择排序(SelectSort)
这两种排序方法都是交换方法的排序算法,效率都是 O(n2)。在实际应用中处于和冒泡排序基本相同的地位。它们只是排序算法发展的初级阶段,在实际中使用较少。
8 基数排序(RadixSort)
基数排序和通常的排序算法并不走同样的路线。它是一种比较新颖的算法,但是它只能用于整数的排序,如果我们要把同样的办法运用到浮点数上,我们必须了解浮点数的存储格式,并通过特殊的方式将浮点数映射到整数上,然后再映射回去,这是非常麻烦的事情,因此,它的使用同样也不多。而且,最重要的是,这样算法也需要较多的存储空间。
9 总结
下面是一个总的表格,大致总结了我们常见的所有的排序算法的特点。
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
冒泡 | O(n2) | O(n2) | 稳定 | O(1) | n小时较好 |
交换 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
选择 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(logRB) | O(logRB) | 稳定 | O(n) | B是真数(0-9), R是基数(个十百) |
Shell | O(nlogn) | O(ns) 1<s<2 | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n2) | 不稳定 | O(nlogn) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |