【数据结构】排序算法全方位剖析

【数据结构】排序算法全方位剖析



前言

1.排序算法是数据结构当中的重要知识,是将数据按照规定的顺序进行重新排序。
2.不同的排序算法性能差异很大,根据代码的实现,可以具备不同的时间复杂度、空间复杂度、以及稳定性。


提示:下面我将为大家详细解析六种常见的排序算法。

一、插入排序

1.思路:

插入排序类似整理扑克牌,假设手里有一副乱序的扑克牌,假设扑克牌的第一个元素为有序,从第二张牌开始,将第二张牌插入到前面已经有序的牌当中,重复上述步骤,可以实现所有牌有序。

2.步骤

1.从第一个元素开始,默认该元素已被排序
2.取下一个元素为tmp,从已排序的元素序列从后往前扫描
3.如果该元素大于tmp,则将该元素移到下一位
4.重复步骤3,直到找到已排序元素中小于等于tem的元素
5.将tmp插入到有序元素中的合适位置
6.重复步骤2~5,直到遍历完数组

3.代码实现

 public static void insertSort(int[] array){
        int i;
        int j;
        int len= array.length; //数组长度
         外层循环,从数组的第二个元素开始(假设第一个元素有序)  
        for ( j = 1; j <len ; j++) {
            int tmp=array[j]; //将待插入元素放入tmp变量
            
            // 内层循环,从当前元素的前一个位置开始向前扫描,找到应该插入的位置 
            for ( i = j-1; i >=0 ; i--) {
            // 如果扫描到的元素大于tmp,则将其向后移动一位  
                if(array[i]>tmp){
                    array[i+1]=array[i];
                }else{
                    break;
                }
            }
            array[i+1]=tmp; //插入至合适位置
        }
    }

4.演示图

在这里插入图片描述

5.总结:

时间复杂度:O(N²)且数组越接近有序,效率越高
空间复杂度:O(1)
稳定性:稳定


二、希尔排序

1.思路

基于插入排序算法的优化,当数组的元素趋于有序时,插入排序的效率更高,若是提前将数组进行预排序(让数组趋于有序),再进行插入排序,此时效率单比插入排序会高很多,这便是希尔排序。

2.步骤

1.先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再取一个比第一增量小的整数作为第二增量,重复上述操作
便可实现数组元素趋于有序。
2.当增量的大小减到1时,进行一次直接插入排序,排序完成。

3.代码实现

   public static void ShellSort(int[] array){
        int len= array.length;
        int gap=len/2;
        while(gap>=1){
            int i;
            int j;
            for(i=gap;i<len;i++){
                int tmp=array[i];
                for (j = i-gap; j >=0 ; j-=gap) {
                    if(array[j]>tmp){
                        array[j+gap]=array[j];
                    }else{
                        break;
                    }
                }
                array[j+gap]=tmp;
            }
            gap/=2;
        }
    }

4.演示图

在这里插入图片描述

4.总结:

1.当gap>1时,都是预排序,让整体更趋于有序,gap==1时,进行最后一次插入排序
2.时间复杂度:不确定,因为gap取值不同,导致时间复杂度不固定,但整体近似于O(N^1.25)
3.空间复杂度O(1)
4.稳定性:不稳定


三、选择排序

1.思路:

遍历数组,每趟遍历时找出数组的最大值,以及最小值,将最大值和最小值元素放至数组最左边和最右边,接着除去已经排好的的区域,对未排序区域继续进行找最大和最小值操作。

2.代码实现

  public static void choiceSort(int[] array){
        int left=0;
        int right=array.length-1;
        while(left<right){
            int maxIndex=left;
            int minIndex=left;
            for (int i = left; i <=right ; i++) {
                if(array[i]>array[maxIndex]){
                    maxIndex=i;
                }
                if(array[i]<array[minIndex]){
                    minIndex=i;
                }
            }
            swap(array,minIndex,left);
            if(left==maxIndex){
               maxIndex=minIndex;
            }
            swap(array, maxIndex, right);
            left++;
            right--;
        }
    }

3.总结:

时间复杂度:O(N²)
空间复杂度:O(1)
稳定性:不稳定


四、堆排序

1.思路:

堆排序是基于数据结构中的比较排序算法。利用完全二叉树,进行排序。
升序:建大根堆 降序:建小根堆

将堆顶元素与末尾元素进行交换,在减少堆中有效元素个数,再将剩余元素调整成新堆,反复进行此操作,即可完成排序。

2.代码实现

