八大排序算法总结(Java实现)

排序的介绍:排序是将一组数据,依指定的顺序进行排列的过程。


排序的分类

1内部排序法:指将需要处理的所有数据都加载到内部存储器中进行排序。包括(交换式排序法、选择式排序法和插入式排序法)

2外部排序法数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(归并排序法和直接合并排序法)

    交换式排序法可分为两种:1、冒泡排序法(Bubble Sorting)2、快速排序法(Quick Sorting)

  选择式排序法可分为两种:1、选择排序法(Selection Sorting)2、堆排序法(Heap Sorting)

  插入式排序法可分为三种:1、插入排序法(Insertion Sorting)2、希尔排序法(Shell Sorting)3、二叉树排序法(Binary-tree Sorting)

  其他排序方法:1、桶排序/基数排序(RadixSort)2、归并排序(Merge Sort),当数据规模变得很大时,归并排序可用来做外部排序算法,合并两个已经排序好的文件。



内部排序法

交换式排序法

交换式排序属于内部排序法,是运用数据值比较后,依判断规则对数据位置进行交换,以达到排序的目的。

冒泡排序法:冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从后向前(从下标较大的元素开始)依次比较相邻元素的排序码,若发现逆序则交换使排序码较小的元素逐渐从后部移向前部(从下标较大的单元移向下标较小的单元),就象水底下的气泡一样逐渐向上冒。(其实也可以从前往后,较大的元素一直下沉)

因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。


冒泡排序实现:

package com.zxt.sort;
 
