经典排序算法(Java实现)

本文详细介绍了多种经典的排序算法在Java中的实现,包括选择排序、插入排序、冒泡排序、希尔排序、归并排序、快速排序及其改进,以及堆排序、计数排序、桶排序和基数排序。各种排序算法的特点、复杂度和适用场景都有所涉及。
摘要由CSDN通过智能技术生成

选择排序

不断选择剩余数组中的最小值----找到未排序部分最小的元素,然后将它和未排序部分首个元素交换位置。

public class Selection {
	public static void sort(Comparable[] a) {
		int N = a.length;
		for(int i = 0; i < N; i++) {
			int min = i;		//最小元素的索引
			for(int j = i+1; j < N; j++) 
				if(less(a[j], a[min]))	min = j;		//寻找剩余部分的最小值
			exch(a, i, min);		//交换
		}
	}
}

对于长度为N的数组,选择排序需要大约N2/2次比较和N次交换。(N*N轨迹表)

特点: 运行时间与输入数组有序程度无关;数据移动最少;稳定排序。

插入排序

将待排序元素插入有序部分的适当位置。

public class Insertion {
	public static void sort(Comparable[] a) {
		int N = a.length;
		for(int i = 1; i < N; i++) {
			for(int j = i; j > 0 && less(a[j], a[j-1]); j--)
				exch(a, j, j-1);		//右移
		}
	}
}

对于长度为N且主键不重复的数组,平均情况下插入排序需要N2/4次比较和N2/4次交换;最坏情况下需要N2/2次比较和N2/2次交换;最好情况下需要N-1次比较和0次交换(完全有序)。

插入排序需要的交换操作和数组中倒置的数量相同,需要的比较次数大于等于倒置的数量,小于等于倒置的数量加上数组的大小再减一。

冒泡排序

从数组末尾开始依次比较相邻元素,大小关系相反则交换位置。

public class bubble {
	public static void sort(Comparable[] a) {
		int N = a.length;
		boolean flag = true;
        for(int i = 0; flag; i++) {
            flag = false;
            for(int j = N-1; j > i; j--) {
                if(less(a[j], a[j-1])) {
                    exch(a, j, j-1);
                    flag = true;
                }
            }
        }
	}
}

对于长度为N的数组,冒泡排序在最坏情况下需要进行(N²-N)/2 次比较运算和(N²-N)/2 次交换运算。

算法复杂度O(n2);稳定排序

希尔排序

使数组中任意间隔为h的元素都是有序的。

public class shell {
	public static void sort(Comparable[] a) {
		int N = a.length;
		int h = 1;
		while(h < N/3)	h = 3*h + 1;
		while(h >= 1) {
			for(int i = h; i < N; i++) {
				for(int j = i; j >= h && less(a[j], a[j-h]); j -= h)
					exch(a, j, j-h);
			}
			h /= 3;
		} 
	}
}

当h满足序列1/2(3k-1)时,算法的时间复杂度基本维持在O(n1.25)。除此之外,只要使用终值为1的递减数列,基本都能高效地完成排序。但是,如果遇到2的幂指数等h=1之前几乎不需要排序的数列,希尔排序的效率会大大降低。

归并排序

采用分治的思想,自顶向下/自底向上将划分得到的数组排序并层层合并。

import java.util.Scanner;

public class Merge {
    private static Comparable[] aux;
    public static void sort(Comparable[] a) {
        aux = new Comparable[a.length];
        sort(a, 0, a.length - 1);
    }
    //自顶向下的归并排序
    private static void sort(Comparable[] a, int lo, int hi) {
        if(hi <= lo)    return;
        int mid = lo + (hi - lo)/2;
        sort(a, lo, mid);
        sort(a, mid+1, hi);
        if(less(a[mid+1], a[mid]))
            merge(a, lo, mid, hi);              //数组有序时会跳过merge操作
    }
    private static void merge(Comparable[] a, int lo, int mid, int hi) {
        int i = lo, j = mid + 1;
        for(int k = lo; k <= hi; k++)
            aux[k] = a[k];                      //辅助数组
        for(int k = lo; k <= hi; k++) {
            if(i > mid)     a[k] = aux[j++];    //左半边用尽,取右半边的元素
            else if(j > hi) a[k] = aux[i++];    //右半边用尽,取左半边的元素
            else if(less(aux[j], aux[i]))   a[k] = aux[j++];    //当前右半边元素小于左半边元素,取右半边元素
            else    a[k] = aux[i++];        //当前右半边元素大于左半边元素,取左半边元素
        }
    }
    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }
    private static void show(Comparable[] a) {
        for(int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
        System.out.println();
    }
    public static boolean isSorted(Comparable[] a) {
        for(int i = 1; i < a.length; i++) {
            if(less(a[i], a[i-1]))
                return false;
        }
        return true;
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String str = input.nextLine();
        String[] a = str.split(" ");
        sort(a);
        assert isSorted(a);
        show(a);
    }
}
   //自底向上
   public static void sort(Comparable[] a) {
        int N = a.length;
        aux = new Comparable[a.length];
        for(int sz = 1; sz < N; sz *= 2)
            for(int lo = 0; lo < N-sz; lo += 2*sz)
                merge(a, lo, lo+sz-1, Math.min(lo+2*sz-1, N-1));
    }

对于长度为N的任意数组,归并排序需要1/2NlgN~NlgN次比较,最多需要访问数组6NlgN次;稳定排序。

自底向上的归并排序比较适合用链表组织的数据。

快速排序

选定切分元素,将数组分为小于该元素(左侧)、大于该元素(右侧)两部分…不断循环直至排序完成。

