排序算法分为6类:
插入排序:直接插入排序、折半插入排序、希尔排序
选择排序:直接选择排序、堆排序
交换排序:冒泡排序、快排
归并排序:二路归并(常用)、自然归并
分配排序:箱排序、基数排序
计数排序
一、插入排序
1、直接插入排序
1)伪代码:
for j ←1 to n
do key ← A[ j]
i ← j – 1
while i > 0 and A[i] > key
do A[i+1] ← A[i]
i ← i – 1
A[i+1] = key
2)思想:每次选择一个元素 key(A[j]) 插入到之前已排好序的部分A[1…i]中,插入过程中key依次由后向前与A[1…i]中的元素进行比较。若发现发现A[x]>=key,则将key插入到A[x]的后面(所以插入排序是稳定的),插入前需要移动元素。
3)时间复杂度:
最好的情况下:正序有序(从小到大),这样只需要比较n次,不需要移动。因此时间复杂度为O(n) ;
最坏的情况下:逆序有序,这样每一个元素就需要比较n次,共有n个元素,因此实际复杂度为O(n2) ;
平均情况下:O(n2)。
4)Java代码实现:
public static void getSort(int[] data, int n){
int temp;
for(int i=1; i<n; i++){
temp = data[i];
int j=i-1;
//从后向前扫描,找到temp大于data[j]的第一个位置
while(j>=0 && data[j]>temp){
data[j+1] = data[j];//向后移动
j--;
}
data[j+1] = temp;
}
}
2、折半插入排序
折半插入排序(binary insertion sort)是对插入排序算法的一种改进。
1)思想:将一个新元素插入已排好序的数组的过程中,寻找插入点时,将待插入区域的首元素设置为a[low],末元素设置为a[high],则轮比较时将待插入元素与a[m],其中m=(low+high)/2相比较,如果比参考元素小,则选择a[low]到a[m-1]为新的插入区域(即high=m-1),否则选择a[m+1]到a[high]为新的插入区域(即low=m+1),如此直至low<=high不成立,即将此位置之后所有元素后移一位,并将新元素插入a[high+1]。
2)时间复杂度
折半插入排序算法是一种稳定的排序算法,比直接插入算法明显减少了关键字之间比较的次数,因此速度比直接插入排序算法快,但记录移动的次数没有变,所以折半插入排序算法的时间复杂度仍然为O(n^2),与直接插入排序算法相同。
3)Java代码实现
public static void getSort(int[] data, int n){
for(int i=1; i<n; i++){
int temp = data[i];
int low=0, high=i-1;
while(low<=high){//折半查找出最合适的插入位置
int mid = (low+high)/2;
if(data[mid]>data[i]){
high = mid-1;
}else if(data[mid]<data[i]){
low = mid+1;
}
}
int j=i;
while(j>low){//向后移动
data[j] = data[j-1];
j--;
}
data[low] = temp;
}
}
3、希尔排序
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。 由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
1)伪代码:
input: an array a of length n with array elements numbered 0 to n − 1
inc ← round(n/2)
while inc > 0 do:
for i = inc .. n − 1 do:
temp ← a[i]
j ← i
while j ≥ inc and a[j − inc] > temp do:
a[j] ← a[j − inc]
j ← j − inc
a[j] ← temp
inc ← round(inc / 2.2)
2)思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量 dt =1( dt<d(t-1) …<d2<d1),即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。一般的初次取序列的一半为增量,以后每次减半,直到增量为1。
百度百科示例:
3)时间复杂度
希尔排序时间复杂度的下界是n*log2n。希尔排序没有快速排序算法快 O(n(logn)),因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择。但是比O( )复杂度的算法快得多。并且希尔排序非常容易实现,算法代码短而简单。 此外,希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。
4)Java代码实现
public static void getSort1(int[] data, int n){
int step = n/2;//默认为数组长度的1/2
while(step>0){
for(int i=step; i<n; i++){
int temp = data[i];
int j = i;
while(j>=step && data[j-step]>temp){//交换
data[j] = data[j-step];
j = j-step;
}
data[j] = temp;
}
step = step/2;//每次步长变为原来的1/2
}
}
二、选择排序
1、直接选择排序
2、堆排序
1)思想:堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。
http://blog.youkuaiyun.com/morewindows/article/details/6709644/
将这篇博客的插入和删除看懂就OK了。
2)时间复杂度
堆排序过程的时间复杂度是O(nlgn)。因为建堆的时间复杂度是O(n)(调用一次);调整堆的时间复杂度是lgn,调用了n-1次,所以堆排序的时间复杂度是O(nlgn)。
3)Java代码实现
/**
* 堆排序
* @author ZD
*/
public class HeapSort {public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int data[] = new int[n];
for(int i=0; i<n; i++){
data[i] = scanner.nextInt();
}
//堆化,从n/2-1开始,再从上往下调整
for(int i=n/2-1; i>=0; i--){
getSort(data, i, n);
}
//排序,删除之后再堆化
for(int i=n-1; i>=1; i--){
int temp = data[0];
data[0] = data[i];
data[i] = temp;
getSort(data, 0, i);
}
for(int i=0; i<n; i++){
System.out.print(data[i]+" ");
}
}public static void getSort(int[] data, int i, int n){//i代表是第几个结点,其左右子节点分别为2*i+1, 2*i+2
int parent = data[i];
int j=2*i+1;
while(j<n){
//小根堆, 排序之后是递减
/*if(j+1<n && data[j+1]<data[j]){//选取左右子节点中最小的那个
j++;
}
if(data[j]>=parent){//左右子节点均比父节点大
break;
}*/
//大根堆和小根堆类似,只需要寻找比父节点大的子节点然后交换即可
//大根堆,排序之后是递增
if(j+1<n && data[j+1]>data[j]){//选取左右子节点中最大的那个
j++;
}
if(data[j]<=parent){//左右子节点均比父节点小
break;
}
data[i] = data[j];
//下一个父节点,如果左右子节点中最小结点比父节点大则交换
i = j;
j = 2*i+1;
}
data[i] = parent;//父节点交换至根节点
}
}
三、交换排序
1、冒泡排序
平均时间复杂度O(n2),因为是比较相邻元素,所以元素相同不交换位置,为稳定排序算法。
1)思想:
-
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
-
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
-
针对所有的元素重复以上的步骤,除了最后一个。
-
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
2)Java代码实现
public static void getSort(int[] data, int n){
for(int i=0; i<n; i++){
for(int j=0; j<n-1-i; j++){
if(data[j]>data[j+1]){//相邻元素比较,将大的元素往后移
int temp = data[j];
data[j] = data[j+1];
data[j+1] = temp;
}
}
}
}
3、快排
快速排序(Quicksort)是对冒泡排序的一种改进。
1)思想:
- 1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
- 2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
- 3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]互换;
- 4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
- 5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。
2)时间复杂度
每次划分都是按照折半划分,那么其时间复杂度是0(nlogn)。极端的情况,如1,2,3,4,5,那么每次第一次划分的地方就是1,这样其实就退化成O(n2)。
3)Java代码实现
public static void getSort(int[] data, int left, int right){
if(left>=right){
return;
}
//key默认为第一个
int key = data[left];
int i=left, j=right;
while(i<j){
//从右往左扫描,第一个比key小的
while(i<j && data[j]>=key){
j--;
}
data[i] = data[j];
//从左往右扫描,第一个比key大的
while(i<j && data[i]<=key){
i++;
}
data[j] = data[i];
}
data[i] = key;//一次Partition完成
/*for(int k=0; k<data.length; k++){
System.out.print(data[k]+" ");
}*/
System.out.println();
getSort(data, left, j-1);
getSort(data, i+1, right);
}
四、归并排序
1、二路归并
是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
1)思想:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。
2)时间复杂度
归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。
3)Java代码实现
/**
* 合并排序
* @author ZD
*/
public class MergeSort {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int data[] = new int[n];
for(int i=0; i<n; i++){
data[i] = scanner.nextInt();
}
int[] temp = new int[n];
getSort(data, 0, n-1, temp);
for(int i=0; i<n; i++){
System.out.print(temp[i]+" ");
}
}
//将有二个有序数列data[first...mid]和data[mid...last]合并。
public static void mergeSort(int[] data, int first, int mid, int last, int[] temp){
int k=0;
int i=first, j=mid+1;
while(i<=mid && j<=last){
if(data[i]<=data[j]){
temp[k++] = data[i++];
}else{
temp[k++] = data[j++];
}
}
//数组剩余部分处理
//前一个数组
while(i<=mid){
temp[k++] = data[i++];
}
//后一个数组
while(j<=last){
temp[k++] = data[j++];
}
//总和排序结果
for(i=0; i<k; i++){
data[first+i] = temp[i];
}
}
//分组排序然后合并
public static void getSort(int[] data, int first, int last, int[] temp){
if(first<last){
int mid = (first+last)/2;
getSort(data, first, mid, temp);//左边排序
getSort(data, mid+1, last, temp);//右边排序
mergeSort(data, first, mid, last, temp);//合并
}
}
}
详细解释可参考:http://blog.youkuaiyun.com/morewindows/article/details/6678165/
记录至此,待续。