publicclassBubbleSort {
   
   publicstaticvoid main(String[]args) {
       intarr[] = { 49, 38, 65, 97,76, 13, 27, 49 };
       inttemp = 0;
       //输出初始数组
        System.out.print("初始数组:");
       for (inti = 0;i <arr.length;i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
 
       //外层循环,可以决定一共走趟
       for (inti = 0;i <arr.length - 1; i++) {
           //内层循环,开始逐个比较,如果发现前一个数比后一个数大则交换
           for (intj = 0;j <arr.length - 1 - i;j++) {
               //换位
               if (arr[j] > arr[j + 1]) {
                   temp =arr[j];
                   arr[j] = arr[j + 1];
                   arr[j + 1] = temp;
                }
            }
           
           //输出中间结果
            System.out.printf("第%d躺排序:",i);
           for (intk = 0;k <arr.length;k++) {
                System.out.print(arr[k] + "\t");
            }
            System.out.println();
        }
       
       //输出最后结果
        System.out.print("最终结果:");
       for (inti = 0;i <arr.length;i++) {
            System.out.print(arr[i] + "\t");
        }
    }
}



快速排序法:快速排序(Quick Sorting)是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。


快速排序实现:

package com.zxt.sort;
 
publicclassQuickSort {
   privatestaticintfrequen = 1;
   
   publicstaticvoid main(String[]args) {
       intarr[] = { 49, 38, 65, 97,76, 13, 27, 49 };
       //输出初始数组
        System.out.print("初始数组:");
       for (inti = 0;i <arr.length;i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
               
       quick_sort(0,arr.length - 1, arr);
       
       //输出最后结果
        System.out.print("最终结果:");
       for (inti = 0;i <arr.length;i++) {
            System.out.print(arr[i] + "\t");
        }
    }
 
   publicstaticvoid quick_sort(intleft,intright,int[]arr) {
       intl =left;
       intr =right;
       //找中间位置的值作为基数
       intpivot =arr[(left + right) / 2];
       inttemp = 0;
       
       while (l <r) {
           while (arr[l] < pivot) {
               l++;
            }
           while (arr[r] > pivot) {
               r--;
            }
           if (l >=r) {
               break;
            }
           temp =arr[l];
           arr[l] = arr[r];
           arr[r] = temp;
           if (arr[l] == pivot) {
                --r;
            }
           if (arr[r] == pivot) {
                ++l;
            }
        }
       
       //输出中间结果
        System.out.printf("第%d躺排序:",frequen);
       for (intk = 0;k <arr.length;k++) {
            System.out.print(arr[k] + "\t");
        }
        System.out.printf("(%d)\n",pivot);
       frequen++;
       
       if (l ==r) {
           l++;
           r--;
        }
       if (left <r) {
           quick_sort(left,r,arr);
        }
       if (right >l) {
           quick_sort(l,right,arr);
        }
    }
}



选择式排序法

选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,经过和其他元素重整,再依原则交换位置后达到排序的目的。

选择排序法:选择排序(Select Sorting)也是一种简单的排序方法。它的基本思想是:第一次从R[0]-R[n-1]中选取最小值,与R[0]交换,第二次从R[1]-R[n-1]中选取最小值,与R[1]交换,第三次从R[2]-R[n-1]中选取最小值,与R[2]交换...,第i次从R[i-1]-R[n-1]中选取最小值,与R[i-1]交换,...,第n-1次从R[n-2]-R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。


选择排序实现:

package com.zxt.sort;
 
publicclassSelectSort {
   publicstaticvoid main(String[]args) {
       intarr[] = { 49, 38, 65, 97,76, 13, 27, 49 };
       inttemp = 0;
       //输出初始数组
        System.out.print("初始数组:");
       for (inti = 0;i <arr.length;i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
 
       for (intj = 0;j <arr.length - 1; j++) {
           //认为第一个数就是最小数
           intmin =arr[j];
           //记录最小数的下标
           intminIndex =j;
           for (intk =j + 1;k <arr.length;k++) {
               if (min >arr[k]) {
                   //修改最小值
                   min =arr[k];
                   minIndex =k;
                }
            }
           //当退出for循环时就找到这次的最小值
           temp =arr[j];
           arr[j] = arr[minIndex];
           arr[minIndex] = temp;
 
           //输出中间结果
            System.out.printf("第%d躺排序:",j);
           for (intk = 0;k <arr.length;k++) {
                System.out.print(arr[k] + "\t");
            }
            System.out.println();
        }
 
       //输出最后结果
        System.out.print("最终结果:");
       for (inti = 0;i <arr.length;i++) {
            System.out.print(arr[i] + "\t");
        }
    }
}




 堆排序法(直接选择排序的改进):将排序码k1,k2,k3,...,kn表示成一棵完全二叉树,然后从第n/2个排序码开妈筛选,使由该结点组成的子二叉树符合堆的定义,然后从第n/2-1个排序码重复刚才操作,直到第一个排序码止,这时候,该二叉树符合堆的定义,初始堆已经建立。

        接着,可以按如下方法进行堆排序:将堆中第一个结点(二叉树根结点)和最后一个结点的数据进行交换(k1kn),再将k1--kn-1重新建堆,然后k1kn-1交换,再将k1--kn-2重新建堆,然后k1kn-2交换,如此重复下去,每次重新建堆的元素个数不断减1,直到重新建堆的元素个数仅剩一个为止。这时堆排序已经完成,则排序码k1,k2,k3,...kn已排成一个有序序列。

        若排序是从小到大排列,则可以建立大根堆实现堆排序,若排序是从大到小排列,则可以用建立小根堆实现堆排序。

        相关概念:堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足:

    

   时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆,或最大项,则对应为大顶堆)。若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:

   (a)大顶堆序列:(96, 83,27,38,11,09)

   (b)小顶堆序列:(1236248547305391

   初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序

因此,实现堆排序需解决两个问题:

1.如何将n个待排序的数建成堆;

2.输出堆顶元素后,怎样调整剩余n-1个元素,使其成为一个新堆。

首先建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。

1n个结点的完全二叉树,则最后一个结点是第n/2个结点的子树。

2)筛选从第n/2个结点为根的子树开始,该子树成为堆。

3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。

如图建堆初始过程:无序序列:(4938659776132749


然后讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。调整小顶堆的方法:

1)设有m个元素的堆,输出堆顶元素后,剩下m-1个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。

2)将根结点与左、右子树中较小元素的进行交换。小顶堆是小元素在上面,所以与较小的元素交换,大顶堆是较大的元素在上面,所以与较大的元素交换)。

