排序算法a

本文详细介绍了各种排序算法,包括冒泡排序、插入排序、选择排序、归并排序、快速排序、桶排序、计数排序和基数排序。讨论了算法的执行效率、内存消耗和稳定性,并提出了针对链表和快速排序的优化策略。此外,还涉及了排序相关的编程题目,如合并两个有序数组、最接近的三数之和、字母异位词分组和数组中的第k个最大元素的解题思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

排序算法

最经典的、最常用的排序算法:冒泡排序、插入排序、选择排序、归并排序、快速排序、计数排序、基数排序、桶排序。

排序算法的三种要素:

  1. 排序算法的执行效率
    最好、最坏、平均情况时间复杂度;
    时间复杂度的系数、常数、低阶;
    比较次数和交换(移动)次数;
  2. 排序算法的内存消耗
    原地排序就是空间复杂度是O(1)的排序算法
  3. 排序算法的稳定性
    待排序列的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。

1.冒泡排序

 public void bubbleSort(int[] a,int n){
        //n表示数据的size
        if(n <= 1)
            return ;
        for(int i = 0;i<n;i++){
            boolean flag = false;
            for(int j =0;j<n-i-1 ;j++){
                if(a[j]>a[j+1]){
                    int temp = a[j];
                    a[j]=a[j+1];
                    a[j+1]=temp;
                    flag =true;
                }
            }
            if(!flag)
                break;
        }
    }

2.插入排序

public void insertionSort(int[] a,int n){
        if (n<=1)
            return;
        for(int i = 1;i<n;i++){
            int temp = a[i];
            int j = i-1;
            for(;j>=0;j--){
                if(a[j]>temp)
                    a[j+1]=a[j];
                else{
                    break;
                }
            }
            a[j+1]= temp;
        }
    }

3.选择排序

 public void selectionSort(int[] a,int n){
        if(n<=1)
            return;
        for(int i = 0;i<n;i++){
            int min = i;
            for(int j = i+1;j<n;j++){
                if(a[j]<a[min])
                    min = j;
            }
            int temp = a[min];
            a[min]= a[i];
            a[i]= temp;
        }
    }

如果数据存储在链表中,这三种排序算法还能工作吗?如果能,那相应的时间、空间复杂度又是多少呢?
一般而言,考虑只能改变节点位置,冒泡排序相比于数组实现,比较次数一致,但交换时操作更复杂;插入排序,比较次数一致,不需要再有后移操作,找到位置后可以直接插入,但排序完毕后可能需要倒置链表;选择排序比较次数一致,交换操作同样比较麻烦。综上,时间复杂度和空间复杂度并无明显变化,若追求极致性能,冒泡排序的时间复杂度系数会变大,插入排序系数会减小,选择排序无明显变化。

4. 归并排序

int [] aux;
    public void merge_sort(int[]a,int n){
        aux = new int[n];
        merge_sort_c(a,0,n-1);
    }
    public void merge_sort_c(int[]a,int lo,int hi){
        if(lo>=hi)
            return;
        int mid = (lo+hi)/2;
        merge_sort_c(a,lo,mid);
        merge_sort_c(a,mid+1,hi);
        merge(a,lo,mid,hi);
    }
    public void merge(int[]a,int lo,int mid,int hi){
        int i = lo,j = mid+1;
        for(int k = lo;k<=hi;k++){
            aux[k]=a[k];
        }
        for(int k = lo; k<=hi; k++){
            if(lo>mid)
                a[k]=aux[j++];
            else if(j>hi)
                a[k]=aux[i++];
            else if(aux[i]<aux[j])
                a[k]=aux[i++];
            else
                a[k]=aux[j++];
        }
    }

