排序算法

(PPT来源:尚硅谷数据结构)

在这里插入图片描述
速度测试:
80000个随机数字排序
冒泡排序:18s
选择排序:3s
插入排序:5s
希尔排序:交换法17s && 移动法 1s
快速排序:1s
归并排序:<1s
基数排序:1s
在这里插入图片描述

插入排序

简单插入排序

初表看作只有首元素有序,从后面 n-1 个元素中依次取出元素,插入有序部分的适当位置
在这里插入图片描述

public static void insertSort(int[] arr){
    for(int i = 1; i < arr.length; i++){
        int insertVal = arr[i]; //待插入的数字
        int insertIndex = i-1; //假设的待插入下标
        //在下标不越界的情况下,一步步向前寻找当前元素能插入的正确位置
        while(insertIndex >= 0 && insertVal < arr[insertIndex]){
        	//抹去了当前元素在数组中的值
            arr[insertIndex + 1] = arr[insertIndex]; 
            insertIndex--;
        }
        //退出循环,说明待插入的位置找到了
        arr[insertIndex + 1] = insertVal;
    }
     System.out.println(Arrays.toString(arr));
 }

1. 直接排序

适用于基本有序,数据量不大的排序表
从后向前查找应插入的位置,比较元素的次数为 n-1
时间复杂度:O( n² )
稳定性:√

2. 折半插入查找

减少了比较元素的次数,为 nlogn
时间复杂度:O( n² )
稳定性:√

3. 希尔排序

简单插入排序时,最坏情况下移动次数过于多:
在这里插入图片描述
优化:缩小增量
将待排序表分割成 d1 个特殊的子表(所有距离为d1倍的放在一起),进行插入排序;再分割成更小的 d2 个特殊的子表插入排序;最后分别有序后再整体进行一次插入排序——一般d1=n/2;d2=d1/2…
在这里插入图片描述
在这里插入图片描述
时间复杂度:特定范围O(n^1.3),最坏情况为O( n² )
稳定性:×

public static void shellSort移动法(int[] arr){
    int len = arr.length;
    int d = len / 2;
    while(d > 0){
        for(int i = d; i < len; i++){
            int j = i; //保存待插入位置的下标
            int tmp = arr[j]; //保存待插入元素的值
            //插入排序
            if(arr[j-d] > arr[j]){
                while(j-d >= 0 && arr[j-d] > tmp){
                	arr[j] = arr[j-d];
                    j -= d;
                }
                //退出while循环即找到了j适当的插入位置
                arr[j] = tmp;
            }
        }
        d /= 2;
    }
    System.out.println(Arrays.toString(arr));
}

交换排序

1. 冒泡排序

两指针从前向后,两两比较,依据大小交换顺序
在这里插入图片描述

  • 一趟确定一个元素的正确的位置(正序确定最大/逆序确定最小);
    每趟排序次数逐渐减小;
    最坏情况一共进行 n-1 次循环
    优化:如果在一趟排序中,一次都没有交换过,说明已经有序
  • 平均时间复杂度:O( n² )
    稳定性:√
public static void BubbleSort(int[] arr){
	int n = arr.length;
    int tmp = 0; //数组交换位置时的临时变量
    for(int i = 0; i < n-1; i++){ //总共进行n-1次
    	boolean flag = false;
        for(int j = 0; j < n-1-i; j++){ //n-1-i 即此趟需要排序的个数
        //如果前面的数 > 后面的数,交换
            if(arr[j] > arr[j+1]){
                tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
                flag = true;
            }
        }
        if(flag = false){ //在一趟排序中若没有交换过数据,说明已经有序
            return;
        }
    }
}

2. 快速排序

对冒泡排序的改进
做法:前找找,后找找~ ~ ~
将待排序数列中任选一个元素作为基准 pivot,一趟排序后会将基准数放在其正确的位置上;之后再递归对左右子表进行快速排序
在这里插入图片描述
平均时间复杂度:最坏情况蜕变为冒泡排序,O(n²)
稳定性:×

  • 中间值作为基准
public class 快速排序 {
    public static void main(String[] args) {
        int[] m = new int[]{3,7,8,0,9,2,5,4};
        quickSort(m, 0, m.length-1);
        System.out.println(Arrays.toString(m));
    }
    public static void quickSort(int[] arr, int left, int right){
        int l = left;
        int r = right;
        int mid = l + (r - l)/2;
        int pivot = arr[mid];
        while(l < r){
            //直到找到一个需要交换的左值、右值
            while(arr[l] < pivot){
                l++;
            }
            while(arr[r] > pivot){
                r--;
            }
            if(l >= r){ //左右值已经平衡
                break;
            }
            int tmp = arr[l];
            arr[l] = arr[r];
            arr[r] = tmp;
            //当左值/右值寻找完后,则只用再次寻找另一边
            if(arr[l] == pivot){
                r--;
            }
            if(arr[r] == pivot){
                l--;
            }
            if(l==r){
                l++;
                r--;
            }
            if(left < r){
                quickSort(arr, left, r);
            }
            if(l < right){
                quickSort(arr, l, right);
            }
        }
    }
}
  • 第一个数作为基准(劣质分割,主要看思想)
import java.util.*;

