排序算法

排序

在这里插入图片描述

方法比较

方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性
选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)不稳定???
冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)稳定
插入排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)稳定
希尔排序 O ( n 1.3 ) O(n^{1.3}) O(n1.3)??? O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)稳定
归并排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n ) O(n) O(n)稳定
快速排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n ) O(n) O(n)稳定
堆排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) O ( 1 ) O(1) O(1)不稳定
计数排序 O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k)稳定
桶排序 O ( n + k ) O(n+k) O(n+k) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n + k ) O(n+k) O(n+k)稳定
基排序 O ( n k ) O(nk) O(nk) O ( n k ) O(nk) O(nk) O ( n k ) O(nk) O(nk) O ( n + k ) O(n+k) O(n+k)稳定

具体算法

(1)交换排序–冒泡排序

两层循环,对里层循环的第 j j j和第 j + 1 j+1 j+1个进行比较,如果乱序则交换,里层每循环结束会将最大的放在里层的最后一位

	public static int[] bubbleSort(int[] arr) {
	    // 冒泡排序 的时间复杂度 O(n^2), 自己写出
	    int temp = 0; // 临时变量
	    boolean flag = false; // 标识变量,表示是否进行过交换
	    for (int i = 0; i < arr.length - 1; i++) {
	    
	        for (int j = 0; j < arr.length - 1 - i; j++) {//有最后i个固定住了
	            // 如果前面的数比后面的数大,则交换
	            if (arr[j] > arr[j + 1]) {
	                flag = true;
	                swap(arr,j,j+1)
	            }
	        }
	
	        if (!flag) { // 在一趟排序中,一次交换都没有发生过
	            break;
	        } else {
	            flag = false; // 重置flag!!!, 进行下次判断
	        }
	    }
	}
	public int[] swap(int[] nums,int a,int b){
	   int tmp = nums[a];
	    nums[a] = nums[b];
	    nums[b] = tmp;
	    return nums;
	}

(2)选择排序

两层循环,里层每次选择最小的放在外层循环的开始位

	public static int[] selectSort(int[] arr){
        for(int i = 0; i < nums.length; i++){
            int minIndex  = i;;
            for(int j = i+1; j < nums.length; j++){
                if(nums[j] < nums[minIndex])minIndex = j;
            }
            nums = swap(nums,i,minIndex);
        }
        return nums;
    }

    public int[] swap(int[] nums,int a,int b){
        int tmp = nums[a];
        nums[a] = nums[b];
        nums[b] = tmp;
        return nums;
    }

(3)插入排序

将数组看作有序表和无序表,将无序表中元素从后往前扫描,插入有序表中的合适位置

//插入排序
    public static int[] insertSort(int[] arr) {
        int preIndex,current;
        for(int i = 1; i < nums.length; i++){
            preIndex = i - 1;
            current = nums[i];//保存待插入的值,while循环时将被有序数组占用
            while(preIndex >=0 && nums[preIndex] > current){
                nums[preIndex + 1] = nums[preIndex];//将大于待插入的数往后挪动
                preIndex --;
            }
            nums[preIndex + 1] = current;
        }
        return nums;
    }

(4)希尔排序

step相同分为同一组,在同一组中先进行排序将数据小的尽量放前面,然后二分减小step来进行更多数据的排序,在同组排序中可以使用冒泡排序法(交换法)和插入排序(移位法)
在这里插入图片描述
交换法:

//冒泡排序法
    public static void shellsortexchane(int[] arr) {
        int step = arr.length / 2;
        int tmp;
        while (step > 0) {//针对步长进行循环
            for (int i = 0; i < arr.length; i++) {//每次+1,挨着堆每组元素进行插入排序
                for (int j = i + step; j < arr.length; j = j + step) {//在当组中依次将同一步长组内的两两交换排序
                    //每组数据为arr[i],arr[i+step],arr[i+2*step],...
                    if (arr[i] > arr[j]) {
                        tmp = arr[i];
                        arr[i] = arr[j];
                        arr[j] = tmp;
                    }
                }
            }
            step = step / 2;
        }
    }

移位法:

//插入法
    public static void shellsortinsert(int[] arr) {
        int step = arr.length / 2;
        int tmp;
        while (step > 0) {//针对步长进行循环
            for (int i = 0; i < arr.length - step; i++) {//开始插入排序
                int j = i + step;
                 //每组数据为arr[i],arr[i+step],arr[i+2*step],...
                tmp = arr[j];
                while (j >= step && arr[j - step] > tmp) {
                    arr[j] = arr[j - step];
                    j = j - step;

                }
                arr[j] = tmp;

            }
            step = step / 2;
        }
    }

(5)归并排序

先进行二分,然后将左右两边数组合并

	public int[] sortArray(int[] nums) {
        mergeSort(0,nums.length - 1,nums);
        return nums;
    }

    public void mergeSort(int left, int right,int[] nums){
        if(left >= right)return;

        int mid = (left + right) / 2;
        mergeSort(left,mid,nums);
        mergeSort(mid+1,right,nums);
        combine(left,mid,right,nums);

    }

    public void combine(int left, int mid, int right,int[] nums){
        //使用辅助数组
        int[] res = new int[nums.length];
        for(int i = 0; i < nums.length; i++){
            res[i] = nums[i];
        }
		//左右数组开始的下标
        int l = left;
        int r = mid+1;
        
        int count = left;//记录合并的下标
        while(l <= mid && r <= right){
            if(res[l] < res[r]){
                nums[count] = res[l];
                l ++;
            }else{
                nums[count] = res[r];
                r++;
            }
            count ++;
        }
        //剩余数组
        while(r <= right){
            res[count] = nums[r];
            r++;
            count ++;
        }
        while(l <= mid){
            nums[count] = res[l];
            l++;
            count ++;
        }

    }

(6 )快速排序

数组中小于等于pivot的数在[0,i],大于pivot的数在[i+1,j]区间


    public void quickSort(int[] nums,int left,int right){
        if(left < right){
            int pivot = partation(nums,left,right);
            quickSort(nums, left , pivot - 1);
            quickSort(nums, pivot + 1, right);
        }
    }

     public int partition(int[] nums,int left,int right){
        int pivot = right;
        int pre = left - 1;//指向小于pivot值的最后一个
        for(int i = left; i < right; i ++){
            if(nums[i] <= nums[pivot]){
                pre ++;
                if(i != pre)swap(nums,pre,i);
            }
        }
        pre ++;
        if(pre < right && nums[pre] > nums[right])swap(nums,pre,right);
        return pre;
    }

    public void swap(int[] nums,int i, int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

(7)堆排序

堆:子结点的值总是大于(小于)父结点
思想:
1)构建初始堆,将待排序列构成一个大顶堆(或者小顶堆),升序大顶堆,降序小顶堆;
2)将堆顶元素与堆尾元素交换,并断开(从待排序列中移除)堆尾元素。
3)重新构建堆。
4)重复2~3,直到待排序列中只剩下一个元素(堆顶元素)。

public int[] sortArray(int[] nums) {
        //创建堆
        for (int i = (nums.length - 1) / 2; i >= 0; i--) {
            //从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(nums, i, nums.length);
        }

        //调整堆结构+交换堆顶元素与末尾元素
        for (int i = nums.length - 1; i > 0; i--) {
            //将堆顶元素与末尾元素进行交换
            int temp = nums[i];
            nums[i] = nums[0];
            nums[0] = temp;

            //重新对堆进行调整
            adjustHeap(nums, 0, i);
        }
        return nums;
    }

    private static void adjustHeap(int[] arr, int parent, int length) {
        //将temp作为父节点
        int temp = arr[parent];
        //左孩子
        int lChild = 2 * parent + 1;

        while (lChild < length) {
            //右孩子
            int rChild = lChild + 1;
            // 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点
            if (rChild < length && arr[lChild] < arr[rChild]) {
                lChild++;
            }

            // 如果父结点的值已经大于孩子结点的值,则直接结束
            if (temp >= arr[lChild]) {
                break;
            }

            // 把孩子结点的值赋给父结点
            arr[parent] = arr[lChild];

            //选取孩子结点的左孩子结点,继续向下筛选
            parent = lChild;
            lChild = 2 * lChild + 1;
        }
        arr[parent] = temp;
    }

