详细的十大经典排序算法总结

 

欢迎大家在语雀进行学习交流

1、插入排序

直接插入排序

  • 基本思想:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
  • 时间复杂度为:O(n2)


如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

 

public void InsertSort(int a[], int n){
    for(int i = 1; i < n; i++){
        if(a[i] < a[i-1]){       //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
            int j = i-1;    
            int x = a[i];        //复制为哨兵,即存储待排序元素
            a[i] = a[i-1];       //先后移一个元素
            while(x < a[j]){     //查找在有序表的插入位置
                a[j+1] = a[j];
                j--;         //元素后移
            }
            a[j+1] = x;      //插入到正确位置
        }
    }
    
}

 

希尔排序

  • 算法实现:先将要排序的一组记录按增量d(d=n/2,n为要排序数的个数)分成若干组子序列,每组中记录的下标相差d。对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。不稳定的排序


 

/**
*直接插入排序的一般形式
*@param int dk 缩小增量,如果是直接插入排序dk=1
*/
public void ShellInsertSort(int a[], int n, int dk){
    for(int i= dk; i<n; ++i){
        if(a[i] < a[i-dk]){         //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
            int j = i-dk;   
            int x = a[i];           //复制为哨兵,即存储待排序元素
            a[i] = a[i-dk];         //首先后移一个元素
            while(x < a[j]){        //查找在有序表的插入位置
                a[j+dk] = a[j];
                j -= dk;             //元素后移
            }
            a[j+dk] = x;            //插入到正确位置
        }
    }
    
}
 
//先按照增量d(d=n/2,n为要排序数的个数)进行希尔排序
public void shellSort(int a[], int n){
    int dk = n/2;
    while( dk >= 1  ){
        this.ShellInsertSort(a, n, dk);
        dk = dk/2;
    }
}

 

2、选择排序

简单选择排序

  • 算法思想:在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。

 

//查找数组中第i个元素到最后一个元素中的最小值
public int SelectMinKey(int a[], int i){
    int k = i;
    for(int j=i+1; j < a.length; j++) {
        if(a[k] > a[j])
            k = j;
    }
    return k;
}
 
//选择排序
public void selectSort(int a[]){
    int key, tmp;
    for(int i = 0; i < a.length; i++) {
        key = SelectMinKey(a, i);           //选择最小的元素
        if(key != i){
            tmp = a[i];
            a[i] = a[key];
            a[key] = tmp;           //最小元素与第i位置元素互换
        }
    }
}

 

堆排序

  • 推荐博客
  • 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
    • 大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]  
    • 小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]  

image.png如图所示为映射到数组中的样子

  • 算法思路
    • 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
    • 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
    • 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

 

public static void sort(int []arr){
    //1.构建大顶堆
    for(int i=arr.length/2-1; i >= 0; i--){
        //从第一个非叶子结点从下至上,从右至左调整结构
        adjustHeap(arr,i,arr.length);
    }
    //2.调整堆结构+交换堆顶元素与末尾元素
    for(int j=arr.length-1;j>0;j--){
        //将堆顶元素与末尾元素进行交换
        int temp=arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
        adjustHeap(arr,0,j);//重新对堆进行调整
    }
}

/**
 * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
 * @param arr
 * @param i
 * @param length
 */
public static void adjustHeap(int []arr,int i,int length){
    int temp = arr[i];      //先取出当前元素i
    for(int k=i*2+1; k < length; k=k*2+1){      //从i结点的左子结点开始,也就是2i+1处开始
        if(k+1<length && arr[k]<arr[k+1])       //如果左子结点小于右子结点,k指向右子结点
            k++;
        
        if(arr[k] > temp){      //如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
            arr[i] = arr[k];
            i = k;
        }else
            break;
    }
    arr[i] = temp;      //将temp值放到最终的位置
}

 

3、交换排序

冒泡排序

  • 算法思路:
    1. 将序列当中的左右元素,依次比较,保证右边的元素始终大于左边的元素;( 第一轮结束后,序列最后一个元素一定是当前序列的最大值)
    2. 对序列当中剩下的n-1个元素再次执行步骤1。
    3. 对于长度为n的序列,一共需要执行n-1轮比较。

