【数据结构】排序(冒泡、选择、插入、希尔、堆、归并、快排)

目录

一、冒泡排序

二、选择排序

三、插入排序

四、希尔排序

五、堆排序

六、归并排序

七、快速排序

八、分析

 总结


一、冒泡排序

        最基础的排序,思路是将数据和下一个数据进行比较,如果比下一个大就交换,然后指向下一个元素,依次类推则最大的元素被移到了最后。然后继续进行下一轮排序,直到排序完成。利用减治的思想。

    public static void bubbleSort(int[] arr){
        for (int i = 0; i < arr.length-1; i++) {
            boolean flag=true;
            for (int j = 0; j < arr.length-i-1; j++) {
                if(arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                    flag=false;
                }
            }
            if(flag){
                return;
            }
        }
    }
    private static void swap(int[] arr,int i, int i1) {
        int t=arr[i];
        arr[i]=arr[i1];
        arr[i1]=t;
    }

二、选择排序

        选择排序,思路是每次都选择出最小的元素放在有序区间的后面。相比于冒泡每次大的都要交换,选择排序是每次找出最小的进行交换。代码如下:

    public static void selectionSort1(int[] arr){
        for (int i = 0; i < arr.length; i++) {
            int minindex=i;
            for (int j = i+1; j < arr.length ; j++) {
                if(arr[j]<arr[minindex]){
                    minindex=j;
                }
            }
            swap(arr,minindex,i);
        }
    }
    private static void swap(int[] arr, int maxindex, int i) {
        int t=arr[maxindex];
        arr[maxindex]=arr[i];
        arr[i]=t;
    }

三、插入排序

        插入排序,就是每次将无序区间的第一个元素和有序区间的所有元素进行比较,如果大就不用移动,如果小就向前移,直到找到合适的位置。

    public static void insertSort(int [] arr){
        for (int i=0;i<arr.length-1;i++){
            for (int j = i; j >=0 ; j--) {
                if(arr[j+1]>arr[j]){
                    break;
                }else{
                    swap(arr,j,j+1);
                }
            }
        }
    }
    private static void swap(int[] arr, int j, int i) {
        int t=arr[j];
        arr[j]=arr[i];
        arr[i]=t;
    }

四、希尔排序

        希尔排序是在插排的基础上,插排每次都是一个一个的进行排序,希尔排序每次都间隔一定数量来进行插排,这个间隔不断减小,直到最后一次使用相邻元素进行比较,也就是插排。

    public static void shellSort(int[] arr){
        int gap=arr.length/2;
        while (gap>1){
            insertSortWithGap(arr,gap);
            gap=gap/2;
        }
        insertSortWithGap(arr,1);
    }
    public static void insertSortWithGap(int[] arr,int gap){
        for (int i = 0; i < arr.length-gap; i++) {
            for (int j = i; j >=0 ; j=j-gap) {
                if(arr[j+gap]>arr[j]){
                    break;
                }else {
                    swap(arr,j+gap,j);
                }
            }
        }
    }
    private static void swap(int[] arr, int i, int j) {
        int t=arr[i];
        arr[i]=arr[j];
        arr[j]=t;
    }

五、堆排序

        堆排序,利用大堆的特点,即最大的元素总是在根节点处。所以我们可以将待排序元素组建成为一个大堆,然后交换最后一个元素和根节点,然后进行向下调整,然后继续交换,直到排序完成。

    public static void heapSort(int [] arr){
        int [] array=creatHeap(arr);

        for (int i = 0; i < arr.length-1; i++) {
            swap(arr,0,array.length-1-i);
            shiftDown(array,array.length-1-i,0);
        }
    }
    private static void shiftDown(int[] array, int size, int index) {
        while (index*2+1<size){
            int max=index*2+1;
            int right=max+1;
            if(right<size&& array[max]<array[right]){
                max=right;
            }
            if(array[index]>=array[max]){
                return;
            }
            swap(array, max, index);
            index=max;
        }
    }
    private static void swap(int[] arr, int i, int j) {
        int t=arr[i];
        arr[i]=arr[j];
        arr[j]=t;
    }
    private static int[] creatHeap(int[] arr) {
        for (int i = (arr.length-2)/2; i >=0 ; i--) {
            shiftDown(arr, arr.length,i);
        }
        return arr;
    }

六、归并排序

        归并排序利用分治的思想,就是将无序区间划分为两个无序区间,然后再继续划分,当元素个数小于等于一的时候自然有序。然后将有序区间进行合并。直到最后完成合并,排序完成。

    private static void mergeSort(int[] array){
        mergeSortRange(array,0,array.length);
    }
    private static void mergeSortRange(int[] array, int from, int to) {
        int size=to-from;
        int mid=from+(size/2);
        if (size<=1){
            return;
        }
        mergeSortRange(array, from, mid);
        mergeSortRange(array, mid,to);
        merge(array,from,mid,to);
    }
    private static void merge(int[] array, int from, int mid, int to) {
        int left=from;
        int right=mid;
        int dest=0;
        int size=to-from;
        int[] other=new int[size];
        while (left<mid && right<to){
            if(array[left]<=array[right]){
                other[dest]=array[left];
                dest++;
                left++;
            }else{
                other[dest++]=array[right++];
            }
        }
        //一定还有一个区间的元素还没有取完。所以一定要补上

        while (left<mid){
            other[dest++]=array[left++];
        }
        while (right<to){
            other[dest++]=array[right++];
        }
        //此时我们需要将元素复制回去
        for (int i = 0; i < size; i++) {
            array[from+i]=other[i];
        }
    }