3)若与左子树交换,如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法(2.

4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2.

5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

称这个自根结点到叶子结点的调整过程为筛选。如图:


堆排序实现:

package com.zxt.sort;
 
publicclassHeapSort {
 
   publicstaticvoid main(String[]args) {
       inta[] = { 49, 38, 65, 97,76, 13, 27, 49 };
        HeapSortobj = new HeapSort();
       
        System.out.print("初始数组:");
       obj.print(a);
 
       for (inti = 0;i <a.length;i++) {
           //创建堆,创建的是大顶堆。每次循环完,二叉树的根节点都是最大值,所以与此时的未排好部分最后一个值交换位置
           obj.createLittleHeap(a,a.length - 1 - i);
           //与最后一个值交换位置,最大值找到了位置
           obj.swap(a, 0, a.length - 1 - i);
            System.out.printf("第%d躺排序:",i);
           obj.print(a);
        }
 
        System.out.print("最终结果:");
       obj.print(a);
    }
 
   //创建大顶堆(升序排序):双亲节点大于子节点的值。从叶子节点开始,直到根节点。这样建立的堆定位最大值
   privatevoid createLittleHeap(int[]data,intlast) {
       //找到最后一个叶子节点的双亲节点
       for (inti =last / 2;i >= 0;i--) {
           //保存当前正在判断的节点
           intparent =i;
           //若当前节点的左子节点存在,即子节点存在
           while (2 *parent + 1 <=last) {
               //由于创建大顶堆,所以大的元素要换上来,biggerIndex总是记录较大节点的值,先赋值为当前判断节点的左子节点
               intbigger = 2 *parent + 1;
               
               //说明存在右子节点
               if (bigger <last) {
                   //左子节点  <右子节点时
                   if (data[bigger] < data[bigger + 1]) {
                       bigger =bigger + 1;
                    }
                }
               
               //若双亲节点值小于于子节点中较大的,则交换2者得值(创建大顶堆,大的元素要上来)
               if (data[parent] < data[bigger]) {
                    swap(data,parent,bigger);
                   parent =bigger;
                }else{
                   break;
                }
            }
        }
    }
 
   publicvoid print(inta[]) {
       for (inti = 0;i <a.length;i++) {
            System.out.print(a[i] + "\t");
        }
        System.out.println();
    }
 
   publicvoid swap(int[]data,inti,intj) {
       if (i ==j) {
           return;
        }
       data[i] = data[i] + data[j];
       data[j] = data[i] - data[j];
       data[i] = data[i] - data[j];
    }
}



插入式排序法

插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。

插入排序:插入排序(InsertionSorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始有序表只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。

插入排序实现:

package com.zxt.sort;
 
publicclassInsertionSort {
   publicstaticvoid main(String[]args) {
       int[]arr = { 49, 38, 65, 97, 76,13, 27, 49 };
       //输出初始数组
        System.out.print("初始数组:");
       for (inti = 0;i <arr.length;i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
 
       //从第1个元素开始循环,第0个看成是已经排序好的有序表
       for (inti = 1;i <arr.length;i++) {
           intinsertVal =arr[i];
           // insertVal准备和前一个数比较
           intindex =i - 1;
           while (index >= 0 &&insertVal <arr[index]) {
               //将把arr[index]向后移动一位
               arr[index + 1] = arr[index];
               //让index向前移动一位
               index--;
            }
 
           //将insertVal插入到适当位置
           arr[index + 1] = insertVal;
 
           //输出中间结果
            System.out.printf("第%d躺排序:",i);
           for (intk = 0;k <arr.length;k++) {
                System.out.print(arr[k] + "\t");
            }
            System.out.println();
        }
 
       //输出最后结果
        System.out.print("最终结果:");
       for (inti = 0;i <arr.length;i++) {
            System.out.print(arr[i] + "\t");
        }
    }
}

 


希尔排序法:希尔排序(ShellSorting)又称为“缩小增量排序”。该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。

因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。


希尔排序实现:

package com.zxt.sort;
 
publicclassShellSort {
   
   publicstaticvoid main(String[]args) {
       int[]arr = { 49, 38, 65, 97, 76,13, 27, 49 };
       //输出初始数组
        System.out.print("初始数组:");
       for (inti = 0;i <arr.length;i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
       
       //类似插入排序,只是插入排序增量是1,这里增量是dk,把1换成dk就可以了
       intdk =arr.length / 2;
       while (dk >= 1) {
           
           //从第dk个元素开始循环
           for (inti =dk;i <arr.length;i++) {
               intinsertVal =arr[i];
               // insertVal准备和前一个数比较
               intindex =i -dk;
               while (index >= 0 &&insertVal <arr[index]) {
                   //将把arr[index]向后移动dk位
                   arr[index + dk] =arr[index];
                   //让index向前移动dk位
                   index =index -dk;
                }
 
               //将insertVal插入到适当位置
               arr[index + dk] =insertVal;
               
               //输出中间结果
                System.out.printf("dk =%d:",dk);
               for (intk = 0;k <arr.length;k++) {
                    System.out.print(arr[k] + "\t");
                }
                System.out.println();
            }
           
           dk =dk / 2;
        }
 
       //输出最后结果
        System.out.print("最终结果:");
       for (inti = 0;i <arr.length;i++) {
            System.out.print(arr[i] + "\t");
        }
    }
}



 

  二叉树排序法:二分插入排序(BinaryInsert Sorting)的基本思想是:在有序表中采用二分查找的方法查找待排元素的插入位置。

        其处理过程:先将第一个元素作为有序序列,进行n-1次插入,用二分查找的方法查找待排元素的插入位置,将待排元素插入。


二分排序实现:

package com.zxt.sort;
 
publicclassBinarySort {
 
   publicstaticvoid main(String[]args) {
       int[]arr = { 49, 38, 65, 97, 76,13, 27, 49 };
        BinarySortbs = new BinarySort();
       //输出初始数组
        System.out.print("初始数组:");
       bs.print(arr);
 
       //从第1个元素开始循环,第0个看成是已经排序好的有序表
       for (inti = 1;i <arr.length;i++) {
           //在有序表中找出待排序元素要插入的位置
           intinsertindex =bs.binaryFind(0,i - 1,arr[i],arr);
           bs.insertvalue(insertindex,i,arr[i],arr);
 
           //输出中间结果
            System.out.printf("第%d躺排序:",i);
           bs.print(arr);
        }
 
       //输出最后结果
        System.out.print("最终结果:");
       bs.print(arr);
    }
 
   //二分查找法
   publicint binaryFind(intleftIndex,intrightIndex,intval,int[]arr) {
       //首先找到中间的数
       intmidIndex = ((rightIndex +leftIndex) / 2);
       intmidVal =arr[midIndex];
 
       if (rightIndex >=leftIndex) {
           //如果要找的数比midVal大
           if (midVal >val) {
               //在arr数组左边数列中找
               return binaryFind(leftIndex,midIndex - 1,val,arr);
            }elseif (midVal <val) {
               //在arr数组右边数列中找
               return binaryFind(midIndex + 1,rightIndex,val,arr);
            }elseif (midVal ==val) {
               returnmidIndex + 1;
            }
        }else{
           returnleftIndex;
        }
 
       return -1;
    }
 
   //插入排序,数组分成有序表和无序表,插入只是在有序表中,last表示有序表的边界
   //如果是在整个数组中插入,则last = arr.length-1
   publicvoid insertvalue(intindex,intlast,intvalue,int[]arr) {
       if (last >arr.length - 1) {
            System.out.println("out of index!");
           return;
        }
 
       inti =last - 1;
       while (index <=i) {
           arr[i + 1] = arr[i];
           i--;
        }
       arr[i + 1] = value;
    }
 
   publicvoid print(int[]arr) {
       for (intk = 0;k <arr.length;k++) {
            System.out.print(arr[k] + "\t");
        }
        System.out.println();
    }
}

 


其他排序方法

        基数排序/桶排序:Radix Sort

        桶排序:假设有一组长度为N的待排关键字序列K[1....n]。首先将这个序列划分成M个的子区间()。然后基于某种映射函数,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i),那么该关键字k就作为B[i]中的元素(每个桶B[i]都是一组大小为N/M的序列)。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排、或者其他排序算法)。然后依次枚举输出B[0]....B[M]中的全部内容即是一个有序序列。

例如待排序列K= {49 38 35 97 76 73 27 49 }。这些数据全部在1100之间。因此我们定制10个桶,然后确定映射函数f(k)=k/10。则第一个关键字49将定位到第4个桶中(49/10=4)。依次将所有关键字全部堆入桶中,并在每个非空的桶中进行快速排序。

桶排序代价分析:桶排序利用函数的映射关系,减少了几乎所有的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块()。然后只需要对桶中的少量数据做先进的比较排序即可。

N个关键字进行桶排序的时间复杂度分为两个部分:

(1)循环计算每个关键字的桶映射函数,这个时间复杂度是O(N)

(2)利用先进的比较排序算法对每个桶内的所有数据进行排序,其时间复杂度为∑O(Ni*logNi)。其中Ni为第i个桶的数据量。

很显然,第(2)部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的最好平均时间复杂度只能达到O(N*logN))。因此,我们需要尽量做到下面两点:

(1)映射函数f(k)能够将N个数据平均的分配到M个桶中,这样每个桶就有[N/M]个数据量。

(2)尽量的增大桶的数量。极限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。 当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。

       对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:

O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)

