八大排序算法及代码实现


作为计算机面试最基础的算法,今天来整理复习一下数据结构中的八大排序算法,面试前必须熟练到可以在纸上手写出来,不依靠IDE。

算法复杂度对比

插入排序

直接插入排序

某一时刻待排序列表状态如下:

有序序列L[1…i-1]L[i]无序序列L[i+1….n]

需要进行以下操作:
1)查找出L[i]在L[1…i-1]中的插入位置k
2)将L[k…i-1]中所有元素全部后移一个位置
3)将L[i]复制到L[k]

执行n-1次就能得到一个有序的表。
使用常数个辅助单元,空间复杂度为o(1)
时间复杂度为o(n2)
由于每次都是从后向前比较后才移动,不会出现相同元素相对位置发生变化的情况。

public class InsertSort {
    public static void main(String[] args) {
        int[] array = {4,7,2,1,5,3,8,6};
        int i,j;
        for (i = 1; i < array.length; i++) {
            int tmp = array[i];
            for (j = i-1; j >= 0; j--) {//注意这个循环
                if (tmp < array[j]){//一边比较一边后移
                    array[j+1] = array[j];
                }else{
                    break;
                }
            }
            array[j+1] = tmp;
        }
        for (int k = 0; k < array.length; k++) {
            System.out.print(array[k]+" ");
        }
    }
}

希尔排序

希尔排序也称为“缩小增量排序”,基本原理是:首先将待排序的元素分为多个子序列,使得每个子序的元素个数相对较少,对各个子序分别进行直接插入排序,待整个待排序序列“基本有序后”,再对所有元素进行一次直接插入排序。

先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。
所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;
然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量 dt =1( dt < …<d2<d1),即所有记录放在同一组中进行直接插入排序为止。

最坏情况下希尔排序的时间复杂度为o(n2)。
由于多次插入排序,元素可能在不同的插入排序中改变相对位置,所以希尔排序是不稳定的排序算法。

目前尚未求得一个最好的增量序列,希尔提出的方法是d1=n/2,di+1 = di/2(向下取整),最后一个增量等于1。

注意实现的时候跟逻辑上的有些许不同,例:
在这里插入图片描述
第一趟排序只会排49和13,并不会49,13,19一起排,排完49和13就排38和27。


public class ShellSort {
    public static void main(String[] args) {
        int[] array = {4,7,2,1,5,3,8,6};

        int i,j,d;
        for (d = array.length/2; d > 0 ; d /= 2) {
            for (i = d; i < array.length; i++) {//对每小段进行插入排序
                int tmp = array[i];
                for (j = i - d; j >= 0 ; j -= d) {
                    if (array[j] > tmp)
                        array[i] = array[j];
                    else
                        break;
                }
                array[j+d] = tmp;
            }
        }

        for (int k = 0; k < array.length; k++) {
            System.out.print(array[k]+" ");
        }
    }
}

交换排序

冒泡排序

冒泡排序的基本思想:从前往后(或从后往前)两两比较相邻元素的值,若为逆序则交换,每一趟都会将一个最大(小)元素放到最终位置。最多重复n-1趟。
时间复杂度为o(n2)
只用常数个辅助单元,空间复杂度为o(1)
A[i]=A[i+1]时不会交换两个元素,所以冒泡排序是稳定的

public class BubbleSort {
    public static void main(String[] args) {
        int[] array = {4,7,2,1,5,3,8,6};
        int i,j;
        boolean flag;//用来检测本趟有没有发生交换,若没有则已完成排序
        for (i = 0; i < array.length-1; i++){
            flag = false;
            for (j = 0; j < array.length-1; j++)
                if(array[j] > array[j+1]){
                    swap(array,j);
                    flag = true;//若发生交换,置flag为true
                }
            if(flag == false)//若这趟没有发生交换,说明已全部有序,后面不用再循环了
                break;
        }
        for (int k = 0; k < array.length; k++) {
            System.out.print(array[k]+" ");
        }

    }