如图为第一轮起泡

 

// n为数组的长度
public void bubbleSort(int a[], int n){
    for(int i = 0 ; i < n-1; i++) {
        for(int j = 0; j < n-i-1; ++j) {
            if(a[j] > a[j+1]){
                int tmp = a[j];
                a[j] = a[j+1];
                a[j+1] = tmp;
            }
        }
    }
}

 

快速排序

  • 算法思想
    1. 从序列当中选择一个基准数(temp),在这里我们选择序列当中第一个数最为基准数
    2. 将序列当中的所有数依次遍历,比基准数大的位于其右侧,比基准数小的位于其左侧
    3. 重复步骤1.2,直到所有子集当中只有一个元素为止。

image.png

 

public void isPalindrome(int[] arr, int begin, int end) {
    if(begin < end){        //区间中不是只有一个数
        int temp = arr[begin];      //取区间中第一个数为基准数
        int i = begin;      //从左到右查的指针
        int j = end;        //从右到左查的指针
        while(i < j){
            while(i < j && arr[j] > temp)       //右边的数大于基准数时,不进行操作,继续向左查找
                j--;
            arr[i] = arr[j];        //将右边小于等于基准元素的数填入右边相应位置
            while(i < j && arr[i] <= temp)      //左边数小于等于基准数时,不进行操作,继续向右查找
                i++;
            arr[j] = arr[i];        //将左边大于基准元素的数填入左边相应位置
        }
        arr[i] = temp;      //将基准元素填入相应位置
        isPalindrome(arr, begin, i-1);      //对基准元素的左边子区间进行快速排序
        isPalindrome(arr, i+1, end);        //对基准元素的右边子区间进行快速排序
    }else
        return;
}

 

4、归并排序

  • 推荐博客
  • 基本思想:归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

image.png

 

//排序是直接调用的方法
public void sort(int[] arr){
    //在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
    int[] temp = new int[arr.length];
    sort(arr, 0, arr.length-1, temp);
}
//将数组分成小数组,并调用合并排序方法
private void sort(int[] arr, int left, int right, int[] temp){
    if(left < right){
        int mid = (left+right)/2;
        sort(arr, left, mid, temp);     //左边归并排序,使得左子序列有序
        sort(arr, mid+1, right, temp);      //右边归并排序,使得右子序列有序
        merge(arr, left, mid, right, temp);     //将两个有序子数组合并操作
    }
}
//对数组进行合并排序,并将排序好的数组放回原数组arr中
private void merge(int[] arr, int left, int mid, int right, int[] temp){
    int i = left;       //左序列指针
    int j = mid+1;      //右序列指针
    int t = 0;      //临时数组指针
    while (i<=mid && j<=right){
        if(arr[i] <= arr[j]){
            temp[t++] = arr[i++];
        }else {
            temp[t++] = arr[j++];
        }
    }
    while(i<=mid){      //将左边剩余元素填充进temp中
        temp[t++] = arr[i++];
    }
    while(j<=right){        //将右序列剩余元素填充进temp中
        temp[t++] = arr[j++];
    }
    //将temp中排好序的元素全部拷贝到原数组中
    t = 0;
    while(left <= right){
        arr[left++] = temp[t++];
    }
}

 

5、利用桶的概念的排序

差异

  • 基数排序:根据键值的每位数字来分配桶;
  • 计数排序:每个桶只存储单一键值;
  • 桶排序:每个桶存储一定范围的数值;

计数排序

  • 算法步骤:
    • 找出待排序的数组中最大和最小的元素
    • 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
    • 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
    • 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
  • 特征:
    • 当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。

 

 @Override
public int[] sort(int[] sourceArray) throws Exception {
    // 对 arr 进行拷贝,不改变参数内容
    int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
    int maxValue = getMaxValue(arr);
    return countingSort(arr, maxValue);
}

