排序大的分类可以分为两种:内排序和外排序。在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序。下面讲的排序都是属于内排序。
内排序有可以分为以下几类:
(1)、插入排序:直接插入排序、二分法插入排序(没实现)、希尔排序。
(2)、选择排序:选择排序、堆排序。
(3)、交换排序:冒泡排序、快速排序。
(4)、归并排序
(5)、计数排序
(6)、基数排序
**算法稳定性定义:
算法稳定性 – 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!**
冒泡排序:
/**
* 冒泡算法:每次把最大的一个数找出来放在最后面。
* 边界问题:举例:6个数字需要遍历5次,所以第一个循环是i<length;
* 而第一次遍历比较次数是5次,加上 下标从0开始,所以第2个循环是j<length - i
* 时间复杂度:O(n^2)
* 优化算法:如果某次遍历中没有发生交换,那就说明已经排序好了,就不必进行下一次的遍历;设置boolean变量来监控判断;
* 根据稳定性定义可知:稳定性的;
* @param sort
*/
private static void buddleSort(int[] sort){
boolean needNextPass = true;
for(int i=1;i<sort.length&& needNextPass;i++){
needNextPass = false;
for(int j=0;j<sort.length-i;j++)
{
if(sort[j]>sort[j+1]){
int temp=sort[j+1];
sort[j+1]=sort[j];
sort[j]=temp;
needNextPass = true;
}
}
}
}
运行结果:
排序前:
2 9 5 4 8 1
排序后:
1 2 4 5 8 9
直接插入排序
/**
* 直接插入排序:将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据
* 边界问题:6个数,插入5次,所以第一次循环是:1<=i<length;
* 算法解释:从后往前找需要替换的那个数
* 根据稳定性定义可知:稳定性的;
* 时间复杂度:O(n^2)
* @param sort
*/
private static void directInsertSort(int[] sort){
for(int i=1; i<sort.length;i++){
int index=i-1;
int temp=sort[i];
while(index >=0 && sort[index]>temp){
sort[index+1]= sort[index];//依次后移每个,等待temp找到合适的直接插入
index--;
}
sort[index + 1]=temp;
}
}
运行结果:
排序前:
2 9 5 4 8 1
排序后:
1 2 4 5 8 9
选择排序
/**
* 选择排序:每次遍历数组找出最小的,然后与开头的交换
* 边界问题:6个数字,需要遍历5次,所以 0<=i<=length-1;
* 第二个就是在已经排序好的基础上往后面找:所以是 : i+1<=j<length;
* 时间复杂度:O(n^2)
* 根据稳定性定义可知:不稳定性的;
* @param sort
*/
private static void selectSort(int [] sort){
for(int i=0;i<sort.length-1;i++)
for(int j=i+1;j<sort.length;j++){
if(sort[j]<sort[i]){
int temp=sort[i];
sort[i]=sort[j];
sort[j]=temp;
}
}
}
运行结果:
排序前:
2 9 5 4 8 1
排序后:
1 2 4 5 8 9
归并排序
因为归并排序的稳定性,所以Arrays.sort()用的就是归并排序。
/**
* 归并排序:将数组分为两半,对每部分递归应用归并排序;两部分排序好后,对他们进行归并。
* 时间复杂度:O(nlogn)
* 空间复杂度:O(n)
* 稳定。
* @param sort
* @param low
* @param high
*/
public static void mergeSort(int[] sort,int low,int high){
if(low < high){
int mid = (low + high)/2;
mergeSort(sort,low,mid);
mergeSort(sort,mid+1,high);
merge(sort,low,mid,high);
}
}
public static void merge(int []sort,int low ,int mid, int high){
int begin1 = low;
int begin2 = mid+1;
int [] temp = new int [high - low +1];
int index =0;
while(begin1 <=mid && begin2<= high){
if(sort[begin1]<=sort[begin2])
temp[index++] = sort[begin1++];
else
temp[index++] = sort[begin2++];
}
//遍历完其中的1半,那么直接复制另一半剩下的到数组中
while(begin1 <= mid)
temp[index++] = sort[begin1++];
while(begin2 <= high)
temp[index++] = sort[begin2++];
//将申请的额外排好序的空间数组赋值给sort数组,完成排序
for(int begin = low;begin <= high; begin++)
sort[begin] = temp[begin-low];
}
运行结果:
排序前:
2 9 5 4 8 1
排序后:
1 2 4 5 8 9
快速排序
/**快速排序:快速排序 通过一趟排序将要排序的数据分割成独立的两部分,
* 一部分数据《= 中间数据 《= 另一部分数据
* 然后再按此方法对这两部分数据分别进行快速排序,
* 整个排序过程可以递归进行,以此达到整个数据变成有序序列。
* 时间复杂度:O(nlogn)
* 问题:不稳定。
* @param sort
* @param low
* @param high
*/
private static void quickSort(int[] sort,int low,int high){
if(low < high)//递归结束的判断
{
int loc = Partition(sort,low,high);
quickSort(sort,low,loc-1);
quickSort(sort,loc+1,high);
}
}
public static int Partition(int[] sort ,int low ,int high){
int elem = sort[low] ; //取一个基准元素
while(low < high){
while(low < high && sort[high]>= elem)
high--;
sort[low] = sort[high];
while(low<high && sort[low]<= elem)
low ++;
sort[high] = sort[low];
}
sort[low] =elem;
return low ;
}
运行结果:
排序前:
2 9 5 4 8 1
排序后:
1 2 4 5 8 9
希尔排序
/**
* 希尔排序:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。
* 先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),
* 即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。
* 时间复杂度:O(nlogn)
* 问题:不稳定
* 希尔排序时间复杂度
希尔排序的时间复杂度与增量(即,步长gap)的选取有关。例如,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为O(N²),而Hibbard增量的希尔排序的时间复杂度为O(N3/2)。
* @param sort
*/
public static void ShellSort(int[] sort){
int hop = sort.length/2;
while(hop > 0)
{
for(int k =0;k< hop ;++k)
{
for(int i =k+ hop;i<sort.length;i+= hop)
{
int j = i-hop;
int tmp = sort[i];
while(j>=0 && sort[j] > tmp)
{
sort[j+hop] = sort[j];
j-= hop;
}
sort[j+hop] = tmp;
}
}
hop/=2;
}
}
运行结果:
排序前:
2 9 5 4 8 1
排序后:
1 2 4 5 8 9
计数排序(类似桶排序)
/**(准确的说,这仅仅是简单的计数排序,仅是统计桶的个数,而桶内数据并没有排序。。。)
* 桶排序:假设待排序的数组a中共有N个整数,并且已知数组a中数据的范围[0, MAX)。
* 在桶排序时,创建容量为MAX的桶数组r,并将桶数组元素都初始化为0;将容量为MAX的桶数组中的每一个单元都看作一个"桶"。
* 在排序时,逐个遍历数组a,将数组a的值,作为"桶数组r"的下标。当a中数据被读取时,就将桶的值加1。
* 例如,读取到数组a[3]=5,则将r[5]的值+1。
* 要求:要求苛刻:1.数字最大值有限,不能太大;2.全为正数
* 时间复杂度:O(max(n,sort.max))确实快,但是牺牲了空间
* 空间复杂度:O(sort.max)
* @param sort
*/
public static void bucketSort(int sort[]){
//寻找出最大值
int max =0;
for(int i=0;i<sort.length;i++){
if(max<sort[i])
max= sort[i];
}
//初始化为0,
int []buckets = new int [max+1];
//1.计数
for(int i=0;i<sort.length;i++)
buckets[sort[i]]++;
//2.排序
for(int i=0,j=0;i<=max;i++){
while(buckets[i]-->0)
sort[j++] = i;
}
}
运行结果:
排序前:
22 19 65 34 998 1
排序后:
1 19 22 34 65 998
基数排序
/**
* 基数排序:多关键字排序(例如扑克牌的花色+数值;数字看成每位的多个关键字),分为:最高位优先和最低位优先的2种方法;
* 基于最低位优先策略做的:方法核心:分配 + 收集;
* 每次循环都做这2步
* 根据稳定性定义可知:稳定性的;
* @param sort
*/
public static void radixSort(int []sort){
//找到最大的数字,确定要排序几次
int max = 0;
for(int i=0; i<sort.length;i++){
if(max < sort[i])
max = sort[i];
}
//判断位数
int times = 0;
while(max>0){
max/=10;
times++;
}
//建立10个队列:注意下,因为java没有泛型数组:
//List<ArrayList>[] queue = new ArrayList<>[10];会报错
//改为:List[] queue = new ArrayList[10];会提示出警告:因为类型不安全,
//所以要这样子做:
List<ArrayList<Integer>> queue = new ArrayList<>();
for(int i =0; i< 10;i++)
{
ArrayList<Integer> queue1 = new ArrayList<>();
queue.add(queue1);
}
//进行times次分配和收集
for(int i =0;i<times;i++){
//分配
for(int j = 0;j<sort.length;j++){
int x = sort[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i);//求每个个位/十位/百位
ArrayList<Integer> queue2 = queue.get(x);
queue2.add(sort[j]);
queue.set(x, queue2);
}
//收集
int count = 0;
for(int j=0;j<10;j++){
while(queue.get(j).size()>0){
ArrayList<Integer> queue3 = queue.get(j);
sort[count] = queue3.get(0);
queue3.remove(0);
count++;
}
}
}
}
运行结果:
排序前:
22 19 65 34 998 1
排序后:
1 19 22 34 65 998
最大堆排序
/**
* 最大堆排序思想:① 初始化堆:将数列a[1...n]构造成最大堆。
② 交换数据:将a[1]和a[n]交换,使a[n]是a[1...n]中的最大值;然后将a[1...n-1]重新调整为最大堆。
接着,将a[1]和a[n-1]交换,使a[n-1]是a[1...n-1]中的最大值;然后将a[1...n-2]重新调整为最大值。 依次类推,直到整个数列都是有序的。
* 分别对应2个方法。
* 数组实现;
* @param a
* @param n
*/
public static void heapSortAsc(int []a,int n){
int i,tmp;
// 从(n/2-1) --> 0逐次遍历。遍历之后,得到的数组实际上是一个(最大)二叉堆。
for(i = n/2-1;i>=0;i--)
maxHeapDown(a,i,n-1);
// 从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
for (i = n - 1; i > 0; i--) {
// 交换a[0]和a[i]。交换后,a[i]是a[0...i]中最大的。
tmp = a[0];
a[0] = a[i];
a[i] = tmp;
// 调整a[0...i-1],使得a[0...i-1]仍然是一个最大堆。
// 即,保证a[i-1]是a[0...i-1]中的最大值。
maxHeapDown(a,0,i-1);
}
}
public static void maxHeapDown(int[] a,int start,int end){
int c = start; // 当前(current)节点的位置
int l = 2*c+1; // 左(left)孩子的位置
int tmp = a[c]; // 当前(current)节点的大小
for(;l<=end;c=l,l=2*l+1){
// "l"是左孩子,"l+1"是右孩子
if(l< end && a[l]<a[l+1])
l++;// 左右两孩子中选择较大者,即m_heap[l+1]
if(tmp>=a[l])
break;// 调整结束
else //交换值
{
a[c] = a[l];
a[l] = tmp;
}
}
}
运行结果:
排序前:
22 19 65 34 998 1
排序后:
1 19 22 34 65 998