    private static void swap(int[] array, int j) {
        int tmp = array[j+1];
        array[j+1] = array[j];
        array[j] = tmp;
    }
}

快速排序

快速排序的基本思想是分而治之,选择一个pivot座位基准,通过一趟排序将表划分为独立的两部分L[1….k-1]和L[k+1……n],L[1….k-1]中所有元素小于pivot,L[k+1……n]中所有元素大于等于pivot,则pivot放在最终位置上。递归这个过程直至每个部分只有1个元素为止。

快速排序用到递归,递归需要用到栈,平均情况下空间复杂度为o(log2n),最坏情况下空间复杂度为o(n)

当初始表基本有序或者基本逆序时,快速排序将退化为冒泡排序,得到最坏的时间性能o(n2),而其平均性能接近最好性能 o(nlog2n)。快排是所有内部排序算法中平均性能最优的排序算法。


public class QuickSort {
    public static void main(String[] args) {
        int[] array = {4,7,2,1,5,3,8,6};

        quicksort(array,0,array.length-1);

        for (int k = 0; k < array.length; k++) {
            System.out.print(array[k]+" ");
        }
    }

    private static void quicksort(int[] array, int low, int high) {
        if(low < high){
            int pivot = partition(array,low,high);
            quicksort(array,low,pivot-1);
            quicksort(array,pivot+1,high);
        }
    }

    //划分操作
    private static int partition(int[] array, int low, int high) {
        int pivot = array[low];
        while (low < high){
            //因为我们让low作为pivot,所以要从high开始。
            while (low < high && array[high] >= pivot) high--;//定位到比pivot小的位置
            array[low] = array[high];
            while (low < high && array[low] <= pivot) low++;
            array[high] = array[low];
        }
        array[low] =pivot;
        return low;
    }
}

选择排序

简单选择排序

对于给定的一组记录,经过第一轮比较后得到最小的记录,然后将记录与第一个记录的位置进行交换;接着对不包括第一个记录以外的其他记录进行第二轮排序,得到最小的记录并与第二个记录进行位置交换;重复该过程,直到进行比较的记录只有一个为止。

时间复杂度为o(n2) 不稳定

public class SelectSort {
    public static void main(String[] args) {
        int[] array = {4,7,2,1,5,3,8,6};
        int min;
        for (int i = 0; i < array.length; i++) {
            min = i;
            for (int j = i+1; j < array.length; j++) {
                if (array[min] > array[j])
                    min = j;
            }
            if (min != i){
                int tmp = array[i];
                array[i] = array[min];
                array[min] = tmp;
            }
        }

        for (int k = 0; k < array.length; k++) {
            System.out.print(array[k]+" ");
        }
    }
}

堆排序

将序列构造成一棵完全二叉树 ;
把这棵普通的完全二叉树改造成堆,便可获取最小值 ;
输出最小值 ;
删除根结点,继续改造剩余树成堆,便可获取次小值 ;
输出次小值 ;
重复改造,输出次次小值、次次次小值,直至所有结点均输出,便得到一个排序 。

时间复杂度为o(nlog2n) 不稳定

public class HeapSort {
    public static void main(String[] args) {
        int[] array = {4,7,2,1,5,3,8,6};

        //构造初始堆,指针是从0开始的,所以是len/2-1
        for (int i = array.length/2-1; i >= 0; i--) {
            AdjustDown(array,i,array.length);
        }

        for (int i = array.length-1; i > 0 ; i--) {
            //交换堆顶和最后一个元素,重新调整堆
            swap(array,i,0);
            AdjustDown(array,0,i-1);

        }
        
        for (int k = array.length-1; k >= 0; k--) {
            System.out.print(array[k]+" ");
        }
    }