N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)

   总结:桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。当然桶排序的空间复杂度为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。

 


       基数排序:分配排序的基本思想:说白了就是进行多次的桶式排序。基数排序过程无须比较关键字,而是通过“分配”和“收集”过程来实现排序。它们的时间复杂度可达到线性阶:O(n)

实例:扑克牌中52张牌,可按花色和面值分成两个字段,其大小关系为:

花色:梅花<方块<红心 <黑心 

面值:2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < J< Q < K < A

      若对扑克牌按花色、面值进行升序排序,得到如下序列:


即两张牌,若花色不同,不论面值怎样,花色低的那张牌小于花色高的,只有在同花色情况下,大小关系才由面值的大小确定(因此花色是主关键码)。这就是多关键码排序。

为得到排序结果,我们讨论两种排序方法。

方法1:先对花色排序,将其分为4个组,即梅花组、方块组、红心组、黑心组。再对每个组分别按面值进行排序,最后,将4个组连接起来即可。

方法2:先按13个面值给出13个编号组(2号,3号,...A),将牌按面值依次放入对应的编号组,分成13堆。再按花色给出4个编号组(梅花、方块、红心、黑心),将2号组中牌取出分别放入对应花色组,再将3号组中牌取出分别放入对应花色组,……,这样,4个花色组中均按面值有序,然后,将4个花色组依次连接起来即可。

