Java八种常见排序算法
排序大的分类可以分为两种:内排序和外排序。在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序。下面讲的排序都是属于内排序。
内排序又可以分为以下几类:
(1)、冒泡排序
(2)、选择排序
(3)、插入排序
(4)、快速排序
(5)、归并排序
(6)、希尔排序
(7)、堆排序
(8)、基数排序
下面逐个接受各个排序方法:
1、冒泡排序:
时间复杂度:O(n^2)
稳定性:稳定
private static int[] bubbleSort(int[] a) {
// TODO Auto-generated method stub
int len=a.length;
for(int i=0;i<len-1;i++)
{
for(int j=len-1;j>i;j--)
{
if(a[j]<a[j-1]) // 轻的向上冒
{
a[j]=a[j]^a[j-1];
a[j-1]=a[j]^a[j-1];
a[j]=a[j]^a[j-1];
}
}
}
return a;
}
2、插入排序:
从第一个元素开始,该元素可以认为已经被排序,取出下一个元素,在已经排序的元素序列中从后向前扫描,如果该元素(已排序)大于新元素,将该元素移到下一位置,直到找到已排序的元素小于或者等于新元素的位置,将新元素插入到该位置中
时间复杂度:O(n^2)
稳定性:稳定
private static int[] insertSort(int[] a) {
int temp;
int len=a.length;
int j;
for(int i=1;i<len;i++)
{
temp=a[i];
j=i;
while(temp<=a[j-1])
{
a[j]=a[j-1];
j--;
}
a[j]=temp;
}
return a;
}
3、选择排序:
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了
时间复杂度:O(n^2)
稳定性:不稳定
private static int[] selectSort(int[] a) {
int len=a.length;
int min;
int index;
for(int i=0;i<len-1;i++)
{
min=a[i];
index=i;
for(int j=i+1;j<len;j++)
{
if(min>a[j])
{
min=a[j];
index=j;
}
}
a[index]=a[i];
a[i]=min;
}
return a;
}
4、快排:
快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j, 交换a[i]和a[j],重复上面的过程,直到i>j。交换a[j]和a[center_index],完成一趟快速排序
时间复杂度:O(nlgn)
稳定性:不稳定
private static void quickSort(int[] a, int start, int end) {
if(start>=end||a==null)
{
return ;
}
int p=partition(a,start,end);
quickSort(a, start, p-1);
quickSort(a, p+1, end);
}
private static int partition(int[] a, int start, int end) {
int first=a[start];
int i=start;
int j=end;
while(i<j){
while(a[i]<=first&&i<end)
{
i++;
}
while(a[j]>first&&j>=start){
j--;
}
if(i<j){
a[i]=a[i]^a[j];
a[j]=a[i]^a[j];
a[i]=a[i]^a[j];
}
}
if(j!=start){
a[start]=a[j];
a[j]=first;
}
return j;
}
5、归并排序:
基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
时间复杂度:O(nlogn)
稳定性:稳定
具体代码如下:
public class SortTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] A={2,5,9,3,4,7,8,156,46,7,26,79,14,4,25,4,496,269};
int[] res=sorting(A);
for(int i=0;i<A.length;i++){
System.out.print(res[i]+" ");
}
}
// 分代处理
private static int[] sorting(int[] a) {
// TODO Auto-generated method stub
int len=a.length;
for(int gap=1;gap<len;gap=2*gap)
{
MergePass(a,gap,len);
}
return a;
}
// 按每一代的gap长度,进行分段,组合
private static void MergePass(int[] a, int gap, int len) {
// TODO Auto-generated method stub
int i=0;
for(i=0;i+2*gap-1<len;i=i+2*gap){
Merge(a,i,i+gap-1,i+2*gap-1);
}
if(i+gap<len){
Merge(a,i,i+gap-1,len-1);
}
}
private static void Merge(int[] a, int low, int mid, int high) {
// TODO Auto-generated method stub
int i=low;
int j=mid+1;
int k=0;
int[] array2=new int[high-low+1];
while(i<=mid&&j<=high)
{
if(a[i]<=a[j])
{
array2[k]=a[i];
i++;
k++;
}else {
array2[k]=a[j];
j++;
k++;
}
}
while(i<=mid){
array2[k]=a[i];
i++;
k++;
}
while(j<=high){
array2[k]=a[j];
j++;
k++;
}
System.arraycopy(array2, 0, a, low, high-low+1);
}
}
6、希尔排序:
基本思想:算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。
时间复杂度:O(nlogn)
稳定性:不稳定
private static void shellSort(int[] a) {
int d=a.length;
int temp;
while(true){
d=(int)Math.ceil(d/2);
for(int x=0;x<d;x++){
for(int i=x+d;i<a.length;i+=d)
{
temp=a[i];
int j=i;
for(;j>=d&&temp<a[j-d];j-=d){
a[j]=a[j-d];
}
a[j]=temp;
}
}
if(d==1){
break;
}
}
}
7、基排序:
基本思想:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
时间复杂度:O(nlogn)
稳定性:稳定
private static void radixSort(int[] array) {
int max=array[0];
for(int i=1;i<array.length;i++){
if(array[i]>max){
max=array[i];
}
}
int time=0;
//判断位数;
while(max>0){
max/=10;
time++;
}
//建立10个队列;
@SuppressWarnings("rawtypes")
List<ArrayList> queue=new ArrayList<ArrayList>();
for(int i=0;i<10;i++){
ArrayList<Integer> queue1=new ArrayList<Integer>();
queue.add(queue1);
}
//进行time次分配和收集;
for(int i=0;i<time;i++){
//分配数组元素;
for(int j=0;j<array.length;j++){
//得到数字的第time+1位数;
int x=array[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i);
@SuppressWarnings("unchecked")
ArrayList<Integer> queue2=queue.get(x);
queue2.add(array[j]);
queue.set(x, queue2);
}
int count=0;//元素计数器;
//收集队列元素;
for(int k=0;k<10;k++){
while(queue.get(k).size()>0){
@SuppressWarnings("unchecked")
ArrayList<Integer> queue3=queue.get(k);
array[count]=queue3.get(0);
queue3.remove(0);
count++;
}
}
}
}
8、堆排序:
基本思想:堆排序是一种树形选择排序,是对直接选择排序的有效改进。堆的定义如下:具有n个元素的序列(h1,h2,…,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,…,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
时间复杂度:O(nlogn)
稳定性:不稳定
private static void heapSort(int[] a) {
int arrayLength=a.length;
//循环建堆
for(int i=0;i<arrayLength-1;i++){
//建堆
buildMaxHeap(a,arrayLength-1-i);
//交换堆顶和最后一个元素
a[0]=a[0]^a[arrayLength-1-i];
a[arrayLength-1-i]=a[0]^a[arrayLength-1-i];
a[0]=a[0]^a[arrayLength-1-i];
System.out.println(Arrays.toString(a));
}
}
private static void buildMaxHeap(int[] data, int lastIndex) {
for(int i=(lastIndex-1)/2;i>=0;i--){
//k保存正在判断的节点
int k=i;
//如果当前k节点的子节点存在
while(k*2+1<=lastIndex){
//k节点的左子节点的索引
int biggerIndex=2*k+1;
//如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在
if(biggerIndex<lastIndex){
//若果右子节点的值较大
if(data[biggerIndex]<data[biggerIndex+1]){
//biggerIndex总是记录较大子节点的索引
biggerIndex++;
}
}
//如果k节点的值小于其较大的子节点的值
if(data[k]<data[biggerIndex]){
//交换他们
data[k]=data[k]^data[biggerIndex];
data[biggerIndex]=data[k]^data[biggerIndex];
data[k]=data[k]^data[biggerIndex];
//将biggerIndex赋予k,开始while循环的下一次循环,
//重新保证k节点的值大于其右子节点的值
k=biggerIndex;
}else{
break;
}
}
}
}
总结:
一、稳定性:
稳定:冒泡排序、插入排序、归并排序和基数排序
不稳定:选择排序、快速排序、希尔排序、堆排序
二、平均时间复杂度
O(n^2):直接插入排序,简单选择排序,冒泡排序。
在数据规模较小时(9W内),直接插入排序,简单选择排序差不多。当数据较大时,冒泡排序算法的时间代价最高。性能为O(n^2)的算法基本上是相邻元素进行比较,基本上都是稳定的。
O(nlogn):快速排序,归并排序,希尔排序,堆排序。
其中,快排是最好的, 其次是归并和希尔,堆排序在数据量很大时效果明显。
三、排序算法的选择
1、数据规模较小
(1)待排序列基本序的情况下,可以选择直接插入排序;
(2)对稳定性不作要求宜用简单选择排序,对稳定性有要求宜用插入或冒泡
2、数据规模不是很大
(1)完全可以用内存空间,序列杂乱无序,对稳定性没有要求,快速排序,此时要付出log(N)的额外空间。
(2)序列本身可能有序,对稳定性有要求,空间允许下,宜用归并排序
3、数据规模很大
(1)对稳定性有求,则可考虑归并排序。
(2)对稳定性没要求,宜用堆排序
4、序列初始基本有序(正序),宜用直接插入,冒泡