七、快速排序

        快速排序作为我们的重量级选手登场了。快速排序也是利用分治的思想,每次选取一个pivot,然后通过partition操作将小于pivot的元素放在其左边,大于pivot的元素放在右边,只要将左边和右边排序好就完成了。所以我们将问题化为子问题。然后递归调用,直到完成排序。所以问题的关键在于partition的完方法。

这是介绍的第一种parttition方法。

    public static void quickSort(int[] arr) {
        quickSortRange(arr, 0, arr.length - 1);
    }
    public static void quickSortRange(int[] arr, int from, int to) {
        if (to <= from) {
            return;
        }
        int pi = partitionMethodA(arr, from, to);
        quickSortRange(arr, from, pi - 1);
        quickSortRange(arr, pi + 1, to);
    }
    private static int partitionMethodA(int[] array, int from, int to) {

        int pivot = array[to];
        int left = from;
        int right = to;
        //循环结束的条件就是left=right的时候。此时所有元素都被比较过了
        while (left < right) {
            //为了确保内层循环在变动的时候左指针超越右指针,需要在前面加上限制条件
            while (left < right && array[left] <= pivot) {
                left++;
            }
            //上面的这个循环结束后,表明此时left指针到了一个比pivot大的值处

            while (left < right && array[right] >= pivot) {
                right--;
            }
            //上面的这个循环结束后,表明此时right指针到了一个比pivot小的值处

            //此时需要做的就是将左右指针的值进行交换,然后继续开始比较
            int t = array[left];
            array[left] = array[right];
            array[right] = t;
        }
        //当所有的值比较完毕之后,现在left==right的值,此时我们的pivot在to位置,只需交换位置即可。
        int t = array[to];
        array[to] = array[left];
        array[left] = t;

        //交换位置后的pivot的下标为left,将其返回。以便下次递归调用。
        return left;
    }

“挖坑法”

    private static int partitionMethodB(int[] arr, int from, int to) {
        int pivot = arr[to];
        int left = from;
        int right = to;
        while (left < right) {
            while (left < right && arr[left] <= pivot) {
                left++;
            }
            arr[right] = arr[left];
            //同样的,我们只是换一个方法来完成交换。假设此时的right(to,pivot)为坑,如果遇到不满足left<=pivot的就将该处的值填入坑中,同时坑的位置变成left
            //坑的作用就是我们不用进行复杂的swap(),而是记录位置。

            while (left < right && arr[right] >= pivot) {
                right--;
            }
            arr[left] = arr[right];
            //此时坑的位置是left,所以将right的值填入left坑中
        }
        arr[left] = pivot;
        //循环结束后我们只需要将提前记录的pivot填入坑中即可。
        return left;
    }
    //以上方法AB的两种思路本质上是一样的,只不过是第二种利用了小技巧使得交换次数变少了

遍历法:

    private static int partitionMethodC(int[] array, int from, int to) {
        //利用遍历的方法,s的左边是小于pivot的值,s右边(包括s)是大于pivot的值。遍历区间是从左往右,直到遍历完成,交换s和最后的pivot即可
        int s = from;
        int pivot = array[to];
        for (int i = from; i < to; i++) {
            if (array[i] < pivot) {
                //当遇见比pivot小的值的时候,需要让这个值到s的前面,所以我们交换i和s的值,然后将后移一位。
                int t = array[i];
                array[i] = array[s];
                array[s] = t;
                s++;
            }
            //遇见大的值不用管,继续遍历即可。
        }
        //最后遍历结束之后将pivot的值和s处的值进行交换即可。
        array[to] = array[s];
        array[s] = pivot;
        return s;
    }

第四种方法将以上方法进行结合

    public static void quickSort1(int[] arr) {
        quickSortRange(arr, 0, arr.length - 1);
    }
    public static void quickSortRange1(int[] arr, int from, int to) {
        if (to <= from) {
            return;
        }
        int[] indices = partitionMethodD(arr, from, to);
        int less=indices[0];
        int great=indices[1];
        quickSortRange(arr, from, less);
        quickSortRange(arr, great, to);
    }
    private static int[] partitionMethodD(int[] array, int from, int to){
        //此方法将区间分割为3块分别是小于等于和大于三部分。其中s是区分小于和等于的边界。i相等于左指针,g相当于右指针。
        int s=from;
        int i=from;
        int g=to;
        int pivot=array[to];

        //当g>=i是比较还未完成
        while (g>=i){
            //当i的值等于pivot时,继续遍历不进行其他操作。
            if(array[i]==pivot){
                i++;
            }
            //当i的值小于pivot时,和C方法一样,交换并且后移即可
            else if(array[i]<pivot){
                swap(array,i,s);
                s++;
                i++;
            }
            //当i的值大于pivot时,交换i和g的值,此时的i不需要++,因为交换过来的值还没有比较,但是g是交换后的值,前移即可
            else {
                swap(array,i,g);
                g--;
            }
        }
        //返回s的前一个位置和i的位置,方便下次递归调用
        return new int[]{s-1,i};
    }
    private static void swap(int[] array, int i, int s) {
        int t=array[i];
        array[i]=array[s];
        array[s]=t;
    }

八、分析

排序方法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序O(n^{2})O(n)O(n^{2})O(1)稳定
选择排序O(n^{2})O(n^{2})O(n^{2})O(1)稳定
插入排序O(n^{2})O(n)O(n^{2})O(1)稳定
希尔排序O(nlogn)O(n^{2})O(n^{1.3})O(n^{2})O(1)不稳定
堆排序O(nlogn)O(nlogn)

 O(nlogn)

O(1)不稳定
归并排序O(nlogn)O(nlogn)

 O(nlogn)

O(n)稳定
快速排序O(nlogn)O(nlogn)

 O(n^{2})

O(logn)O(n)不稳定

 总结

        我们一定要熟记这几种排序的思想,尤其是快速排序,还要会分析它们的优点和缺点,使用的情况等。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值