两种多关键码排序方法:多关键码排序按照从最主位关键码到最次位关键码或从最次位到最主位关键码的顺序逐次排序,分两种方法:

最高位优先(Most Significant Digit first)法,简称MSD法:

1)先按k1排序分组,将序列分成若干子序列,同一组序列的记录中,关键码k1相等。

2)再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。

3)再将各组连接起来,便得到一个有序序列。扑克牌按花色、面值排序中介绍的方法一即是MSD法。

最低位优先(Least Significant Digit first)法,简称LSD法:

1)先从kd开始排序,再对kd-1进行排序,依次重复,直到按k1排序分组分成最小的子序列后。

2)最后将各个子序列连接起来,便可得到一个有序的序列,扑克牌按花色、面值排序中介绍的方法二即是LSD法。


基数排序:是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

上面的问题是多关键字的排序,但单关键字也仍然可以使用这种方式。

比如字符串“abcd”“aesc"dwsc""rews"就可以把每个字符看成一个关键字。另外还有整数425321235432也可以每个位上的数字为一个关键字。基数排序的思想就是将待排数据中的每组关键字依次进行桶分配。比如下面的待排序列:

278109063930589184505269008083

我们将每个数值的个位,十位,百位分成三个关键字:278 -> k1(个位)=8k2(十位)=7k3=(百位)=2。然后从最低位个位开始(从最次关键字开始),对所有数据的k1关键字进行桶分配(因为,每个数字都是0-9的,因此桶大小为10),再依次输出桶中的数据得到下面的序列。

930063083184505278008109589269

再对上面的序列接着进行针对k2的桶分配,输出序列为:

505008109930063269278083184589

最后针对k3的桶分配,输出序列为:

008063083109184269278505589930

 

性能分析:很明显,基数排序的性能比桶排序要略差。每一次关键字的桶分配都需要O(N)的时间复杂度,而且分配之后得到新的关键字序列又需要O(N)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2N),当然d要远远小于N,因此基本上还是线性级别的。基数排序的空间复杂度为O(N+M),其中M为桶的数量。一般来说N>>M,因此额外空间需要大概N个左右。

但是,对比桶排序,基数排序每次需要的桶的数量并不多。而且基数排序几乎不需要任何“比较”操作,而桶排序在桶相对较少的情况下,桶内多个数据必须进行基于比较操作的排序。因此,在实际应用中,基数排序的应用范围更加广泛。




外部排序法