public class Finder { //快速排序思路,第k大的数字即排序后index=k-1的数字
    public int findKth(int[] a, int n, int K) {
        // write code here
        quickSort(a, 0, n-1);
        return a[n-K]; //从小到大排列,所以要从后向前查看选择第k个数
    }
    
    public void quickSort(int[] a, int start, int end){
        if(start < end){ //递归
            int i = partition(a, start, end); //寻找中心基准数,并开始一趟排序
            quickSort(a, i+1, end);
            quickSort(a, start, i-1);
        }
    }
    
    public int partition(int[] a, int start, int end){
        int x = a[start];
        int i = start;
        for(int j = start+1; j <= end; j++){
            if(a[j] < x){
                swap(a, i+1, j);
                i++;
            }
        }
        swap(a, start, i); //将基准数放在正确的位置上
        return i; //返回基准数后,以其为界限划分处两个子列表进行快速排序
    }
    
    public void swap(int[] a, int i, int j){
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
}

选择排序

1. 简单排序

排列好前 i 个元素后,每次从 i 之后挑选最小的元素放到 i + 1 的位置
在这里插入图片描述

  • 每轮排序都是一个寻找最小元素的循环;
    寻找最小元素:先假定当前数最小,依次与后续元素比较,有更小的,就重新确定最小数的值与下标;
    一共需要 n - 1 轮排序;
  • 平均时间复杂度:O( n² )
    稳定性:×
public static void selectSort(int[] arr){
    int n = arr.length;
    for(int i = 0; i < n-1; i++){
        //每轮都寻找最小元素
        int index = i;
        int min = arr[i];
        for(int j = i+1; j <= n-1; j++){
            //重置最小值
            if(min > arr[j]){
                index = j;
                min = arr[j];
            }
        }
        //优化:当 下标 变换,说明当前数不是最小数时,才需要交换;
        //当前数字碰巧为最小数时不需要交换
        if(index != i){
            arr[index] = arr[i];
            arr[i] = min;
        }
    }
    System.out.println(Arrays.toString(arr));
}

2. 堆排序

  • 堆:
  1. 如何将堆调整为小根堆
    输出堆顶元素,以堆中最后一个元素代替之 → 筛选过程从堆顶至叶子调整,即将新根节点与左右孩子进行比较,小的交换上去 → 反复筛选

元素较多时有效率,建堆操作
平均时间复杂度:O( nlogn )
稳定性:×

归并排序(多路归并 是外部排序)

分 + 治:将两个或两个以上的有序列表组合成一个新的有序表
时间复杂度线性增长
eg:2-路归并排序
在这里插入图片描述
在这里插入图片描述

public class 归并排序 {
    public static void main(String[] args) {
        int[] arr = new int[]{3,7,8,0,9,2,5,4};
        int[] tmp = new int[arr.length];
        mergeSort(arr, 0, arr.length-1, tmp);
        System.out.println(Arrays.toString(arr));
    }

    public static void mergeSort(int[] arr, int left, int right, int[] tmp) {
        if(left < right){
            int mid = (left + right)/2;
            //向左右递归进行分解
            mergeSort(arr, left, mid, tmp);
            mergeSort(arr, mid+1, right, tmp);
            merge(arr, left, mid, right, tmp);
        }
    }

    public static void merge(int[] arr, int left, int mid, int right, int[] tmp){
        int i = left;
        int j = mid + 1;
        int t = 0;

        //左右两边已经有序,按照规则拷贝到 tmp 中
        while(i <= mid && j <= right){
            //判断两个分组的各个元素大小,按照大小拷贝到 tmp
            if(arr[i] <= arr[j]){
                tmp[t++] = arr[i];
                i++;
            }else{
                tmp[t++] = arr[j];
                j++;
            }
        }
        //左边分组还有剩余
        if(i <= mid){
            tmp[t++] = arr[i++];
        }
        if(j <= right){
            tmp[t++] = arr[j++];
        }
        //并不是每次都将tmp的所有元素拷贝到arr
        t = 0;
        int tmpLeft = left;
        while(tmpLeft <= right){
            arr[tmpLeft++] = tmp[t++];
        }
    }
}

时间复杂度:O(nlogn)
稳定性:√

基数排序

public class 基数排序 {
    public static void main(String[] args) {
        int[] arr = {53, 3, 542, 748, 14, 214};
        RadixSort(arr);
    }
    public static void RadixSort(int[] arr){
        int max = arr[0]; //数组中最大数
        for(int i = 0; i < arr.length; i++){
            if(arr[i] > max){
                max = arr[i];
            }
        }
        int maxLength = (max + "").length(); //最大数的位数,决定了需要循环几轮
        int[][] bucket = new int[10][arr.length];
        int[] bucketElementCounts = new int[10]; //各个桶中放入数据的个数

        for(int i = 0, n = 1; i < maxLength; i++, n*=10){
            //输入元素
            for(int j = 0; j < arr.length; j++){
                int digitOfElement = arr[j] / n % 10;
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                bucketElementCounts[digitOfElement]++;
            }
            //取出元素
            int index = 0;
            for(int k = 0; k < 10; k++){
                if(bucketElementCounts[k] != 0){
                    for(int l = 0; l < bucketElementCounts[k]; l++){
                        arr[index++] = bucket[k][l];
                    }
                }
                bucketElementCounts[k] = 0; //取完值后将桶内元素数置0
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

时间复杂度:d 趟分配和收集 O(d(n+r))
稳定性:√

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值