public static void swap(int[] array,int i,int j){
        int tmp=array[i];
        array[i]=array[j];
        array[j]=tmp;
    }
    public static void heapSort(int[] array){
       int len=array.length;
        creatHeap(array); //创小根堆
        for (int i = array.length-1;i >0; i--) {
            swap(array,0,i);
            len--;
            siftDown(array,0,len);
        }
    }
    private static void creatHeap(int[] array){
        int par=(array.length-1)/2;
        for (; par >=0 ; par--) {
            siftDown(array,par,array.length);
        }
    }
    private static void siftDown(int[] array,int par,int len){
        int child=2*par+1;
        while(child<len){
            //调整child为子节点最小值的下标
            if(child+1<len&&array[child]>array[child+1]){
                child=child+1;
            }
            if(array[par]>array[child]){
                swap(array,child,par);
            }
            par=child;
            child=2*par+1;
        }
    }

3.总结:

时间复杂度:O(N*logN)
空间复杂度:O(1)
稳定性:不稳定


五、快速排序

1.思路:

选一个关键字key,一般为最左边或最右边,然后利用key将数组中元素分为左右两部分:左部分值都比key小,右部分值都比key大,再利用递归思想选取左部分key重复操作和右部分的key重复操作。

2.代码实现:

    public static void quick(int[] array){
        quickSort(array,0,array.length-1);
    }
    public static void quickSort(int[] array,int left,int right){


        if(left>=right)
            return;
        int pivot=partition(array,left,right);
        quickSort(array,left,pivot-1);
        quickSort(array,pivot+1,right);
    }
    private static int partition(int[] array,int left,int right){
        int tmp=array[left];
        while(left<right){
            while(left<right&&array[right]>=tmp){
                right--;
            }
            array[left]=array[right];
            while(left<right&&array[left]<=tmp){
                left++;
            }
            array[right]=array[left];
        }
        array[left]=tmp;
        return left;
    }

3.总结:

时间复杂度:O(NlogN)
空间复杂度:O(n
logN)
稳定性:不稳定


六、归并排序

1.思路:

①先将数组中所有元素拆散,分成未排序的子数组,直到每个子数组只包含一个元素。
②依次将子数组中元素与相邻的子数组进行排序,再合并成有序的子数组。
③重复执行合并和排序的过程,直到最终合并成一个完整的、有序的数组。

2.代码实现

   public static void mergeSort(int[] array){
        int left=0;
        int right=array.length-1;
        apart(array,left,right);
    }
    /**
     * 递归将数组分割成更小的部分,直到每个部分只有一个元素或为空。
     * 随后调用 merge 方法来合并这些部分。
     * */
    private static void apart(int[] array,int left,int right){
        if(left==right){
            return;
        }
        int mid=(left+right)/2;

        //将数组中所有元素分离
        apart(array,left,mid); //递归地分割数组的左半部分
        apart(array,mid+1,right); //递归地分割数组的右半部分

        // 合并分割后的两个子数组
        merge(array,left,right,mid);
    }

    /**
     * 合并两个已排序的子数组,并将合并后的结果存储回原始数组中。
     *
     * @param array 原始数组,包含两个待合并的子数组
     * @param left  合并后数组的起始索引(也是原始左子数组的起始索引)
     * @param right 合并后数组的结束索引(也是原始右子数组的结束索引)
     * @param mid   左右子数组的分界索引
     */
    private static void merge (int[] array,int left,int right,int mid){
        int s1=left;
        int e1=mid;
        int s2=mid+1;
        int e2=right;


        int[] tmpArray=new int[right-left+1]; //创建一个临时数组来存储合并后的结果
        int count=0; //记录tmpArray中实际元素个数

        // 合并两个子数组,直到其中一个子数组的所有元素都被复制
        while(s1<=e1&&s2<=e2){
            if(array[s1]<array[s2]){
                tmpArray[count]=array[s1];
                s1++;
                count++;
            }else{
                tmpArray[count]=array[s2];
                s2++;
                count++;
            }
        }

        // 如果左子数组还有剩余元素,将它们复制到临时数组中
        while(s1<=e1){
            tmpArray[count]=array[s1];
            s1++;
            count++;
        }
        // 如果右子数组还有剩余元素,将它们复制到临时数组中
        while(s2<=e2){
            tmpArray[count]=array[s2];
            s2++;
            count++;
        }
        for (int i = 0; i < tmpArray.length ; i++) {
            array[left+i]=tmpArray[i];

        }
    }

3.演示图

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值