合并排序法(归并排序):合并排序法(MergeSorting)是外部排序最常使用的排序方法。若数据量太大无法一次完全加载内存,可使用外部辅助内存来处理排序数据,主要应用在文件排序。

将欲排序的数据分别存在数个文件大小可加载内存的文件中,再针对各个文件分别使用“内部排序法”将文件中的数据排序好写回文件。再对所有已排序好的文件两两合并,直到所有文件合并成一个文件后,则数据排序完成。


归并排序实现:

package com.zxt.sort;
 
publicclassMergeSort {
   
   publicstaticvoid main(String[]args) {
        MergeSortms = new MergeSort();
       int[]arr = { 49, 38, 65, 97, 76,13, 27, 49 };
       
       ms.merge_sort(arr, 0, arr.length - 1);
    }
 
   // 递归分成小部分
   publicvoid merge_sort(int[]arrays,intstart,intend) {
       if (start < end) {
           intm = (start + end) / 2;
            merge_sort(arrays,start,m);
            merge_sort(arrays,m + 1,end);
            combin_arrays(arrays,start,m,end);
           
            print(arrays);
        }
    }
 
   // 合并数组
   publicvoid combin_arrays(int[]arrays,intstart,intm,intend) {
       intlength =end -start + 1;
       // 用来存放比较的数组,用完复制回到原来的数组
       inttemp[] =newint[length];
       inti =start;
       intj =m + 1;
       intc = 0;
       
       while (i <= m &&j <=end) {
           if (arrays[i] < arrays[j]) {
               temp[c++] = arrays[i++];
            }else{
               temp[c++] = arrays[j++];
            }
        }
       
       while (i <= m) {
           temp[c++] = arrays[i++];
        }
       while (j <= end) {
           temp[c++] = arrays[j++];
        }
       
       c = 0;
       for (intt =start;t <=end;t++) {
           arrays[t] = temp[c++];
        }
    }
   // 打印数组
   publicvoid print(int[]arrays) {
       for (inti = 0;i <arrays.length;i++) {
            System.out.print(arrays[i] + "\t");
        }
        System.out.println();
    }
}



查找

常用的查找方式有两种:1、顺序查找(最简单,效率最低)2、二分查找。


二分查找:

package com.zxt.sort;
 
import java.util.Scanner;
 
publicclassBinarySeach {
 
   publicstaticvoid main(String[]args) {
       // 定义arr数组并赋值
       intarr[] = { 2, 5, 7, 12, 25};
 
        System.out.print("请输入你需要查找的数:");
        Scannersr =new Scanner(System.in);
       inta =sr.nextInt();
 
        BinarySeachbf = new BinarySeach();
       bf.binaryFind(0,arr.length - 1, a,arr);
    }
 
   // 二分查找法
   publicvoid binaryFind(intleftIndex,intrightIndex,intval,intarr[]) {
       // 首先找到中间的数
       intmidIndex = ((rightIndex + leftIndex) / 2);
       intmidVal =arr[midIndex];
       
       if (rightIndex >= leftIndex) {
           // 如果要找的数比midVal大
           if (midVal > val) {
               // 在arr数组左边数列中找
                binaryFind(leftIndex,midIndex - 1,val,arr);
            }elseif (midVal < val) {
               // 在arr数组右边数列中找
                binaryFind(midIndex + 1, rightIndex,val,arr);
            }elseif (midVal == val) {
                System.out.println("数组arr[" + midIndex +"]中的数字是" + arr[midIndex]);
            }
        }else{
            System.out.println("没有找到你要找的数!");
        }
    }
}


总结

各种排序的稳定性,时间复杂度和空间复杂度总结


 



时间复杂度函数O(n)的增长情况

所以对n较大的排序记录。一般的选择都是时间复杂度为O(nlog2n)的排序方法。

时间复杂度

(1)平方阶(O(n2))排序:各类简单排序:直接插入、直接选择和冒泡排序;

(2)线性对数阶(O(nlog2n))排序:快速排序、堆排序和归并排序;

(3)O(n1+§))排序,§是介于01之间的常数:希尔排序;

(4)线性阶(O(n))排序:基数排序,此外还有桶、箱排序。

说明:当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至On);

而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为On2);

原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。


稳定性

排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序,这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对次序发生了改变,则称该算法是不稳定的。

稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以避免多余的比较;

稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序;

不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。


选择排序算法准则

