十大排序算法

参考1参考2 仅作学习分享使用

1. 冒泡排序(比较算法)

思路

  1. 比较相邻的元素,如果升序,如果第一个比第二个大就交换位置
  2. 一对一对比较,直到最后一对,那么最大的就在最后,至此完成一趟的比较
  3. 对所有元素重复,除了每趟的最后一个,直到排完序

实现

public staic void bubbleSort(int[] array)
{
    for(int i=1;i<array.length;i++) //i作为完成的趟数 总共就是(长度-1)趟 每趟找出当前趟最大值放在后面
    {	//i从1开始,作为趟数
        for(int j=0;j<=array.length-i-1;j++)
        {	//j是数组下标,从0开始。比较j和j+1 所以最大到length-i-1,-1是因为有j+1
            //length-i 是因为每趟可以找到最大的一个放在最后或者最小的一个放在最后
            if(array[j]>array[j+1])
            {
                int temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
            }  
        }
    }
}

复杂度

稳定性是指当两个数相同时,他们的相对顺序不会发生变化

冒泡是稳定的

时间复杂度上:(n-1)+(n-2+...+...+1)=n*(n-1)/2 O(n2)

2. 快速排序(改进冒泡的算法)

快排是对冒泡的一种改进

思路

  1. 从数列中取出一个数作为基准数
  2. 分区过程:把比这个数大的放右边,小的放左边
  3. 再对左右分区重复第二步,直到区间只有一个数

经过一趟之后,基准数左边的数都比它小,右边的数都比它大

每趟的实现:

  1. 设置 i=0; j=length-1
  2. 以第一个数为基准数,pivot=A[0]=A[i]
  3. 从后往前搜索(j–),找到第一个小于pivot的数,交换A[j]和A[i]
  4. 从前往后搜索(i++),找到第一个大于pivot的数,交换A[i]和A[j]
  5. 重复3.4步骤,直到i==j;一趟完成

实现

public static void swap(int[] array,int i,int j)
{
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}
public static void quickSort(int[] array,int low,int high)
{
    if(low<high)
    {
        int i = low;
        int j = high;
        int pivot = array[low];
        while(i<j)
        {
            //从后往前
            while(i<j && array[j]>=pivot)
            {
                j--;
            }
            //交换
            swap(array,i,j);
            //从前往后
            while(i<j && array[i]<=pivot)
            {
                i++;
            }
            swap(array,i,j);
        }//至此完成一趟快排
        //递归进行左右两边的快排
        quickSort(array,low,j-1);
        quickSort(array,j+1,high);
    }
}

复杂度

不稳定的算法

时间复杂度:O(n*log2n)

最糟糕的情况:每次选取的值是最大或者最小,导致划分出一个空表,那么为O(n2)

改善:选取中间的数,尽可能选组中间数

3. 插入排序(插入算法)

思路

把n个待排序元素看为一个有序表和一个无序表,最开始有序表包含1个元素,无序表包含n-1个元素。

排序时,从无序表选出第一个元素,把它插入到有序表中适当的位置,重复n-1次即可。

实现

//升序
public static void swap(int[] array,int i,int j)
{
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}
public static void insertSort(int[] array)
{
    for(int i=1;i<array.length;i++)
    {	//循环次数,n-1次
        for(int j=i;j>0;j--)	//因为i从1开始,所以j从i开始,后面有j-1
        {
            if(array[j]<array[j-1])
                //如果后面的数比前面的小,就交换位置,继续
                swap(array,j-1,j);
            else break;
            //如果位置j的数比j-1的数要大,那么肯定比所有的都大,直接break
        }
    }
}

复杂度

稳定的算法

时间复杂度:O(n2)

4. 希尔排序(改进插入的算法)

对插入算法的改进

思路

按照元素下标的一定增量分组,对每组使用直接插入排序算法;(初始增量为length/2)

随着增量逐渐减少,每组包含的元素越来越多;(增量每趟之后除以2)

当增量减少至1的时候,整个元素被分为一组,终止。

比如增量为5,那么下标0,5,10, …为一组;1,6,11,…为一组。等等

实现