(8)计数排序

不是基于比较,适用于输入数据为一定范围内的整数,如题leedcode75. 颜色分类
算法思路:
1)找到输入数据的最大和最小值
2)统计数组中每个值为i的数出现的次数,存入数组C的第i项
3)对所有的计数累加,对C从第一个数开始,每项和前一项相加
4)反向填充目标数组:将每个元素i放在心数组的第C[i]想,每放一个元素将C[i]-1
leedcode75. 颜色分类:给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

	public void sortColors(int[] nums) {
        //根据最大最小确定统计数组大小
        int[] colors = new int[3];

        //统计每一项的个数到统计数组里
        for(int i = 0; i < nums.length; i++){
            colors[nums[i]] ++;
        }

        //填充统计数组到原来的nums
        int count = 0;
        for(int i = 0; i < nums.length; i++){
            while(colors[count] <= 0){
                count ++;
            } 
            nums[i] = count;
            colors[count] --;
        }
    }

(9)桶排序

假设数据服从均匀分布,将数据分到有限的桶里,然后在桶内分布排序
算法思路:
1)设置一个定量的数组当作空桶;
2)遍历输入数据,并且把数据一个一个放到对应的桶里去;
3)对每个不是空的桶进行排序;
4)从不是空的桶里把排好序的数据拼接起来。
例题:164. 最大间距451. 根据字符出现频率排序692. 前K个高频单词

例题451. 根据字符出现频率排序:给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

	public String frequencySort(String s) {
        HashMap<Character,Integer> map = new HashMap<>();

        //统计词频记录在map中
        for(int i = 0; i < s.length(); i++){
            map.put(s.charAt(i), map.getOrDefault(s.charAt(i),0) + 1);
        }

        // 构造桶的集合
        List<Character>[] buckets = new List[s.length()];
        // 将频率为i的字符放到第i-1个桶里
        for(char key : map.keySet()){
            int times = map.get(key);
            if(buckets[times - 1] == null)buckets[times - 1] = new ArrayList<Character>();
            buckets[times - 1].add(key);
        }

        //将字符拼接到一起
        StringBuilder res = new StringBuilder();
        for(int i = buckets.length - 1; i >= 0; i--){//先高频
            if(buckets[i] != null){
                for (char c : buckets[i]) {// 遍历桶里的每个字符
                // System.out.println(c +" "+ i);
                    for(int j = 0; j <= i; j++){ // 每个字符出现了i+1次
                        res.append(c);
                    }
                }
            }
        }

        return res.toString();
    }

692. 前K个高频单词:给一非空的单词列表,返回前 k 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。

public List<String> topKFrequent(String[] words, int k) {
        HashMap<String,Integer> map = new HashMap<>();

        //统计词频记录在map中
        for(int i = 0; i < words.length; i++){
            map.put(words[i], map.getOrDefault(words[i],0) + 1);
        }

        // 构造桶的集合
        List<String>[] buckets = new List[words.length];
        // 将频率为i的字符放到第i-1个桶里
        for(String key : map.keySet()){
            int times = map.get(key);
            if(buckets[times - 1] == null)buckets[times - 1] = new ArrayList<String>();
            buckets[times - 1].add(key);
        }

        // 对桶内字符进行排序
        for(int i = 0; i < buckets.length; i++){
            if(buckets[i] != null){
                Collections.sort(buckets[i]);
                // System.out.println(buckets[i]);
            }
        }

        //将字符合并输出
        List<String> res = new ArrayList<String>();
        int i = buckets.length - 1;
        for(; i >=0; i--){
            if(buckets[i] != null){
                for(String str : buckets[i]){
                    if(k > 0){
                        res.add(str);
                        k--;
                    }else{
                        return res;
                    }
                }
            }
        }
        return res;
    } 

(10)基数排序

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。
有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
算法思路:
1)取得数组中最大的数,并取得位数
2)arr为原始数组,从最低位开始取每个位组成radix数组
3)对radix进行计数排序

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值