每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。

影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:

1.待排序的记录数目n的大小;

2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;

3.关键字的结构及其分布情况;

4.对排序稳定性的要求。



源代码下载

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用Java实现八大排序算法的示例代码: 1. 冒泡排序: ```java public class BubbleSort { public static void bubbleSort(int[] arr) { int n = arr.length; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } public static void main(String[] args) { int[] arr = {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}; bubbleSort(arr); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } } ``` 2. 选择排序: ```java public class SelectionSort { public static void selectionSort(int[] arr) { int n = arr.length; for (int i = 0; i < n - 1; i++) { int minIndex = i; for (int j = i + 1; j < n; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; } } int temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } } public static void main(String[] args) { int[] arr = {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}; selectionSort(arr); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } } ``` 3. 插入排序: ```java public class InsertionSort { public static void insertionSort(int[] arr) { int n = arr.length; for (int i = 1; i < n; i++) { int key = arr[i]; int j = i - 1; while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; j--; } arr[j + 1] = key; } } public static void main(String[] args) { int[] arr = {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}; insertionSort(arr); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } } ``` 4. 希尔排序: ```java public class ShellSort { public static void shellSort(int[] arr) { int n = arr.length; for (int gap = n / 2; gap > 0; gap /= 2) { for (int i = gap; i < n; i++) { int temp = arr[i]; int j = i; while (j >= gap && arr[j - gap] > temp) { arr[j] = arr[j - gap]; j -= gap; } arr[j] = temp; } } } public static void main(String[] args) { int[] arr = {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}; shellSort(arr); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } } ``` 5. 归并排序: ```java public class MergeSort { public static void mergeSort(int[] arr, int left, int right) { if (left < right) { int mid = (left + right) / 2; mergeSort(arr, left, mid); mergeSort(arr, mid + 1, right); merge(arr, left, mid, right); } } public static void merge(int[] arr, int left, int mid, int right) { int n1 = mid - left + 1; int n2 = right - mid; int[] L = new int[n1]; int[] R = new int[n2]; for (int i = 0; i < n1; ++i) { L[i] = arr[left + i]; } for (int j = 0; j < n2; ++j) { R[j] = arr[mid + 1 + j]; } int i = 0, j = 0; int k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } public static void main(String[] args) { int[] arr = {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}; mergeSort(arr, 0, arr.length - 1); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } } ``` 6. 快速排序: ```java public class QuickSort { public static void quickSort(int[] arr, int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } } public static int partition(int[] arr, int low, int high) { int pivot = arr[high]; int i = low - 1; for (int j = low; j < high; j++) { if (arr[j] < pivot) { i++; int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } int temp = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = temp; return i + 1; } public static void main(String[] args) { int[] arr = {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}; quickSort(arr, 0, arr.length - 1); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } } ``` 7. 堆排序: ```java public class HeapSort { public static void heapSort(int[] arr) { int n = arr.length; for (int i = n / 2 - 1; i >= 0; i--) { heapify(arr, n, i); } for (int i = n - 1; i >= 0; i--) { int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; heapify(arr, i, 0); } } public static void heapify(int[] arr, int n, int i) { int largest = i; int left = 2 * i + 1; int right = 2 * i + 2; if (left < n && arr[left] > arr[largest]) { largest = left; } if (right < n && arr[right] > arr[largest]) { largest = right; } if (largest != i) { int swap = arr[i]; arr[i] = arr[largest]; arr[largest] = swap; heapify(arr, n, largest); } } public static void main(String[] args) { int[] arr = {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}; heapSort(arr); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } } ``` 8. 计数排序: ```java public class CountingSort { public static void countingSort(int[] arr) { int n = arr.length; int max = Arrays.stream(arr).max().getAsInt(); int min = Arrays.stream(arr).min().getAsInt(); int range = max - min + 1; int[] count = new int[range]; int[] output = new int[n]; for (int i = 0; i < n; i++) { count[arr[i] - min]++; } for (int i = 1; i < range; i++) { count[i] += count[i - 1]; } for (int i = n - 1; i >= 0; i--) { output[count[arr[i] - min] - 1] = arr[i]; count[arr[i] - min]--; } for (int i = 0; i < n; i++) { arr[i] = output[i]; } } public static void main(String[] args) { int[] arr = {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}; countingSort(arr); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值