目录

1,排序的比较方法

2,堆排序

3,冒泡排序

4,直接插入排序

5,希尔排序

6,直接选择排序

7,快速排序

8,归并排序

一,排序的比较

我们一般比较排序只要通过判断排序是否是稳定的,然后就是比较时间复杂度和空间复杂度,并且在下面的排序当中也会设计到,具体稳定性:也就是如下图,我们原本红色的5在后面,不过通过排序之后,可能会有以下两种情况,位置不改变也就是稳定的,空间复杂度我们探究的是最坏情况

二,堆排序

核心:堆排序为的是原地进行排序,而不是重现创建空间来排序,这和我下面提到的从小到大排的时候就要建立大根堆,而相反的从大到小排序就要建立小根堆说法一致

堆排序就是利用优先级队列的思想来排序,当我们想要从小到大排的时候就要建立大根堆,而相反的从大到小排序就要建立小根堆,我们就拿从小到大排来举例,我们先要创建一个大根堆,堆顶元素就是所有里面最大的,然后将最后一个元素和堆顶元素进行交换,然后usedsize--,然后调整除刚换下来的数据以外的数据,然后找到次大的,然后调整剩下的n-2个,直到调整完

稳定性不稳定,因为当我们堆顶元素和最后元素进行交换后,原来本来存在的顺序会进行一个一百八十度翻转

时间复杂度:O(nlogn),通俗理解:一共 n 个元素,每个元素最多下沉 (log n) 层,总代价 (nlog n)。

空间复杂度:O(1),没有创建新的空间

 public void creatheap(){
        //先找到最后一颗子树的根节点的下标
        for (int parent = (usedsize-1-1)/2; parent >=0; parent--) {
            shelldown(parent, usedsize);
        }
    }
    //向下调整
    private void shelldown(int parent,int usedsize){
        //1,先取得左孩子节点的下标
        int child=parent*2+1;
        //调整到child小于usedsize的时候
        while (child<usedsize){
            //先取得左右孩子中最大的,并且得保证有右孩子的情况下,没有就直接不用变
            if (child+1<usedsize && elem[child]<elem[child+1]){
                child++;
            }
            //现在child里面存储的就是左右孩子里的最大值的下标,然后进行交换
            if (elem[child]>elem[parent]){
                swap(elem,parent,child);
                //只能保证调整了的是正确的,但是还得向下调整保证换完的也是大根堆
                parent=child;
                child=parent*2+1;
            } else {
                break;
            }
        }
    }
//从小到大排
    public void heapsort(){
        int end=usedsize-1;
        while (end>0){
            swap(elem,0,end);
            shelldown(0,end);
            end--;
        }
    }

三,冒泡排序

很常见的一种排序方法,让大的依次放到最后面,跟冒泡一样

public void BubbleSort(int[]array){
        for (int i = 0; i < array.length-1; i++) {
            for (int j = 0; j < array.length - 1 - i; j++) {
                if (array[j]>array[j+1]){
                    int tmp=array[j];
                    array[j]=array[j+1];
                    array[j+1]=tmp;
                }
            }
        }
    }

稳定性:稳定,因为内层循环是严格的判断array[j]>array[j+1]