//升序
public static void swap(int[] array,int i,int j)
{
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}
public static void shellSort(int[] array)
{	 //初始增量为长度/2,直到1
    for(int gap=array.length/2;gap>0;gap/2)
    {
        //从gap位置元素开始,逐个对组内的元素进行排序
        for(int i=gap;i<array.length;i++)
        {
            //比较的时候是有增量的,所以j-=gap,j>=gap是为了防止j小于零
            for(int j=i;j>=gap;j-=gap)
            {
                //如果后面的比前面的小就交换位置,跟前面的插入排序是一样的
                if(array[j]<array[j-gap])
                {
                    swap(array,j,j-gap);
                }
                //如果比前面的那个大,那么比前面所有的元素都大
                else break;
            }
        }  
    }
}

复杂度

不稳定的

复杂度:跟数据的顺序和增量的选择有关

O(n*log2n)~O(n2)

5. 选择排序(选择算法)

思路

以由小到大排序为例,首先在未排序序列中找到最小元素,存放到排序序列的起始位置。

然后再从剩余末排序元素中继续寻找最小元素,然后放到已排序序列的末尾。

以此类推,直到所有元素均排序完毕。

实现

//升序
public static void swap(int[] array,int i,int j)
{
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}
public static void selectionSort(int[] array)
{	
    //最后一个元素不用确定位置,所以-1,i表示当前剩余元素中最小的位置。
    for(int i=0;i<array.length-1;i++)
    {
        int min_index = i;
        for(int j=i+1;j<array.length;j++)
            //从i+1开始不断往后比较
        {
            //找到最小值的位置
            if(array[j]<array[min_index])
            {
                min_index = j;
            }
        }
        //把元素交换
        swap(array,i,min_index);
    }
}

复杂度

不稳定的

复杂度:O(n2)

6. 堆排序(改进的选择排序)

思路

对于堆:完全二叉树(从上往下,从左往右生成节点);所有父节点的值都要大于或者小于子节点的值。

堆的表示可以直接用数组表示,还可以通过下标来表示父节点,左孩子或者右孩子

比如:下标为i;父节点为(i-1)/2;左孩子为(2i+1);右孩子(2i+2)

  • 先把待排序列构建为一个大顶堆
  • 可以知道最上面的元素为最大的值,但是整个数组并不是有序的
  • 把堆顶的数据和最后一个交换,再从堆顶往下进行重构堆

建堆的时候从下往上,重构堆是从上往下,可以保证构建的是符合堆特性的

实现