5. 快速排序

 public void quick_sort(int[] a ,int n){
        quick_sort_c(a,0,n-1);
    }
    public void quick_sort_c(int[]a,int lo,int hi){
        if(lo>=hi)
            return;
        int mid = partition(a,lo,hi);
        quick_sort_c(a,lo,mid-1);
        quick_sort_c(a,mid+1,hi);
    }
    public int partition(int[]a,int lo,int hi){
        int i = lo,j = hi+1;
        int v = a[lo];
        while (true){
            if(i<hi&&a[++i]<v){
                if(i==hi){
                    break;
                }
            }
            if(j>lo&&v<a[--j]){
                if(j==lo){
                    break;
                }
            }
            if(i>=j)//-----重点标记
                break;
            exch(a,i,j);
        }
        exch(a,lo,j);//将最左值与j所在位置的值进行交换
        return j;

    }
    public void exch(int[] a,int i,int j){
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

如何优化快速排序?
选择一个好的分区点,如果分区点选择的不好,则会造成快速排序退换成复杂度为O(n2)
1.三数取中法;
2.随机法;

归并和快排的时间复杂度是O(nlogn)
归并排序的处理过程是由下而上的,先处理子问题,在合并。而快排相反,处理过程是由上而下的,先分区,然后在处理子问题。
递归虽然是稳定的,但是它是非原地排序算法,合并函数无法在原地执行,而快速排序却可以原地分区,实现原地排序

6.桶排序

核心思想:就是将要排序的数据分到几个有序的桶里,每个桶里的数据在单独进行排序。桶内排完序后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。
桶排序对要排数据的要求是非常苛刻的。
首先要排序的数据需要很容易分到m个桶中去,且桶之间有着天然的大小顺序。
其次,数据在各个桶之间的分布是比较均匀的。针对桶内的数据量较大,我们可以继续划分

7.计数排序

计数排序是桶排序的特例。当要排序的n个数据,所处的范围并不大,比如最大为k,我们就可以把数据划分成k个桶,每个桶内的数据值都是相同的,省掉了桶内排序的时间。
计数排序的计数如何而来???
计数排序只能用在数据范围不大的场景中,如果数据范围k比要排序的数据n大很多,就不适合用计数排序了,而且,计数排序只能给非负整数排序,如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下转化为非负整数。

8. 基数排序

基数排序对要排序的数据是有要求的,要求可以分割出独立的“位”来比较,而且位之间有递进关系,如果a数据的高位比b数据大,那剩下的低位就不用比较了。除此之外,每一位的数据范围不能太大,要可以用线性排序算法来排序,否则,基数排序的时间复杂度就无法做到O(n)了。
在这里插入图片描述

9. 排序相关题目

9.1 合并两个有序数组

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
思路:利用归并排序中的merge方法来求解

 public void merge(int[] nums1, int m, int[] nums2, int n) {
        int[]temp = new int[m];
        for(int i =0;i<m;i++){
            temp[i]=nums1[i];
        }
        int i = 0,j=0;
        for(int k = 0;k<m+n;k++){
            if(i>=m){
                nums1[k]=nums2[j++];
            }else if(j>=n){
                nums1[k]=temp[i++];
            }else if(temp[i]<nums2[j]){
                nums1[k]=temp[i++];
            }else{
                nums1[k]=nums2[j++];
            }
        }
    }

9.2 最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
思路:直接进行三层循环可以暴力求解,但是太过时间复杂度过高,所以可以选择进行优化。用两个指针进行

public int threeSumClosest(int[] nums, int target) {
        //思路一:三层循环,会越界
        //思路二:固定一层,其他两层优化
        Arrays.sort(nums);
        int best = 1000000;
        for(int i =0;i<nums.length;i++){
            int j = i+1,k=nums.length-1;
            while(j<k){
                int sum = nums[i]+nums[j]+nums[k];
                if(sum == target){
                    return sum;
                }
                if(Math.abs(best-target)>Math.abs(sum-target)){
                    best = sum;
                }
                if(sum<target){
                    int j0=j;
                    while(j0<k&&nums[j0]==nums[j]){
                        j0++;
                    }
                    j=j0;
                }else{
                    int k0=k;
                    while(k0>j&&nums[k0]==nums[k]){
                        k0--;
                    }
                    k=k0;
                }
            }

        }
        return best;
    }

此题也可以扩展为求四数之和,只不过在此基础上,在多一层循环,需要注意优化,相同值的情况需要去除掉,双指针

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        ArrayList<List<Integer>> list = new ArrayList<List<Integer>>();
        if(nums==null || nums.length<4){
            return list;
        }
        Arrays.sort(nums);
        
        for(int i = 0;i<nums.length-3;i++){
            if(i>0 && nums[i]==nums[i-1]){
                continue;
            }
            if(nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target){
                break;
            }
            for(int j =i+1;j<nums.length-2;j++){
                if(j>(i+1)&&nums[j]==nums[j-1]){
                    continue;
                }
                if(nums[i]+nums[j]+nums[j+1]+nums[j+2]>target){
                    break;
                }
                int left = j+1,right=nums.length-1;
                while(left<right){
                    int sum = nums[i]+nums[j]+nums[left]+nums[right];
                    if(sum==target){
                        list.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
                        while(left<right && nums[left]==nums[left+1]){
                            left++;
                        }
                        left++;
                        while(left<right && nums[right]==nums[right-1]){
                            right--;
                        }
                        right--;
                    }else if(sum <target){
                        left++;
                    }else{
                        right--;
                    }

                }
            }

        }
        return list;

    }
}

9.3 字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母都恰好只用一次。
示例 1:
输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出: [[“bat”],[“nat”,“tan”],[“ate”,“eat”,“tea”]]
思路:用hashmap,将异位词排序后作为key,异位词作为value,进行存储,主要的就是利用一些java高级语法。

public List<List<String>> groupAnagrams(String[] strs) {
        Map<String,List<String>> map = new HashMap<String,List<String>>();
        for(String str : strs){
            char[] chararray = str.toCharArray();
            Arrays.sort(chararray);
            String key = new String(chararray);
            List<String> list = map.getOrDefault(key,new ArrayList<String>());
            list.add(str);
            map.put(key,list);
        }
        return new ArrayList<List<String>>(map.values());
    }

9.4数组中的第k个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

思路:利用快排的partition来进行判断,需要注意的是边界条件

public int findKthLargest(int[] nums, int k) {
        if(nums==null) {
            return -1;
        }
        int mid = partitions(nums,0,nums.length-1);
        while(mid != k-1){
            if (mid>k-1){
                mid = partitions(nums,0,mid-1);
            }else{
                mid = partitions(nums,mid+1,nums.length-1);
            }
        }
        return nums[mid];
    }
    public int partitions(int[]nums,int lo,int hi){
        int v = nums[lo];
        int i =lo,j=hi+1;
        while(true){
            while(i<hi&&nums[++i]>v){//开始的条件如果大于hi了也会报错
                if(i==hi){
                    break;
                }
            }
            while (j>lo&&nums[--j]<=v){//需要小于等于号
                if(j==lo){
                    break;
                }
            }
            if(i>=j){
                break;
            }
            exch(nums,i,j);
        }
        exch(nums,lo,j);
        return j;
    }
    public void exch(int[] a,int i,int j){
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值