    private static void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
    //最核心部分  调整形成小顶堆 i是工作指针
    private static void AdjustDown(int[] array, int i, int len) {
        int tmp = array[i];
        for (int j = i * 2 + 1; j < len ; j = j*2+1) {//注意j的步长是*2+1
            if (j + 1 < len && array[j] > array[j+1])
                j++;//选择较小的子节点
            if (tmp > array[j]){//如果交换了,子树会受影响,所以循环判断
                array[i] = array[j];
                i = j;
            }
            else    //没有发生交换,不用再循环。
                break;

        }
        array[i] = tmp;
    }
}

这个排序算法也是调代码调到最吐血的一个算法,个人认为在八个排序算法里面属最难。

基数排序

基数排序不是基于比较进行排序的,而是基于关键字各位的大小进行排序的。包括“分配”和“收集”两种操作。(基数排序是一种特殊的桶排序)
在这里插入图片描述
假设排序的数中最大的是d位数,基数排序需要进行d趟分配和收集,所以基数排序的时间复杂度是o(d*n)

import java.util.Vector;

public class RadixSort {
    public static void main(String[] args) {
        int[] a = {278,109,63,930,589,184,505,269,8,83};
        int d = maxd(a);
        int i = 1;//从个位开始
        int tmpi = 0;
        int[] num ={0,0,0,0,0,0,0,0,0,0};//每个桶里面装的数字的个数
        int[][] buckets = new int[10][a.length];//第一维是0~10

        for (int j = 0; j < d; j++) {


            //分配
            for (int x:a){
                int tmp = ((x/i)%10);
                buckets[tmp][num[tmp]] = x;
                num[tmp]++;
            }

            //收集
            for (int k = 0; k < 10; k++) {
                if(num[k] != 0)
                    for (int l = 0; l < num[k]; l++) {
                        a[tmpi++] = buckets[k][l];
                    }
                num[k] = 0;//清空桶
            }
            tmpi = 0;//记得重置
            i *= 10;
        }
        


        for (int x:a) {
            System.out.print(x+" ");
        }
    }

    //求出数组中的数据最大是几位数
    private static int maxd(int[] a) {
        int max = 0;
        for (int x:a) {
            int d = 0;
            while (x > 0){
                x /= 10;
                d++;
            }
            if (d > max)
                max = d;
        }
        return max;
    }
}

归并排序

假定待排序表含有n个记录,则可以看成是n个有序的子表,每个子表长为1,然后两两归并,得到n/2个长度为2或1的有序表,再两两归并,。。。。如此重复,直到合并成一个长度为n的有序表为止。这种排序成为2路归并排序。

归并排序是一种稳定的排序算法。
每趟归并的时间复杂度为o(n),共需进行log2n(向下取整)趟,所以时间复杂度为o(nlog2n)
辅助数组刚好要使用n个单元,所以空间复杂度为o(n)

public class MergeSort {
    public static void main(String[] args) {
        int[] a = {4,7,2,1,5,3,8,6};

        mergeSort(a,0,a.length-1);

        for (int x:a) {
            System.out.print(x+" ");
        }
    }

    private static void mergeSort(int[] a, int low, int high) {
        if (low < high){
            int mid = (low + high) / 2;
            mergeSort(a,low,mid);//对左侧子序列进行递归排序
            mergeSort(a,mid+1,high);//对右侧子序列进行递归排序
            Merge(a,low,mid,high);//归并
        }
    }

    private static void Merge(int[] a, int low, int mid, int high) {
        int[] tmp = new int[a.length];
        int j = low;
        int k = mid+1;
        int l = j;//l是要在数据a中用的指针
        //将a中的数据复制到辅助表中
        for (int i = low; i <= high; i++)
            tmp[i] = a[i];

        for (j = low; j <= mid && k <= high; l++) {//注意看好这里是l++
            if (tmp[j] <= tmp[k])
                a[l] = tmp[j++];//j和k的指针也要记得++
            else 
                a[l] = tmp[k++];
        }
        //把子表中剩余的数复制到数组中
        while (j <= mid)
            a[l++] = tmp[j++];
        while (k <= high)
            a[l++] = tmp[k++];
    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值