public static void heapSort(int[] array)
{
    //初始化一个最大堆
    //建堆的时候,只需要找到最后一个节点的父节点,然后依次往上进行heapify(i--)
    int last_node = array.length-1;
    int parent = (last_node-1)/2;
    for(int i=parent;i>=0;i--)
    {
        heapify(array,array.length,i);
    }
    //创建完堆之后,可以知道最上面是最大值
    //所以将堆顶和最后一个元素进行交换
    for(int j=array.length-1;j>=0;j--)
    {
        swap(array,j,0);
        //交换之后,把最后位置的元素从堆中剔除,所有长度变了,这里长度就是j
        heapify(array,j,0);
    }
}
//
public static void heapify(int[] tree,int length,int i)
{
    if(i>=length)
        return;//递归的出口
    int c1 = 2 * i + 1;
    int c2 = 2 * i + 2;
    int max = i;
    //找到最大值,看是否符合堆
    if(c1<length && tree[c1]>tree[max])
    {
        max = c1;
    }
    if(c2<length && tree[c2]>tree[max])
    {
        max = c2;
    }
    //如果不符合堆的性质,要做交换
    if(i != max)
    {
        swap(tree,max,i);
        //递归对它下面的继续检查是否符合
        heapify(tree,length,max);
    }
}
public static void swap(int[] array,int i,int j)
{
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

复杂度

不稳定的算法

时间复杂度:O(n*log2n)

7. 归并排序(分治)

分治法的一个典型应用

思路

  • 把长度为n的输入序列分成两个长度为n/2的子序列
  • 对这两个子序列分别采用归并排序
  • 将两个排序好的子列合并成一个 最终的排序序列

实现

//升序
public static void mergeSort(int[] array,int low,int high)
{
    //递归结束的标志,只剩下一个元素
    if(low<high)
    {
        int mid = (low+high)/2
        //对左边进行归并排序
        mergeSort(array,low,mid);
        //对右边进行归并排序
        mergeSort(array,mid+1,high);
        //合并两个有序数组
        merge(array,low,mid,high);
    }
}
public static void merge(int[] array,int low,int mid,int high)
{
    //合并的时候,从有序数组的最左边开始,升序,逐一比较。
    //谁小谁的指针就往后移动,直到某个指针移动完毕
    int[] temp = new int[array.length];
    //由前面知道,下标是low,mid;mid+1,high
    int i = low;
    int j = mid+1;
    int k = 0;
    //比较两个数组最左边的元素,谁小谁的指针就往后移动
    while(i<=mid && j<=high){
        if(array[i]<=array[j])
        {
            temp[k++] = array[i++];
        }
        else temp[k++] = array[j++];
    }
    //左边有剩余
    while(i<=mid)
    {
        temp[k++] = array[i++];
    }
    //右边有剩余
    while(j<=high)
    {
        temp[k++] = array[j++];
    }
    //最后把temp的元素放到array中
    k = 0;
    while(low<=high)
    {
        array[k++] = temp[low++]
    }
}

复杂度

稳定的

复杂度:O(n*log2n)

8. 计数排序(非比较类算法)

计数排序是一种非比较排序算法,其核在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

思路

  1. 先找到元素中最大的值max
  2. 创建一个计数数组,大小为max+1,下标(0,max+1)
  3. 处理每一个数,比如出现了6,那么在计数数组中下表为6的位置加1
  4. 最后遍历计数数组,以此把数取出

典型的空间换时间算法

实现

public static void countSort(int[] array)
{
    //找到最大值
	int max = array[0];
    for(int i=1;i<array.length;i++)
    {
        if(array[i]>max)
        {
        	max = array[i];
        }
    }
    //创建计数数组
    int[] count = new int[max+1];
    //遍历array
    for(int i=0;i<array.length;i++)
    {
        count[array[i]]++;
    }
    int k = 0;
    //遍历计数数组
    for(int i=0;i<count.length;i++)
    {
        while(count[i])
        {
            //count的下标代表元素的值,对应的count值是有多少个元素
            array[k++] = i;
            count[i]--;
        }
    }
}

复杂度

代码优化:数组的大小可以设置为max-min+1;向数组计数的时候下标减去偏移量min,在输出数组的时候加上偏移量min。

//优化:
public static void countSort(int[] array)
{
    //找到最大值
	int max = array[0];
    for(int i=1;i<array.length;i++)
    {
        if(array[i]>max)
        {
        	max = array[i];
        }
    }
    //找到最小值
    int min = array[0];
    for(int i=1;i<array.length;i++)
    {
        if(array[i]<min)
        {
        	min = array[i];
        }
    }
    //创建计数数组
    int[] count = new int[max-min+1];
    //遍历array
    for(int i=0;i<array.length;i++)
    {
        count[array[i]-min]++;
    }
    int k = 0;
    //遍历计数数组
    for(int i=0;i<count.length;i++)
    {
        while(count[i])
        {
            //count的下标代表元素的值,对应的count值是有多少个元素
            array[k++] = i + min;
            count[i]--;
        }
    }
}

稳定的算法

复杂度:时间复杂度是O(n+k),其中n是数组长度, k是数据的范围。

因为将n个数放入计数数组的时间复杂度是O(n),从长为k的计数数组中输出元素的时间复杂度为O(k),所以总的时间复杂度为O(n+k)。

9. 桶排序(改进的计数排序)

思路

假设输入数据服从均均分布,将数据分到有限数量的桶里,每个桶再分别排序。

桶排序利用了函数的映射关系把数据分配到桶里,效与否的关键就在于这个映射函数的确定。

步骤:

  1. 找出待排序数组中的max和min
  2. 桶的数量:[(max-min)/array.length]+1
  3. 遍历数组,计算出(array中)每个元素放入桶中的位置。存放桶的下标=(array[i]-min)/array.length

桶之间的数据是有序的,桶内的数据是无序的,只需要对桶内的数据进行排序

使用数组或者ArrayList存储桶,使用ArrayList存储桶里面的数据

实现

import java.util.ArrayList;
public static void bucketSort(int[] array)
{
     //找到最大值
	int max = array[0];
    for(int i=1;i<array.length;i++)
    {
        if(array[i]>max)
        {
        	max = array[i];
        }
    }
    //找到最小值
    int min = array[0];
    for(int i=1;i<array.length;i++)
    {
        if(array[i]<min)
        {
        	min = array[i];
        }
    }
    //确实桶的数量
    ArrayList<ArrayList<Integer>> buckets = new ArrayList<>();
    int count = (max-min)/array.length +1 ;
    for(int i=0;i<count;i++)
    {
        //每个桶中再增加一个arraylist
        buckets.add(new ArrayList<Integer>());
    }
    //遍历array,把数据放入对应的桶中
    for(int i=0;i<array.length;i++)
    {
        buckets.get((array[i]-min)/array.length).add(array[i]);
    }
    //遍历桶,对桶内的数据进行排序
    for(int i=0;i<buckets.size();i++)
    {
        //这里排序可以随便采用其他方法,这里用冒泡
        bubbleSort(buckets.get(i));
    }
    //遍历每个桶里的每个数据
    int k = 0;
    for(int i=0;i<buckets.size();i++)
    {
        //获取每个桶里的数据为一个list
        ArrayList<Integer> list = buckets.get(i);
        for(int j=0;j<list.size();j++)
        {
            array[k++] = list.get(j);
        }
    }
}

复杂度

稳定性:取决于桶内的排序算法

时间复杂度:如果要排序的数据有n个,我们把它们分在m个桶中,这样每个桶里的数据平均为k= n/ m。如果每个桶使用快速排序,每个桶内排序的时间复杂度就O(k*log2k)。m个桶就是m * O(k*log~2~k)=m * O((n / m)*log~2~(n /m)=O(nlog~2~(n/m))

当桶的个数m接近数据个数n时, log ( n/m )就是一个较小的常数 ,所以时间复杂度接近O(n)。

桶排序的最好情况时间复杂度为0(n) ,如果数据分配到桶的分布情况极度倾斜,比如所有数据都分配到了一个桶,那么这时的时间复杂度就是最坏情况,至于用0表示为多少,就需要看桶排序结合的排序算法的时间复杂度是什么了, 如果是快速排序,那么就是O(n*log2n) ,插入排序则是0(n2)。

10. 基数排序

思路

  • 基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。
  • 比如说先比较个位,再去比较十位,再以此类推
  • 对数要进行初始化,前面没有的就补0
    比如说对个位进行排序,其他位就按照个位进行即可。
  1. 建立桶数组buckets(下标是0-9)因为个位上的数就只有(0-9)。按照个位把数组中的数添加到桶总,注意桶中只是计数并没有不是记录具体的数
  2. 根据buckets进行排序,借助temp[]保存排序的结果,找出temp和buckets的映射关系。对于映射关系,把桶里面的数据累加起来形成buckets[]。 temp[数组索引]=buckets[i]-1
  3. 从后往前遍历原始数组,将元素放入temp中,保证原本的数据顺序不发生变化

实现

public static void radixSort(int[] array)
{
	//分解数字  (value/exp)%10   exp:当求个位时,exp=1;求十位时,exp=10;以此类推
     //找到最大值
	int max = array[0];
    for(int i=1;i<array.length;i++)
    {
        if(array[i]>max)
        {
        	max = array[i];
        }
    }
    //对每位进行操作
    for(int exp=1;max/exp>0;exp*=10)
    {
        //创建桶和数组
        int[] buckets = new int[10];
        int[] temp = new int[array.length];
        //遍历数组元素,计算桶数据个数
        for(int i=0;i<array.length;i++)
        {
            buckets[(array[i]/exp)%10]++;
        }
        //桶数据累加
        for(int i=1;i<buckets.length;i++)
        {
            buckets[i]+=buckets[i-1];
        }
        //从后往前遍历array,放入到temp中
        for(int i=array.length-1;i>=0;i--)
        {
            //(array[i]/exp)%10是在计算它的桶号
            temp[buckets[(array[i]/exp)%10]-1] = array[i];
            //减一是因为可能某位数有多个数据
            buckets[(array[i]/exp)%10]--;
        }
        //把temp给到array
        for(int i=0;i<temp.length;i++)
        {
            array[i] = temp[i];
        }
    }
}

复杂度

稳定性:基于分别收集分别排序,是稳定的

时间复杂度:O(nm) ,n是数据的个数,m是数据位数