private int[] countingSort(int[] arr, int maxValue) {
    int bucketLen = maxValue + 1;
    int[] bucket = new int[bucketLen];

    for (int value : arr) {
        bucket[value]++;
    }

    int sortedIndex = 0;
    for (int j = 0; j < bucketLen; j++) {
        while (bucket[j] > 0) {
            arr[sortedIndex++] = j;
            bucket[j]--;
        }
    }
    return arr;
}

private int getMaxValue(int[] arr) {
    int maxValue = arr[0];
    for (int value : arr) {
        if (maxValue < value) {
            maxValue = value;
        }
    }
    return maxValue;
}

 

桶排序

  • 桶排序:是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要
  • 为了使桶排序更加高效,我们需要做到这两点:
    • 在额外空间充足的情况下,尽量增大桶的数量
    • 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
  • 到输入的元素可以均匀的分配到每一个桶中时最快,当输入的元素分配到一个桶中时最慢

image.png

 

private static final InsertSort insertSort = new InsertSort();

@Override
public int[] sort(int[] sourceArray) throws Exception {
    // 对 arr 进行拷贝,不改变参数内容
    int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
    return bucketSort(arr, 5);
}

private int[] bucketSort(int[] arr, int bucketSize) throws Exception {
    if (arr.length == 0) {
        return arr;
    }
    //获取数组中的最小值和最大值
    int minValue = arr[0];
    int maxValue = arr[0];
    for (int value : arr) {
        if (value < minValue) {
            minValue = value;
        } else if (value > maxValue) {
            maxValue = value;
        }
    }
    //返回小于等于x的最大整数:Math.floor(x);
    int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;
    int[][] buckets = new int[bucketCount][0];

    // 利用映射函数将数据分配到各个桶中
    for (int i = 0; i < arr.length; i++) {
        int index = (int) Math.floor((arr[i] - minValue) / bucketSize);
        buckets[index] = arrAppend(buckets[index], arr[i]);
    }

    int arrIndex = 0;
    for (int[] bucket : buckets) {
        if (bucket.length <= 0) {
            continue;
        }
        // 对每个桶进行排序,这里使用了插入排序
        bucket = insertSort.sort(bucket);
        for (int value : bucket) {
            arr[arrIndex++] = value;
        }
    }

    return arr;
}

//自动扩容,并保存数据
private int[] arrAppend(int[] arr, int value) {
    arr = Arrays.copyOf(arr, arr.length + 1);
    arr[arr.length - 1] = value;
    return arr;
}

 

基数排序

  • 算法原理:将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数

如果最大数有更多位可继续进行比较

 

@Override
public int[] sort(int[] sourceArray) throws Exception {
    // 对 arr 进行拷贝,不改变参数内容
    int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
    int maxDigit = getMaxDigit(arr);
    return radixSort(arr, maxDigit);
}
//获取最高位数
private int getMaxDigit(int[] arr) {
    int maxValue = getMaxValue(arr);
    return getNumLenght(maxValue);
}
//获取最大值
private int getMaxValue(int[] arr) {
    int maxValue = arr[0];
    for (int value : arr) {
        if (maxValue < value) {
            maxValue = value;
        }
    }
    return maxValue;
}
//获取数字的位数
protected int getNumLenght(long num) {
    if (num == 0) {
        return 1;
    }
    int lenght = 0;
    for (long temp = num; temp != 0; temp /= 10) {
        lenght++;
    }
    return lenght;
}

private int[] radixSort(int[] arr, int maxDigit) {
    int mod = 10;
    int dev = 1;

    for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
        // 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
        int[][] counter = new int[mod * 2][0];

        for (int j = 0; j < arr.length; j++) {
            int bucket = ((arr[j] % mod) / dev) + mod;
            counter[bucket] = arrayAppend(counter[bucket], arr[j]);
        }

        int pos = 0;
        for (int[] bucket : counter) {
            for (int value : bucket) {
                arr[pos++] = value;
            }
        }
    }
    return arr;
}

/**
 * 自动扩容,并保存数据
 *
 * @param arr
 * @param value
 */
private int[] arrayAppend(int[] arr, int value) {
    arr = Arrays.copyOf(arr, arr.length + 1);
    arr[arr.length - 1] = value;
    return arr;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值