先来谈谈排序的概念:
所谓排序,就是根据排序码的递增或者递减顺序把数据元素依次排列起来,使一组任意排列的元素变为一组按其排序码线性有序的元素。
排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。我们这里说说八大排序就是内部排序。
当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
1、交换排序
交换排序的基本思想:根据序列中两个元素的比较结果来对换这两个记录在序列中的位置,也就是说,将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
(1)冒泡排序:
冒泡排序的思想:根据序列中两个元素的比较结果交换两个元素的位置,将键值较大的元素向序列尾部移动,将键值较小的元素向序列前部移动。
冒泡排序示例:
算法实现:
package com.wit.stan.sort;
/**
* 交换排序:
* 交换排序的基本思想:根据序列中两个元素的比较结果来交换这两个记录在序列中的位置,
* 也就是说,将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
* 如:
* 冒泡排序
* 时间复杂度:最好情形O(n),平均情形O(n^2),最差情形O(n^2)
* 空间复杂度:O(1)
* 稳 定 性:稳定
* 内部排序(在排序过程中数据元素完全在内存)
* @author 关启培
*
*/
public class BubbleSort {
public static void main(String[] args) {
int[] a= {25,1,2,3,4,5,6,7,24,34};
BubbleSort bubbleSort=new BubbleSort();
/* bubbleSort.bubbleSort(a, a.length);*/
bubbleSort.bubbleSortPlus(a, a.length);
System.out.println("排序为:");
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");
}
System.out.println();
System.out.println("=====================");
//以数组形式输出
System.out.println(Arrays.toString(a));
}
//原始算法
public void bubbleSort(int a[],int n) {
int temp;
int m=0;
for(int i=0;i<n-1;i++) {
for(int j=0;j<n-1-i;j++) {
if(a[j]>a[j+1]){
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
System.out.println(a[j+1]+"和"+a[j]+"进行了交换");
}
m++;
}
}
System.out.println("判断的次数:"+m);
}
//优化算法
public void bubbleSortPlus(int a[],int n){
int temp;
int m=0;
for(int i=0;i<n-1;i++) {
//添加一个标记位
int flag=0;
for(int j=0;j<n-1-i;j++){
if(a[j]>a[j+1]) {
temp=a[j];
a[j]=a[j+1];
a[j+1]=temp;
flag=1;
}
m++;
}
if(flag==0) {
break;
}
}
System.out.println("优化判断的次数:"+m);
}
}
(2)快速排序:
快速排序算法是基于分治策略的另一个排序算法。
快速排序的思想:
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,记为X。
2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置(即最终位置)
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
快排目前有两类实现算法,第一种是标准算法,第二种是两头交换法。总的思想与上面三步一样,在细节处理上有一些差异。
标准算法思想及实现
标准算算法采用的思想是挖坑填坑的思想:
以一个数组作为示例,取区间第一个数为基准数。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
72 | 6 | 57 | 88 | 60 | 42 | 83 | 73 | 48 | 85 |
初始时,i = 0; j = 9; X = a[i] = 72
由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。
从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++; 这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j--;
数组变为:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
48 | 6 | 57 | 48 | 60 | 42 | 83 | 73 | 88 | 85 |
i = 3; j = 7; X=72
再重复上面的步骤,先从后向前找,再从前向后找。
从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
从i开始向后找,当i=5时,由于i==j退出。
此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。
数组变为:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
48 | 6 | 57 | 42 | 60 | 72 | 83 | 73 | 88 | 85 |
可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。
对挖坑填数进行总结
1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。
代码实现:
package com.wit.stan.sort;
import java.util.Arrays;
/**
* 快速排序算法
* 基本思想:
* 1.先从数列中取出一个数作为基准数,记为x。
2.分区过程,将不小于x的数全放到它的右边,不大于x的数全放到它的左边。(这样key的位置左边的没有大于key的,右边的没有小于key的,只需对左右区间排序即可)
3.再对左右区间重复第二步,直到各区间只有一个数
* @author stan
*
*/
public class Quick_Sort {
public static void main(String[] args) {
int[] a= {3,5,22,45,5,8,4,6};
Quick_Sort qu=new Quick_Sort();
qu.quick_sort(a, 0, a.length-1);
System.out.println("快速排序后的数组为:");
System.out.println(Arrays.toString(a));
}
public void quick_sort(int a[],int l,int r) {
int i=l,j=r;
int x=a[l];
while(i<j) {
while(i<j&&a[j]>x) {
j--;
}
if(i<j) {
a[i++]=a[j];
}
while(i<j&&a[i]<x){
i++;
}
if(i<j) {
a[j--]=a[i];
}
}
a[j]=x;
//此时i==j,下面s[i]或者s[j]都可以,j-1,j+1也ok
if(l<i) {
quick_sort(a, l, i-1);
}
if(r>i) {
quick_sort(a, i+1, r);
}
}
}
两头交换法思想及实现
两头交换法与标准算法思想的差异是,先从左边开始找到大于基准值的那个数,再从右边找到小于基准值的那个数,将两个数交换(这样比基准值小的都在左边,比基准值大的都在右边)。直到数列分成大于基准值和小于基准值的两个区间,以这两个区间进行同样的排序操作。
代码实现如下(这里只有算法,没有实现过程):
//两头交换法实现快速排序
public void quickSort(int a[],int begin,int end) {
if(begin>end)
return;
int i=begin,j=end,temp=0;
//这里选取了中间为基准值
int x=a[(i+j)>>1];
while(i<=j) {
while(a[i]<x) {
i++;
}
while(a[j]>x) {
j--;
}
if(i<=j) {
temp=a[i];
a[i]=a[j];
a[j]=temp;
i++;
j--;
}
}
quickSort(a, begin,j);
quickSort(a, i,end);
}
上面的算法是两头交换法官方的版本,边界情况较少,比较健壮。