参考大话数据结构与http://blog.csdn.net/hguisu/article/details/7776068/
|
| 时间 | 空间 |
交换 排序 | 冒泡排序 | O(N2) | O(1) |
快速排序 |
|
| |
选择 排序 | 选择排序 | O(N2) | O(1) |
堆排序法 |
| O(1) | |
插入排序 | 插入排序 | O(N2) | O(1) |
希尔排序 | O(1)-O(N2) | O(lgN)~O(N) | |
| 归并排序 | O(N*lgN) | O(N) |
| 基数排序 | O(N) | O(M) |
稳定性 | 相同值元素在排序前后 相对次序是否保持不变 |
---|---|
稳定 | 插入、冒泡、归并、基数、 |
不稳定 | 选择、快速、希尔、堆 |
1.冒泡排序法:【一列数,自上而下相邻两数依稀比较、调整,小数冒泡出来,大数有所下沉】
一趟冒泡下来,最小的数已经冒出来了,接着二趟冒泡(除了最小之外的所有元素),次小数冒到位,。。。。。。
import java.util.*;
public class BubbleSort {
public int[] bubbleSort(int[] A, int n) {
if(A == null || A.length == 0)
return null;
for(int i = 0; i < A.length-1 ;i++){
/*for (int j = A.length-1; j > i ;j--){
if(A[j]<A[j-1])//j要跟j+1比较,i这里只是给了一个冒泡排序的标志起始位置
swap(A,j,j-1);//这里是A[j]>A[j+1],冒泡,一次循环之后得保证最大数冒出来
}*/
for (int j = 0; j < A.length-1-i ;j++) {
if(A[j]>A[j+1])
swap(A,j,j+1);//这里是A[j]>A[j+1],冒泡,一次循环之后得保证最大数从底端down出来
}
}
return A;//return的位置
}
public static void swap(int[] A, int i, int j){
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
鸡尾酒排序【定向冒泡排序】可以以双向在序列中排序
先找到最小放第一位,再找最大放最后,再找次小,再找次大。。。。。
public static int[] cocktailSort(int[] src)
{
//将最小值排到队尾
for(int i = 0 ; i < src.length/2 ; i++)
{
for(int j = i ; j < src.length-i-1 ; j++)
{
if(src[j] < src[j+1])
{
int temp = src[j];
src[j] = src[j+1];
src[j+1] = temp;
}
System.out.println("交换小"+Arrays.toString(src));
}
//将最大值排到队头
for(int j = src.length-1-(i+1); j > i ; j--)
{
if(src[j] > src[j-1])
{
int temp = src[j];
src[j] = src[j-1];
src[j-1] = temp;
}
System.out.println("交换大"+Arrays.toString(src));
}
System.out.println("第"+i+"次排序结果:"+Arrays.toString(src));
}
return src;
}
2.选择排序法:选最值数依次与应呆位置数交换
一趟选择下来,找到的最小(最大)的数与第1个位置数交换完成;第二趟选择,找到次小(次大)的数与第2个位置交换完成;。。。。。。;第n-1个数与第n个数比较、交换为止。整个序列有序。
import java.util.*;
public class SelectionSort {
public int[] selectionSort(int[] A, int n) {
if(A == null || A.length == 0)
return null;
for(int i = 0;i < A.length-1;i++){
int temp = i;
for(int j = i;j < A.length-1;j++){
if( A[j+1] < A[temp] )
temp = j+1;//找到未成序的最小值的位置
}
swap(A, temp ,i);
}
return A;
}
public static void swap(int[] A, int i, int j){
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
3.插入排序法:直接插入排序法
拿一个数据一次插入有序表中,得到新的记录数+1的有序表。
需要设立哨兵,作为临时存储与判断数组插入位置。
移动位置的问题:移动位置(元素后移腾位置)只会发生在已经有序的数组中。
import java.util.*;
public class InsertionSort {
public int[] insertionSort(int[] A, int n) {
int i, j, temp;
for(i = 1;i < n ;i++){ //不能再用 int i会出现重定义
temp = A[i]; //待插入的节点是A[i] 注意i的取值范围
for(j = i; j > 0 && A[j - 1] > temp; j-- ){
A[j] = A[j - 1];
}
A[j] = temp;//j已经--了 所以这里用的j而不是j-1
/*int flag = i;
for(int j = i;j > 0 && A[j-1] >temp ;j-- ){
A[j] = A[j-1];
flag = j-1; //是否要用到 flag ,j不是局部变量么 oo大循环之前设置了全局变量 i,j
}
A[flag] = temp;
*/
}
return A;
}
}
4.快速排序法(有Partition过程O(N))
选择初始基准benchmark(第1或最末位的数),一趟快排之后,左边<b,右边>b,那么b已经找好有序后的其准确位置。之后在左边、右边部分,分别快排,直到整个序列有序。
import java.util.*;
public class QuickSort {
public int[] quickSort(int[] A, int n) { //这个参数结构不足以完成 所以这里当一个中介
if(A == null || n <= 0)
return null;
Qsort(A,0,n-1);
return A;
}
public static void Qsort(int[]A , int low, int high){
int privot ;
if(low < high){
privot = Partation(A, low, high);
Qsort(A,low,privot-1);
Qsort(A,privot+1,high);
}
}
public static int Partation(int[] A, int low,int high ){
int privotkey = A[low];//标记privotkey作为本次划分参考值
while(low < high){//也就是说这个局部序列还没有排序完全,所以是while循环
while(low < high && A[high] >= privotkey){
high--;
}
swap(A,low ,high);
while (low < high && A[low] <= privotkey){
low++;
}
swap(A,low,high);//privotkey的值在交换后的low处
}
return low;
}
public static void swap(int[] A, int i, int j){
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
可能的改进:
(1)优化选取枢纽:选择privotkey,可以选取A的low,high,mid进行比较swap之后让low位置存三个的中位数,以防选择的privotkey过大或者过小,影响快排效率;
(2)优化小数组的排序:快排有递归,对于大规模的排序使用(此时递归的花销相对算法的有良性能可忽略),但是对小序列,使用插入排序效率更高。所以在quickSort函数处可以对(high-low)的值判断,已选择哪种排序方法。
(3)优化不必要的交换:在Partation函数部分,中间两个while循环结束后,因为privotkey的位置会随着swap进行多次不必要的交换,其实可以换一种方式,减少这里的内存占用。
public static int Partation(int[] A, int low,int high ){
int privotkey = A[low];//标记privotkey作为本次划分参考值
while(low < high){//也就是说这个局部序列还没有排序完全,所以是while循环
while(low < high && A[high] >= privotkey){
high--;
}
A[low] = A[high];//此时high位置被空出,原来函数此处是暂存privotkey值
while (low < high && A[low] <= privotkey){
low++;
}
A[high] = A[low];
}
A[low] = privotkey;//此时 low与high位置重合;那么安稳放置划分值的位置
return low;
}
(4)优化递归操作:在Qsort函数中,尾部有两次递归,递归多了,会影响性能。每次递归调用都会耗费一定的栈空间,所以基金可能减少递归。对Qsort实施尾递归优化。
快排最优时间复杂度是 O(nlgn)的计算:
最优情况下,Partation划分均匀,递归数的深度为{}lgN}+1前者向下取整,,遍历需要递归lgN 次,需要时间设为T(n)的话,第一次Partation需要对整个数组做一个扫描,n次,一分为二,各自Partation 需要T(n/2)的时间,那么:
T(n) <= 2T(n/2)+n ;T(1) = 0;
T(n) <= 2(2T(n/4)+n/2) +n = 4T(n/4)+2n ;
T(n) <= 4(2T(n/8)+n/4) +2n = 8T(n/8)+3n ;
......
T(n) <= nT(1) + lgn*n = nlgn
最坏情况 n-1 + n-2+n-3+... +1 = n(n-1)/2 是O(N方)
5.堆排序:一种树型排序
包括两部分a.初始堆建立(先放进二叉树,再反复删选)b.调整小根堆(输出堆顶元素即最小元素,把堆底元素送入堆顶,再调整小跟堆 将根元素与左右子树中较小的一个交换,如果该子树堆被破坏,重复前面的交换过程)
import java.util.*;
public class HeapSort {
public int[] heapSort(int[] A, int n) {
if(A == null || n == 0)
return null;
int i;
//问题1:如何由无需序列构成一个大根堆?
for( i = n/2-1;i>=0;i--){
heapAdjust(A,i,n-1);//先将待排序的 数组 构成一个大根堆,父节点大于两个子节点。
}
//问题2:现有大根堆的根节点一定是最大节点,输出到辅助数组之后,如何 重新构成大根堆 找到次大节点(新大根堆的根节点)
for( i = n-1;i > 0; i--){
swap(A,0,i);//i这里是数组A最后一个元素的位置
heapAdjust(A,0,i-1);
}
return A;
}
public static void heapAdjust(int[] A, int begin,int end){
int temp = A[begin];int j;
for( j = 2*begin +1;j<= end;j = 2*j+1){//j指向左子树编号
if(j < end && A[j] < A[j+1] ){
++j;
}
if(temp >= A[j]){
break;
}
A[begin] = A[j];
begin = j;
}
A[begin] = temp;//终于找到 begin 的准确插入位置
}
public static void swap(int[] A, int i, int j){
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
6.希尔排序:缩小增量排序
import java.util.*;
public class ShellSort {
public int[] shellSort(int[] A, int n) {
// write code here
if(A == null || A.length == 0)
return null;
int increment=n;int i,j;int temp;
do{
increment = increment/3 +1;
for(i=increment;i<A.length;i++){
if(A[i]<A[i-increment]){
temp = A[i];
for(j=i-increment;j>=0 && A[j] > temp;j-=increment){
A[j+increment]=A[j];//都不是A[i]的合适插入位置,所以这些不合适的等比例后移
}
A[j+increment]=temp;//为A[i]找到合适的插入位置
}
}
}while(increment>1);
return A;
}
}
7.归并排序:Merge Sort
把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
1 个元素的表总是有序的。所以对n 个元素的待排序列,每个元素可看成1 个有序子表。对子表两两合并生成n/2个子表,所得子表除最后一个子表长度可能为1 外,其余子表长度均为2。再进行两两合并,直到生成n 个元素按关键码有序的表
import java.util.*;
public class MergeSort {
public int[] mergeSort(int[] A, int n) {
if(A == null || A.length == 0)
return null;
Msort(A,A,0,n-1);
return A;//SR[]和A指向的是同一个地址,所以这里直接return A
}
public static void Msort(int[] A,int [] SR ,int s,int d){ //SR[]和A指向的是同一个地址
int m ;
int SR1[] = new int[A.length];
if(s == d){
SR[s]=A[s];//只有一个元素
}
else{
m = (s+d)/2;
Msort(A,SR1,s,m);//把A的[s,...m]排序保存在SR1中
Msort(A,SR1,m+1,d);//把A的[m+1,...d]排序保存在SR1中
Merge(SR,SR1,s,m,d);//把SR1的[s,...m],[m+1,...d]有序的存在SR中 必须写成SR,不可以写成A???不是指向同一个地址么
}
}
public static void Merge(int[] SR,int[] SR1,int s,int m,int d){
int i ;int j ;int k=s;int l;
for(i=s,j=m+1;i<=m && j<=d;k++){
if(SR1[i] < SR1[j])
SR[k] = SR1[i++];
else{
SR[k] = SR1[j++];
}
}
if(i<=m)
for(l=i;l<=m;l++){
SR[k++]=SR1[i++];
}
if(j<=d)
for(l=j;l<=d;l++){
SR[k++]=SR1[j++];
}
}
}
8.桶排序、基数排序
【计数排序】员工身高 170-198,给170,...,198号桶,再从桶中依次倒出,倒出顺序即为排序顺序
【基数排序】是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,他不受到 O(n log n) 下限的影响。
设有n个数,m个桶,如果数字均匀分布,那么每个桶有n/m个数字,对每个筒采用快排(O(logK))
O(n + m * n/m*log(n/m)) = O(n + nlogn - nlogm) ,如果数字都落在一个桶中,就沦为一般排序了
下面例子更高级,eg:十进制数,023,014,101,072,011
先按个位0-9号桶,在将桶倒出组成序列,再按此序列,
进入十位数0-9号桶,。。。。。。
进入百位数0-9号桶,。。。。。。