七大排序算法及优化

8 篇文章 0 订阅
本文详细介绍了几种经典的排序算法,包括直接插入排序、希尔排序、选择排序、堆排序、冒泡排序和快速排序,以及它们的时间复杂度和稳定性。对于某些算法进行了优化,如折半插入排序和双向选择排序。同时,文章还提到了归并排序的分治思想。
摘要由CSDN通过智能技术生成

## 排序的分类:

在这里插入图片描述

1、直接插入排序

每次从无序区间选择第一个元素插入有序区间的合适位置

因为在插入元时,小于等于的前一个不插,是稳定的排序,时间复杂度为O(n^2)

在近乎有序的数组中性能非常好

public static void insertSort(int[] arr){
        //默认第一个元素是有序的
        //无序区间
        for (int i = 1; i < arr.length; i++) {
            //有序区间[i,arr.length)
            // 有序区间[0,i)
            for (int j=i; j >0; j--) {
                //j为无序区间的第一个元素,j -1为有序区间最后一个元素(有序区间最大的元素),所以arr[j]>arr[j-1]时循环结束
                if(arr[j]<arr[j-1]){
                    swap(arr,j,j-1);
                }
                else {
                    break;
                }
            }
            //System.out.println(Arrays.toString(arr));
        }
    }

**优化:**在插入元素时,可以用折半查找法在有序的元素区间进行查找

折半插入排序:

public static void insertSortBS(int[] arr){
        //无序区间
        for (int i = 1; i < arr.length; i++) {
            int val=arr[i];//无序区间的第一个值
            int low=0,high=i;
           while (low<high){
                int mid=(low+high)/2;
                if(val>=arr[mid]){
                    low=mid+1;
                }
                //右区间取不到,不用-1
                else{
                    high=mid;
                }
            }
            //数据搬移
            for (int j = i; j >low; j--) {
                arr[j]=arr[j-1];
            }
            //low为元素插入位置
            arr[low]=val;
            System.out.println(Arrays.toString(arr));
        }
    }

2、希尔排序

选定一个整数gap,将待排序数组中按gap排序,所有举例为gap的元素放在一组,在组减进行排序,然后不断缩小gap直到为1,数组有序。

希尔排序为不稳定的排序,时间复杂度为O(n^2)

//根据间隔两两进行比较
    public static void shellSort(int[] arr){
        //先确定间距为数组长度的一半
        int gap=arr.length/2;
        //间距为1时停止
        while (gap>1){
            insertSortShell(arr,gap);
            gap/=2;
        }
        insertSortShell(arr,1);
    }
    //希尔排序中的选择排序
    private static void insertSortShell(int[] arr, int gap) {
        //两个元素交换是右边的元素
        for (int i = gap; i < arr.length; i++) {
            //两个元素交换时左边的元素
            //在内层循环中,凡是满足间隔的一组数循环反复交换‘’‘
            for (int j = i; j-gap>=0 && arr[j]<arr[j-gap] ; j=j-gap) {
                swap(arr,j,j-gap);
            }
        }
    }

3、选择排序

每一次从无需区间选择最大(最小)的元素放在无序区间的最后(最前),直到全部待排序数据匀速排完

选择排序是不稳定的排序,时间复杂度为O(n^2)

public static void selectSort(int[] arr){
        //每次从数组中选择最小或最大的放在数组头部或尾部
        //最外层循环表示执行的总次数
        //已经有序的集合[0,i)   待排序的集合[i+1,n)
        //每次排序,已经排序的集合元素个数+1,待排序的集合元素个数-1
        for (int i = 0; i < arr.length; i++) {
            int min=i;//存储最小值元素的下标,默认为i
            //此循环从外层循环的下一个元素开始进行内层循环
            for (int j = i+1; j < arr.length; j++) {
                if(arr[min]>arr[j]){
                    min=j;//遇到比默认值小的元素将min进行更新
                }
            }
            //此时min存储的是最小元素的下标,将min于默认的最小元素交换
            swap(arr,i,min);
        }
    }
    public static void swap(int[] arr,int i,int min){
        int temp=arr[i];
        arr[i]=arr[min];
        arr[min]=temp;
    }

优化:双向选择排序,一趟选出最大和最小两个元素,大的放后,小的放前

双向选择排序:

public static void selectSortDouble(int[] arr){
        int low=0,high=arr.length-1;
        while (low<high){
            int min=low,max=low;
            for (int i = low+1; i <= high; i++) {
                if(arr[i]<arr[min]){
                    min=i;
                }
                if(arr[i]>arr[max]){
                    max=i;
                }
            }
            //此循环走完后,min和max分别存储最小元素下标和最大元素下标
            swap(arr,low,min);
            //此时有特殊情况:如果第一个元素为最大值元素,最小值元素进行交换的时候将之前确定好的元素下标改变
            // 此时就要更新最大值元素下标为换走的那个最小值元素下标
            //要是没有这个特殊情况,不用进入此if
            if(max==low){
                max=min;
            }
            swap(arr,high,max);
            low=low+1;
            high=high-1;
        }
    }

4、堆排序

步骤:
(1)先堆化,从最后一个飞叶子结点开始,将小元素下沉,建成最大堆,这样堆顶为最大值元素;
(2)从元素最后一个叶子结点开始,与堆首的元素(最大值元素)进行交换,再进行下沉操作,将大的元素发放在后面,直到所有元素都与堆首元素进行交换,形成升序

堆排序是不稳定的排序算法,时间复杂度为O(nlogn)