import java.util.Arrays;
import java.util.Collections;
import java.util.Scanner;

public class Quick {
    public static void sort(Comparable[] a) {
        Collections.shuffle(Arrays.asList(a));  //消除对输入的依赖
        sort(a, 0, a.length - 1);
    }

    private static void sort(Comparable[] a, int lo, int hi) {
        if(hi <= lo)    return;
        int j = partition(a, lo, hi);
        sort(a, lo, j-1);
        sort(a, j+1, hi);
    }
    //原地切分
    private static int partition(Comparable[] a, int lo, int hi) {
        int i = lo, j = hi+1;           //左右扫描指针
        Comparable v = a[lo];           //切分元素
        while(true) {
            while(less(a[++i], v)) {
                if(i == hi)
                    break;
            }                           //寻找左侧大于v的下标
            while(less(v, a[--j])){}    //寻找右侧小于v的下标
            if(i >= j)
                break;                  //指针相遇时切分结束
            exch(a, i, j);
        }
        exch(a, lo, j);
        return j;
    }
    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;      //可能会不必要地将一些等值元素交换,但在某些典型应用中能够避免算法的运行时间变为平方级别
    }
    private static void exch(Comparable[] a, int i, int j) {
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
    private static void show(Comparable[] a) {
        for(int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
        System.out.println();
    }
    public static boolean isSorted(Comparable[] a) {
        for(int i = 1; i < a.length; i++) {
            if(less(a[i], a[i-1]))
                return false;
        }
        return true;
    }
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String str = input.nextLine();
        String[] a = str.split(" ");
        sort(a);
        assert isSorted(a);
        show(a);
    }
}

对于长度为N的无重复数组,快速排序平均需要2NlnN次比较(以及1/6的交换);最多需要N2/2次比较;不稳定排序。

内循环中数据移动少、切分不平衡时较为低效(随机打乱数组可以有效预防)

算法改进

  1. 对小数组使用插入排序
  2. 三取样切分:使用子数组的中位数作为切分元素
  3. 三向切分:划分得到小于、等于、大于三个部分
    private static void sort(Comparable[] a, int lo, int hi) {
        if(hi <= lo)    return;
        int lt = lo, i = lo+1, gt = hi;
        Comparable v = a[lo];
        while(i <= gt) {
            int cmp = a[i].compareTo(v);
            if(cmp < 0) exch(a, lt++, i++);
            else if(cmp > 0)    exch(a, i, gt--);
            else i++;
        }	//a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]
        sort(a, lo, lt-1);
        sort(a, gt+1, hi);
    }

对于大小为N的数组,三向切分的快速排序需要(2ln2)NH次比较(其中H为由主键值出现频率定义的香农信息量)。

堆排序

循环构造大根堆,然后利用下沉排序销毁堆。

import java.util.Scanner;

public class Heapsort {
    public static void sort(Comparable[] a) {
        int N = a.length - 1;
        for(int k = N/2; k > -1; k--) {
            sink(a, k, N);
        }
        while(N > 0) {
            exch(a, 0, N--);
            sink(a,0, N);
        }
    }

    private static boolean less(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }
    private static void exch(Comparable[] a, int i, int j) {
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
    private static void sink(Comparable[] a, int k, int N) {
        while(2*k <= N) {
            int j = 2*k;
            if(j < N && less(a[j], a[j+1]))
                j++;
            if(!less(a[k], a[j]))
                break;
            exch(a, k, j);
            k = j;
        }
    }
    private static void show(Comparable[] a) {
        for(int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
        System.out.println();
    }
    public static boolean isSorted(Comparable[] a) {
        for(int i = 1; i < a.length; i++) {
            if(less(a[i], a[i-1]))
                return false;
        }
        return true;
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String str = input.nextLine();
        String[] a = str.split(" ");
        sort(a);
        assert isSorted(a);
        show(a);
    }
}

平均时间复杂度为 Ο(nlogn)

计数排序

设输入数组为A(0≤Ai≤K),输出数组为B。维护一个辅助数组C[k]:1. 遍历Ai,利用C[Ai]记录该元素出现次数;2. 遍历Cj,累加得到小于等于j的元素个数。然后倒序遍历Ai,根据C[k]得到输出数组B。

import java.util.Scanner;

public class countSort {
    public static String[] b;
    public static final int K = 10000;
    private static void sort(String[] a) {
        int[] c = new int[VMAX+1];
        b = new String[a.length];
        for(String i : a){
            c[Integer.parseInt(i)]++;
        }
        for(int i = 1; i < K+1; i++){
            c[i] += c[i-1];
        }
        for(int j = a.length-1; j > -1; j--){
            b[c[Integer.parseInt(a[j])]-1] = a[j];
            c[Integer.parseInt(a[j])]--;
        }	//倒序保证排序的稳定性
    }

    private static boolean less(String v, String w) {
        return v.compareTo(w) < 0;
    }

    private static void show(String[] a) {
        for (String s : a) {
            System.out.print(s + " ");
        }
        System.out.println();
    }

    public static boolean isSorted(String[] a) {
        for(int i = 1; i < a.length; i++) {
            if(less(a[i], a[i-1]))
                return false;
        }
        return true;
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String str = input.nextLine();
        String[] a = str.split(" ");
        sort(a);
        assert isSorted(b);
        show(b);
    }
}

时间复杂度为O(n+K)

桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。
为了使桶排序更加高效,我们需要做到这两点:

  1. 在额外空间充足的情况下,尽量增大桶的数量
  2. 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
    同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。

基数排序

根据键值的每位数字来分配桶

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值