排序-Java

排序方法

平均情况

最好情况

最坏情况

辅助空间

稳定性

冒泡排序

O(n2)

O(n)

O(n2)

O(1)

稳定

简单选择排序

O(n2)

O(n2)

O(n2)

O(1)

不稳定

直接插入排序

O(n2)

O(n)

O(n2)

O(1)

稳定

希尔排序

O(nlogn)~ O(n2)

O(n1,3)

O(n2)

O(1)

不稳定

堆排序

O(nlogn)

O(nlogn)

O(nlogn)

O(1)

不稳定

归并排序

O(nlogn)

O(nlogn)

O(nlogn)

O(n)

稳定

快速排序

O(nlogn)

O(nlogn)

O(n2)

O(nlogn)~ O(n)

不稳定

1.排序算法

  链接:http://blog.csdn.net/pi9nc/article/details/1222085

1.1选择排序

  步骤:
      1、初始状态:无序区为R[1..n],有序区为空。
      2、第i趟排序(i=1,2,3...n-1)
           第i趟排序开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。
              该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,
              使R[1..i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
      3、前n-1趟结束,数组有序化了

  代码:

 1 public void selectSort(List<Integer> as,int left,int right){
 2         int N = right-left+1;
 3         for(int i=left;i<N;i++){
 4             int temp = i;
 5             for(int j=i+1;j<N;j++){
 6                 if(as.get(temp)>as.get(j)){
 7                   temp=j;
 8                 }
 9             }
10             if(temp!=i){
11                 int t = as.get(i) ;
12                 as.set(i,as.get(temp));
13                 as.set(temp, t);
14             }
15         }
16       
17     }

  分析:
      复杂度:最优:O(n^2),最差:O(n^2),平均:O(n^2)
      额外空间:O(1)
      稳定性:不稳定

1.2 插入排序

步骤:
    1、从第一个元素开始,该元素可以认为已经被排序
    2、取出下一个元素,在已经排序的元素序列中从后向前扫描
    3、如果该元素(已排序)大于新元素,将该元素移到下一位置
    4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
    5、将新元素插入到该位置后
    6、重复步骤2~5

代码:

 1 private void insertSort(List<Integer> as,int left,int right){  
 2         int N = right-left+1;
 3         for(int i=left;i<right+1;i++){
 4             int temp = as.get(i);
 5             int j=i;
 6             for(;j>left;j--){
 7                 if(temp<as.get(j-1)){
 8                     as.set(j, as.get(j-1));
 9                 }else break;
10             }
11             as.set(j, temp); 
12         }
13    }

  分析:
     复杂度:最优:O(n),最差:O(n^2),平均:O(n^2)
     额外空间:O(1)
     稳定性:稳定

1.3 希尔排序

描述:
    1、先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。
    2、所有距离为d1的倍数的记录放在同一个组中,在各组内进行直接插入排序。
    3、取第二个增量d2<d1重复上述的分组和排序,
    4、直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。

增量序列:
    1.希尔增量:ht=[n/2] hk=[hk+1/2]
    2.Knuth增量
    3.Hibbard增量
    4.Sedgewick增量

1.4 堆排序

步骤:
      数组储存成堆的形式之后,第一次将A[0]与A[n - 1]交换,
      再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n-2]交换,
      再对A[0…n-3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。
      由于每次都是将最小的数据并入到后面的有序区间,
      故操作完成后整个数组就有序了

代码:

 1 private  void duiSort(List<Integer> as){
 2   for(int i=as.size()-1;i>0;i--){
 3        int  tmp = as.get(i);
 4        as.set(i, as.get(0));
 5        as.set(0, tmp);
 6        perDown(as ,0,i);
 7    }
 8 }
 9 //建堆
10 private  void buildDui(List<Integer> as){
11    for(int i = as.size()/2;i>=0;i--){
12        perDown(as ,i,as.size()) ;
13    }
14 }
15 //下沉
16 private  void perDown(List<Integer> as ,int i,int n){
17    int child;
18    int tmp;
19    for(tmp=as.get(i);2*i+1<n;i=child){
20         child = 2*i+1;
21         if(child!=n-1&&as.get(child)<as.get(child+1)){
22              child++;
23          }
24         if(tmp<as.get(child)){
25              as.set(i,as.get(child));
26         }else break;
27    }
28    as.set(i,tmp);
29 }

  分析:
      复杂度:最优:O(nlogn),最差:O(nlogn),平均:O(nlogn)
      额外空间:O(n)

1.5 归并排序

  描述:
      1、Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
      2、Conquer: 对这两个子序列分别采用归并排序。
      3、Combine: 将两个排序好的子序列合并成一个最终的排序序列。

  代码:

 1 /**
 2 *归并排序,将输入数组array从start到end的数值进行排序
 3 *@param array 输入数值数组
 4 *@param start 须排序的起始位置
 5 *@param end 须排序的结束位置
 6 */
 7 /**
 8 *把长度为n的输入序列分成两个长度为n/2的子序列
 9 *对这两个子序列分别分割成两个一半的子序列,依次递归分割
10 *当出现有序子序列时进行回溯,将两个分别有序的子序列进行合并
11 **/
12 public static void mergeSorting(int[] array , int start,int end){
13     if(start<end){
14         int center = (start+end)/2;
15         mergeSorting(array ,start,center);
16         mergeSorting(array,center+1,end);
17         merge(array,start,center+1,end);
18     }
19 }
20 /**
21 *合并,将数组arrayd的start到center和center+1到end已经排序好的数值按顺序合并
22 *@param array 输入数值数组
23 *@param leftPos 须合并的起始位置
24 *@param rightPos 须合并的右边起始位置
25 *@param rightEnd 须合并的右边结束位置
26 */
27 /**
28 *(1)将两部分的最小值进行比较,将小者放入输入数组中记(这一部分出现新的最小值),
29 *(2)再重复(1)的步骤,
30 *(3)当其中一部分完了,另一部分依次放入输出数组
31 **/
32 public int[] merge(int[] array , int leftPos,int rightPos,int rightEnd){
33     int[] marray = new int[rightEnd-leftPos+1];
34     int tmpLeftPos = leftPos;
35     int tmpLeftEnd =rightPos-1;
36 
37     int i=0;
38     while(tmpLeftPos<=tmpLeftEnd&&rightPos<=rightEnd){
39         if(array[tmpLeftPos]<array[rightPos]){
40             marray[i++]= array[tmpLeftPos++];
41         }else{
42             marray[i++]= array[rightPos++];
43         }
44     }
45     while(tmpLeftPos<=tmpLeftEnd){
46         marray[i++]= array[tmpLeftPos++];
47     }
48     while(rightPos<=rightEnd){
49         marray[i++]= array[rightPos++];
50     }
51     for(int tmp:marray){
52         array[leftPos++] = tmp;
53     }
54     return array;
55 }

1.6 快速排序

  基本思想:
         通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,
         则可分别对这两部分记录继续进行排序,以达到整个序列有序

  步骤:
        1、从数列中挑出一个元素,称为 "基准"(pivot),
        2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。
             在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
        3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

  代码:

 1   private void swapArray(List<Integer> as,int i,int j){
 2         int tmp = as.get(i);
 3         as.set(i, as.get(j));
 4         as.set(j, tmp);
 5     }
 6     //枢纽元的选择:(三数中值分割法)左端、右端、中心位置三个元素的中值
 7     //(1):选取a[left]、a[right]、a[center]进行比较;
 8     //(2):最大者放到a[right],最小者放到a[left],中间值放到a[right-1],a[right-1]的值放到a[center]
 9     //(3):i初始为left,j初始为right-1
10     private Integer Median3(List<Integer> as,int left,int right){
11       
12         int center= (left+right)/2;
13         if(as.get(center)<as.get(left)){swapArray(as,center,left);}
14         if(as.get(right)<as.get(left)){swapArray(as,right,left);}
15         if(as.get(right)<as.get(center)){swapArray(as,right,center);}
16        
17         swapArray(as,right-1,center);
18         return as.get(right-1);
19     }
20     private void quickSort(List<Integer> as,int left,int right){
21         int DING =10;
22         if(left+DING<=right){
23             int key =  Median3( as,left,right);
24            
25             int i =left,j=right-1;
26            
27             while(true){
28                 while(as.get(++i)<key){}
29                 while(as.get(--j)>key){}
30                 if(i<j) swapArray( as,i,j);
31                 else break;
32             }
33            
34             swapArray( as,i,right-1);
35            
36             quickSort(as,left,i-1) ;
37             quickSort(as,i+1,right) ;
38            
39         }else{
40             //当数组元素较少时采用插入排(截止范围N=10)
41             insertSort(as,left,right);
42         }
43     }

1.7 桶排序

  描述:
        1、设置一个定量的数组当作空桶子。
        2、寻访串行,并且把项目一个一个放到对应的桶子去。
        3、对每个不是空的桶子进行排序。
        4、从不是空的桶子里把项目再放回原来的串行中。

2.最长公共子序列

  http://www.cnblogs.com/huangxincheng/archive/2012/11/11/2764625.html    
  http://wenku.baidu.com/view/9494d24be45c3b3567ec8bf9.html

(1)子序列不见得一定是连续的,子串要求一定是连续的;

动态规划:
    第一步:先计算最长公共子序列的长度。
    第二步:根据长度,然后通过回溯求出最长公共子序列。
    现有两个序列X={x1,x2,x3,...xi},Y={y1,y2,y3,....,yi},
    设一个C[i,j]: 保存Xi与Yj的LCS的长度。    
    递推方程为:
        1. 当i=0||j=0,则C[i][j]=0;
        2. 当i,j>0&Xi=Yi,则C[i][j]=C[i-1][j-1]+1;
        3. 当i,j>0&Xi!=Yi,则C[i][j]=C[i-1][j]>C[i][j-1]?C[i-1][j]:C[i][j-1];
    采用一个标记函数Flag[i,j],当
        ①:C[i,j]=C[i-1,j-1]+1  时 标记Flag[i,j]="left_up";    (左上方箭头)
        ②:C[i-1,j]>=C[i,j-1]   时 标记Flag[i,j]="left";       (左箭头)
        ③: C[i-1,j]<C[i,j-1]    时 标记Flag[i,j]="up";         (上箭头)

代码:

 1 /**
 2   *  
 3   * @param str1 
 4   * @param n1
 5   * @param str2
 6   * @param n2
 7   * @return
 8   */
 9   int[][] LCS;
10   String[][] Flag;
11   ArrayList<Integer> WN = new ArrayList<Integer>();
12   ArrayList<Integer> WM= new ArrayList<Integer>();
13   
14   public void getLCS(String str1,int n1,String str2,int n2){
15       char[] ctr1 = str1.toCharArray();
16       char[] ctr2 = str2.toCharArray();
17       LCS=new int[n1+1][n2+1];
18       Flag=new String[n1+1][n2+1];
19       for(int i=0;i<=n1;i++){
20           for(int j=0;j<=n2;j++){
21               if(i==0||j==0){
22                   LCS[i][j]=0;
23                  
24               }else{
25                   if(ctr1[i-1]==ctr2[j-1]){
26                         LCS[i][j]=LCS[i-1][j-1]+1;
27                         Flag[i][j] = "left_up";
28                     }
29                     else{
30                         if(LCS[i-1][j]>=LCS[i][j-1]){
31                             LCS[i][j]=LCS[i-1][j];
32                             Flag[i][j] = "left";
33                         }else{
34                             LCS[i][j]=LCS[i][j-1];
35                             Flag[i][j] = "up";
36                         }
37                     }
38               }
39           }
40       } 
41   }
42   
43   public void SubSequence(int i, int j){
44     
45       if(i==0||j==0){
46           return ;
47       }
48       if("left_up".equals(Flag[i][j])){
49          WN.add(i-1);
50          WM.add(j-1);
51          SubSequence(i - 1, j - 1);
52       }else{
53           if("up".equals(Flag[i][j])){
54               SubSequence(i, j - 1);
55           }else{
56               SubSequence(i - 1, j);
57           }
58       }
59   }
60   public String getSubString(String str1,String str2){
61       String str ="";
62       for(int i=0;i<WN.size();i++){
63           str = str+str1.charAt(WN.get(i));
64           System.out.println(WN.get(i)+" "+WM.get(i));
65       }
66       System.out.println(str);
67       return str;
68   }

3.字符串相似度

  http://www.cnblogs.com/huangxincheng/archive/2012/11/11/2765633.html

  跟“最长公共子序列”一样,我们采用一个二维数组来保存字符串X和Y当前的位置的最小编辑距离。
  现有两个序列X={x1,x2,x3,...xi},Y={y1,y2,y3,....,yi},
  设一个C[i,j]: 保存Xi与Yj的当前最小的LD。
      ①: 当 Xi = Yi 时,则C[i,j]=C[i-1,j-1];
      ②:当 Xi != Yi 时, 则C[i,j]=Min{C[i-1,j-1],C[i-1,j],C[i,j-1]}+1;
  最终我们的C[i,j]一直保存着最小的LD

  代码:

 1 public static int getLCS(String str1,int n1,String str2,int n2){
 2       char[] ctr1 = str1.toCharArray();
 3       char[] ctr2 = str2.toCharArray();
 4       int[][] LCS=new int[n1+1][n2+1];
 5       for(int i=0;i<=n1;i++){
 6           for(int j=0;j<=n2;j++){
 7               if(i==0||j==0){
 8                   LCS[i][j]=0;
 9               }else{
10                   if(ctr1[i-1]==ctr2[j-1]){
11                         LCS[i][j]=LCS[i-1][j-1];
12                     }
13                     else{
14                         int tmp = LCS[i-1][j]>LCS[i][j-1]?LCS[i][j-1]:LCS[i-1][j];
15                         LCS[i][j] = (tmp>LCS[i-1][j-1]?LCS[i-1][j-1]:tmp) +1;
16                     }
17               }
18           }
19       } 
20       return LCS[n1][n2];
21   }

4.数组分割

  问题:无序、元素2N的正整数数组,将数组分割为两个元素个数为N的数组,使两个数组的和接近

1.元素有负数;
2.分割的数组不是N个而是任意个;
3.两个数组:
  a.和接近;
  b.接近总和的一半;
  c.两个数组和的差为一定值。
4.两个数组a、b,大小都为n,数组元素的值任意整形数,无序;
   要求:通过交换a、b数组中的元素,使[数组a元素的和]与[数组b元素的和]之间的差最小

 1 void shuzudis(int[] arry){
 2         int N = arry.length;
 3         int sum =0;
 4         int min=arry[0];
 5         //找出最小值
 6         for(int i=0;i<N;i++){
 7             min = min>arry[i]?arry[i]:min;
 8         }
 9         //如果有负值,则整体都加上减去最小值,进行正数化
10         if(min<0){
11             for(int i=0;i<N;i++){
12                 arry[i]-=min;
13                 sum+=arry[i];
14             }
15         }
16         //isOK[i][v]表示任意i个元素之和是否能够为v
17         //初始化
18         boolean[][] isOK = new boolean[N+1][sum+1];
19         for(int i=0;i<N+1;i++){
20             for(int j=0;j<sum+1;j++){
21                 isOK[i][j]=false;
22             }
23         }
24         isOK[0][0]=true;
25         //重点
26         for(int i=0;i<N;i++){
27             for(int j=i+1;j>0;j--){
28                 for(int v=0;v<sum+1;v++){
29                     if(v>=arry[i]&&isOK[j-1][v-arry[i]]){
30                         isOK[j][v]=true;
31                     }
32                 }
33             }
34         }
35         //显示
36         for(int i=0;i<N+1;i++){
37             for(int j=0;j<sum+1;j++){
38                 System.out.print(isOK[i][j]+" ");
39             }
40             System.out.println();
41         }
42     }

5.5/3数组分割

  问题:编写一个函数,传入一个int型数组,返回该数组能否分成两组,
        使得两组中各元素加起来的和相等,并且,所有5的倍数必须在
        其中一个组中,所有3的倍数在另一个组中(不包括5的倍数),
        能满足以上条件,返回true;不满足时返回fals

 1 import java.util.*;
 2 
 3 public class Main {  
 4     public static void main(String[] args) {
 5         Scanner sc = new Scanner(System.in);
 6         while(sc.hasNext()){
 7             int n = sc.nextInt();
 8             int sum1 = 0, sum2 = 0;
 9             int[] a = new int[n];
10             int count = 0;
11             for(int i =0;i<n;i++){
12                 int tmp = sc.nextInt();
13                 if(tmp%5==0){
14                     sum1+=tmp;
15                 }
16                 else if(tmp%3==0)
17                     sum2 += tmp;
18                 else{
19                     a[count++] = tmp;
20                 }
21             }
22             int sum = Math.abs(sum1-sum2);//这里只考虑绝对值就可以了
23             System.out.println(f(0,count,a,0,sum));
24         }
25     }
26  
27     public static boolean f(int i ,int n,int[] a,int result,int sum){
28         if(i==n){
29             return Math.abs(result) == sum;//绝对值相等就可以
30         }
31         else{
32             return (f(i+1,n,a,result+a[i],sum)||f(i+1,n,a,result-a[i],sum));
33         }
34     }
原文地址:https://www.cnblogs.com/ljb161108/p/7144527.html