public static int parent(int k){
        return (k-1)/2;
    }
    public static int left(int k){
        return 2*k+1;
    }
    public static int right(int k){
        return 2*k+2;
    }
    //每次先下沉,将最大元素升上去,小元素降下去,再交换堆首与最后一个叶子结点,再下沉,反复,形成升序
    public static void heapSort(int[] arr){
        //首先将数组调整为最大堆,从对的最后一个非叶子结点开始
        for (int i = 0; i < (arr.length-1)/2; i++) {
            siftDown(arr,i,arr.length);
        }
        //交换堆顶的元素和堆中的最后一个元素
        for (int i = arr.length-1; i >=0 ; i--) {
            swap(arr,i,0);
            siftDown(arr,0,i);
        }
    }
    //元素的下沉操作(最大堆),i为要下沉的堆元素,n为数组的长度
    private static void siftDown(int[] arr,int i,int n){
        //从最后一个非叶子结点开始,判断这个结点是否还有下沉的余地
        while ((2*i)+1<n){
            int j=left(i);//做孩子索引
            //让j一直保持左右孩子中的最大值,此时注意有有孩子才能进入此循环
            if(j+1<n && arr[j]<arr[j+1]){
                j=j+1;
            }
            //将小的元素沉下去,形成最大堆
            if(arr[i]<arr[j]){
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
            else {
                break;
            }
        }
    }

5、冒泡排序

在一趟中,紧挨的两个元素进行交换,将大的元素放在前,小的放在后,两两不断交换,直到一趟的元素都交换完成,再进行第二趟,一共进行arr.length-1趟

冒泡排序是稳定的排序,时间复杂度为0(n^2)

//冒泡排序
    public static void bubbleSort(int[] arr){
        for (int i = 0; i < arr.length-1; i++) {
            for (int j = i+1; j < arr.length; j++) {
                if(arr[i]>arr[j]){
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }

6、快速排序

(1)每次默认数组中第一个元素为key,i从数组最左开始,j从数组最由开始,当i>key时,i与key交换,否则i++;当j<key时,i与key交换,否则j–,直到l=j时,停止
(2)上述步骤将数组以key分为小数组,再在小数组中重复上述步骤,不断递归,直到数组被分为1个元素,递归结束

public static void quickSort(int[] arr){
        qsort(arr,0,arr.length-1);
    }

    private static int[] qsort(int arr[],int start,int end) {
        int pivot = arr[start];
        int i = start;
        int j = end;
        while (i<j) {
            while ((i<j)&&(arr[j]>pivot)) {
                j--;
            }
            while ((i<j)&&(arr[i]<pivot)) {
                i++;
            }
            if ((arr[i]==arr[j])&&(i<j)) {
                i++;
            } else {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        if (i-1>start){
            arr=qsort(arr,start,i-1);
        }
        if (j+1<end) {
            arr=qsort(arr,j+1,end);
        }
        return arr;

    }

优化策略:
(1)当递归的数组元素个数<=15,用插入排序
(2)key的取值选取随机数,选出的随机数再与第一个位置的元素交换,开始比较

7、归并排序

(1)将原数组不断进行拆分,拆分到每个数组只剩下一个元素
(2)merge:将拆分的数组不断进行合并,在合并的过程中进行排序,直到整个数组都合并完,数组有序

//归并排序:先拆分为一个一个的元素,再一个一个合并
    public static void mergeSort(int[] arr){
        mergeSortInternal(arr,0,arr.length-1);
    }
    //在l到r上进行归并排序
    private static void mergeSortInternal(int[] arr,int l,int r){
        if(l>=r){
            //此时只有一个元素或者不符合逻辑
            return;
        }
        //找出中间位置,从中间将数组不断拆分
        int mid=l+((r-l)>>1);//防止溢出
        mergeSortInternal(arr,l,mid);//排序左区间
        mergeSortInternal(arr,mid+1,r);//排序右区间
        //此时左右区间都有序,只需合并两个区间
        merge(arr,l,mid,r);
    }
    //将有序的两个数组合并为大的有序数组(l...mid,mid+1...r)
    //这个方法具体实现两个数组怎样合并为一个有序数组
    private static void merge(int[] arr, int l, int mid, int r) {
        int[] temp=new int[r-l+1];
        //将原数组的元素拷贝到新数组,此新数组用来存放左右排序好的数组,以便于直接在最初的数组上进行替换
        for (int i = l; i <= r; i++) {
            //新数组的索引和原数组的索引有l个单位的偏移量
            //arr从1000开始,temp[0]=arr[1000]
            //arr为原数组的部分,temp为复制原部分数组的一个数组,所以arr的索引比temp的大写,temp就比arr向右偏移l个,就需要减
            temp[i-l]=arr[i];
        }
        //遍历两个数组,将两个数组进行有序合并:将左右两个数组最小值写会原数组
        int i=l;//左有序区间的第一个索引
        int j=mid+1;//左有序区间的第一个索引
        for (int k = l; k <= r; k++) {
            //此时左区间走完,将右区间所有值写会原数组
            if(i>mid){
                arr[k]=temp[j-l];
                j++;
            }
            //此时右区间走完,将左区间所有值写会原数组
            else if(j>r){
                arr[k]=temp[i-l];
                i++;
            }
            //此时左区间的元素小于右区间的元素   <=让此排序稳
            else if(temp[i-l]<=temp[j-l]){
                arr[k]=temp[i-l];
                i++;
            }
            else {
                arr[k]=temp[j-l];
                j++;
            }
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值