时间复杂度:O(N^2),外层执行n-1轮,内层最坏依次执行n-1,n-2......总比较次数:((n-1)+(n-2)+...+1 = n(n-1)/2,也就是n^2

空间复杂度:O(1),原地排序

四,直接插入排序

跟打牌把牌插入一样,定义了i,j下标,并且开始j=i-1,并且假设一个零时变量tmp,并且把array[i]的值放入tmp里面,并且和array[j]的值进行比较,然后j--进行回调,直接插入排序的特点:有序度越高,插入排序需要的元素移动次数越少,运行速度越快;完全有序时达到线性最优效率。

  //直接插入排序
    public void InsertSort(int[] array){
        for (int i = 1; i < array.length; i++) {
            int tmp=array[i];
            int j = i-1;
            for (; j >=0; j--) {
                if (array[j]>tmp){
                    array[j+1]=array[j];
                }else {
                    break;
                }
            }
            array[j+1]=tmp;
        }
    }

稳定性:稳定,比较逻辑:if(tmp < array[j]) 才移动元素

时间复杂度:O(N^2),内层循环每次都要把前面所有元素往后移

空间复杂度:O(1),原地排序

五,希尔排序

将数据分成很多组来进行排序,如第一次分成五组,每组数据进行排序,接下来再分成两组,组内排序,最后是整组数据一起排序,当然分的组的个数各有说法,可以分成6组可以分成3组,都是可以的,所以我以gap=array.lenth/2来进行分组数,当然组的数据也不能挨着来分,是跳着来分的,目的是为了让大的数据尽可能的往后移

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

稳定性:不稳定

时间复杂度:O(N^1.3)-O(N^1.5)

空间复杂度:O(1),原地排序

六,直接选择排序

定义i,j下标和minIndex记录小值的下标,j一直往后走,找到比当前minIndex记录的值还小的值,如果有则minIndex换成j,然后进行交换

public static void SelectSort(int[]array){
        for (int i = 0; i < array.length; i++) {
            int minIndex=i;
            for (int j = i+1; j < array.length; j++) {
                if (array[j]<array[minIndex]){
                    minIndex=j;
                }
            }
            //开始交换
            swap(array,i,minIndex);
        }
    }
    private static void swap(int[]array,int i,int minIndex){
        int tmp=array[i];
        array[i]=array[minIndex];
        array[minIndex]=tmp;
    }

稳定性:不稳定

时间复杂度:O(N^2)

空间复杂度:O(1),原地排序

七,快速排序

1,Hoare法

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

    private static void quick(int[] array, int start, int end) {
        if (start>=end){
            return;
        }
        //找到中值的下标
        int par=pattion(array,start,end);
        //左边排序
        quick(array,start,par-1);
        //右边排序
        quick(array,par+1,end);
    }

    private static int pattion(int[] array, int low, int high) {
        //array[low]这个值是一个基准值,通过low以及high以及最后的一个交换,使基准值的左边比他小,右边比他大,让他是一个中值,并且返回中值的下标
        int pivot=array[low];
        int i=low;
        while (low<high){
            //high找到比pivot小的值
            //low找到比pivot大的值
            while (array[high]>=pivot && low<high){
                high--;
            }
            while (array[low]<=pivot && low<high){
                low++;
            }
            swap(array,high,low);
        }
        //走完这个循环后low和high走到了一起,让这个值的下标和pivot的下标交换
        swap(array,i,low);
        return low;
    }

稳定性:不稳定

时间复杂度:最小O(N*log2N),最大:O(N^2)

空间复杂度:最小O(log2N),最大:O(N)

2,挖坑法

private static int pattion2(int[] array, int low, int high) {
        int tmp=array[low];
        int i=low;
        while (low<high){
            //high找到比pivot小的值
            //low找到比pivot大的值
            while (array[high]>=tmp && low<high){
                high--;
            }
            array[low]=array[high];
            while (array[low]<=tmp && low<high){
                low++;
            }
            array[high]=array[low];
        }
        array[low]=tmp;
        return low;
    }

3,快排的改善

因为快排的时间复杂度取决于递归的树的高度,因为数量是不变的,所以当我们是完全二叉树的时候,我们树的高度是最小的,为log2N,因此此时的时间复杂度和空间复杂度是最小的,最大的情况就是123,或者321这种有序的数组,因为这样的树就只有左树或者只有右树,导致树的高度最大,为N,所以我们要改善就要尽量使高度减少,用到的方法就是三数取中,找到low下标,high下标和(low+high)/2下标中的最小值,然后将low下标的值和这个值进行交换

 private static int threeMid(int[] array,int low, int high) {
        int mid = (low+high)/2;
        if(array[low] < array[high]) {
            if(array[mid] < array[low]) {
                return low;
            }else if(array[mid] > array[high]) {
                return high;
            }else {
                return mid;
            }
        }else {
            // array[low] > array[high]
            if(array[mid] < array[high]) {
                return high;
            }else if(array[mid] > array[low]) {
                return low;
            }else {
                return mid;
            }
        }
    }

4,快排的非递归实现

public static void QuickSortNor(int array[]){
        int start=0;
        int end=array.length-1;
        int par=pattion2(array,start,end);
        Deque<Integer> stack=new LinkedList<>();
        if (par>start+1){
            stack.push(start);
            stack.push(par-1);
        }
        if (par<end-1){
            stack.push(par+1);
            stack.push(end);
        }
        while (!stack.isEmpty()){
            end=stack.poll();
            start=stack.poll();
            par=pattion2(array,start,end);
            if (par>start+1){
                stack.push(start);
                stack.push(par-1);
            }
            if (par<end-1){
                stack.push(par+1);
                stack.push(end);
            }
        }
    }

5,两个问题解决

第一个问题:等号是否可以去除

第二个问题:是否可以让low先走 先++

问题一:不可以,因为当我们0下标和array.lenth-1下标的数一样,那么就会两个一样的数一直交换陷入死循环

问题二:不可以,因为我们每次par的目标都是让par左边的数都比par小,右边都比par大,如果让low先走,low是为了找到比par大的数字,最后当low和high相遇的前一步,是low先走的,那么low和high会想遇到low的任务,就是比基准大的数,当基准和这个大的数交换就不能实现左边都小了

八,归并排序

1,正常递归

先把数据整齐的拆分为左右两部分,一直到只剩一个元素的时候,开始逐渐的往上开始合并

public static void MergeSort(int[] array){
        mergesort(array,0,array.length-1);
    }

    private static void mergesort(int[] array, int left, int right) {
        if (left>=right){
            return;
        }
        int mid=(left+right)/2;
        //左分
        mergesort(array,left,mid);
        //右分
        mergesort(array,mid+1,right);
        //合并
        marge(array,left,mid,right);
    }

    private static void marge(int[] array, int left, int mid, int right) {
        int[] tmp=new int[right-left+1];
        //用于记录数组的下标
        int k=0;
        int s1=left;
        int e1=mid;
        int s2=mid+1;
        int e2=right;
        //保证两段都有数据
        while (s1<=e1 && s2<=e2){
            if (array[s1]>array[s2]){
                tmp[k++]=array[s2++];
            } else{
                tmp[k++]=array[s1++];
            }
        }
        //这里走完当s1超过了e1或者s2超过了e2,也就是有一段或者两段走完了的,所以可能出现没走完的,就让他直接放在数组里
        while (s1<=e1){
            tmp[k++]=array[s1++];
        }
        while (s2<=e2){
            tmp[k++]=array[s2++];
        }
        //下面得将其拷贝在数组里
        for (int i = 0; i < tmp.length; i++) {
            array[i+left]=tmp[i];
        }
    }

稳定性:稳定

时间复杂度:O(N*log2N)

空间复杂度:O(N)

2,归并排序的非递归

思想:递归是递归左右两边,为了不递归,那我就要找到左右两边最开始的下标,所以我引进了gap这个变量,并且gap每次乘2,然后left和right每次都是分组的第一个和最后一个,mid每次都是还没分组的最后一个,例如6 3,mid就指向6,四个已经分好的时候1 2 3 6 4,mid就指向6

public static void MergeSort2(int[] array) {
        int gap = 1;
        while (gap < array.length) {
            for (int i = 0; i < array.length; i = i + gap * 2) {
                int left = i;
                int mid = left + gap - 1;
                if (mid > array.length-1 ) {
                    mid = array.length - 1;
                }
                int right = mid + gap;
                if (right > array.length-1) {
                    right = array.length - 1;
                }
                marge(array, left, mid, right);
            }
            gap *= 2;
        }
    }

更多推荐