Java15/排序

一 排序

参考:十大经典排序算法
排序算法
排序算法的时间复杂的和空间复杂度:
算法空间复杂度和时间复杂度
稳定:若a=b,且a在b的前面,经过排序后a仍在b的前面,则成该排序算法稳定。
稳定的排序:插入排序,冒泡排序,归并排序
不稳定的排序:堆排序,快速排序,选择排序,希尔排序

比较排序:数据之间的排序依赖它们之间的比较。
非比较排序:根据数据在空间中的位置进行排序,对数据规模和数据分布有一定要求。

  • 一个稳定的排序,可以实现为不稳定的排序(更改一个等号的问题)
  • 一个不稳定的排序,不可能实现为稳定的排序
1 直接插入排序(稳定)

拿到无序数组的第一个元素,来和前面有序数组的元素进行比较,比该元素大的,直接向后移动,进而找到一个合适的位置进行插入。

static void insertSort(int[] array){
    //假定第一个元素有序了,从第二个元素开始和前面有序的数据
    //进行比较,找位置插入
    for (int i =1 ; i <array.length ; i++) {
        int ch=array[i];
        //这整个循环是一个挪位的过程
        int j=0;
        for (j = i-1; j >=0; j--) {
            if(ch<array[j]){
                array[j+1]=array[j];
            }else{
                break;
            }
        }
        //插入
        array[j+1]=ch;
    }
}

时间复杂度
最佳(数据有序时):O(n)
最坏(数据逆序时):O(n2)
平均:O(n2)

空间复杂度: O(1)

注意:
数据越有序,插入排序的时间效率越高。

2 希尔排序(不稳定)

希尔(shell)排序,是插入排序的一种优化,主要是根据“序列越有序,插入排序的时间效率越高”这一原则改进。即通过将数据进行分组插入排序,分组内元素距离的大小称为增量,希尔增量(希尔增量不是最优的增量)的大小可通过:gap1=length/2; gap2=gap1/2;…;gapN=1.进行确定。最后一个增量必须为1.
shell排序
代码形式上和插入排序相同。

   static void shellSort(int[] array,int gap){
    for (int i = gap; i < array.length; i++) {
        int ch = array[i];
        int j=0;
        for (j = i-gap; j >=0 ; j-=gap) {
            if(ch<array[j]){
                array[j+gap]=array[j];
            }else{
                break;
            }
        }
        //插入
        array[j+gap]=ch;
    }
}

 public static void main(String[] args) {
        int[] a={1,6,3,4,9,4,3,2,8};
        int[] gap={4,2,1};
        for (int i = 0; i < gap.length; i++) {
            shellSort(a,i);
        }
        System.out.println(Arrays.toString(a));
    }

时间复杂度:
O(n^1.3-1.5)
空间复杂度:
O(1)

3 选择排序(不稳定)

依次从无序区间中取值,和前面有序区间的最后一个元素进行比较,若无序区间的值小,则进行交换,然后再继续从无序区间中取值比较。

代码连续两个循环搞定:

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

时间复杂度:
最佳:O(n2)
最坏:O(n2)
平均:O(n2)
空间复杂度:
O(1)

扫描二维码关注公众号,回复: 9869867 查看本文章
4 堆排序(不稳定)

前置知识点了解:完全二叉树中父节点和子节点的关系:

leftChild = 2parent+1;
rightChild = 2parent +2;
parent = (child-1)/2

升序排列数组要建大堆
降序排列数组要建小堆

本例中我们采用升序排序,建一个大堆。
array.length-1-1:获取最后一个子节点的父节点

创建一个大堆

将这个数组array看作是完全二叉树层序遍历的结果
每颗子树都要调整为大堆
首先要找到最后一个子节点的父节点(array.length-1-1)/2

//1 创建一个大堆
public void createHeap(int[] array){
    //找到最后一个节点的父节点,依次向下调整
    for(int i=(array.length-1-1)/2;i>=0;i--){
        adjustDown(array,i,array.length);
    }
}

向下调整

public void adjustDown(int[] array,int root,int len){
    //时间复杂度log2n
    int parent = root;
    int child = 2*parent+1;
    //看看是否有孩子节点
    while(child<len){
    	//是否有右孩子节点
        if(child+1<len&&array[child]<array[child+1]){
            child++;
        }
        //child保存的是最大值的下标
        if(array[child]>array[parent]){
            int tmp = array[child];
            array[child] = array[parent];
            array[parent] = tmp;
	    //向下调整,即让parent指向child
            parent = child;
            child=2*parent+1;
        }else{
            break;
        }
    }
}

接下来简单描述一下为啥要向下调整
以建一个大堆为例:
二叉树1
第一次调整,让parent指向最后一个子节点的父节点。
二叉树2
对于已经是大堆的子树,直接break掉了。
二叉树3
当出现这种情况时,就能够清楚向下调整的意义所在了。
二叉树3
通过向下调整,被改变的子树,重新恢复为大堆,整个大堆建造过程结束。
二叉树4
创建完堆之后进行堆排序,因为是大堆,头节点保存的是最大的元素。堆排序就是将堆顶元素不断向后面的有序数组前添加,这就有点类似于选择排序。

/2 堆排序(默认为大堆),从小到大,第一个和最后一个交换
public  void heapSort(int[] array){
    //传入一个数组,先建立一个大堆
    createHeap(array);
    int end = array.length-1;
    while(end>0){
        //将堆顶元素和end元素进行交换
        int tmp = array[end];
        array[end] = array[0];
        array[0] = tmp;
        //end=array.length-1,此处的end代表数组长度
        //交换之后,通过向下调整,重新建立一个大根堆
        adjustDown(array,0,end);
        end--;
    }
}

注意:
在向下调整时,传的是数组的大小adjustDown(array,0,end);,而堆排序里面的end指的是数组的下标,所以要在 end--;之前进行向下调整adjustDown(array,0,end);

时间复杂度:
最佳:O(nlog2n)
最坏:O(nlog2n)
平均:O(nlog2n)
建堆的时间复杂度为:O(nlog2n)
空间复杂度:
O(1)

5 冒泡排序(稳定)

通过相邻数的比较将最大的数冒泡到无序区间的最后。

public static void bubbleSort(int[] array){
        //外层for循环控制冒泡的次数,每一次冒泡的完成代表,
        //完成一个数字有序排序
       for (int i = 0; i <array.length-1 ; i++) {
           //内层for循环控制每次冒泡进行交换的次数
           //如果在一次冒泡的过程中,一次都没有进行交换,则说明
           //该数组已有序,退出冒泡排序即可
           boolean flg = false;
            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;
                    flg = true;
                }
            }
            if(!flg){
                break;
            }
        }
    }

时间复杂度:
数据有序时 O(n)
数据逆序时 O(n^2)
空间复杂度:
O(1)

6 快速排序(不稳定)
  • 从待排序区间选择一个数,作为基准值(pivot)
  • partition:遍历整个待排序区间,将比基准值小的放到基准值的左边,比基准值大的放到基准值的右边
  • 采用分治思想,对分好的左右区间以同样的方式进行处理,直到小区间长度==1.
  public void quickSort(int[] array){
        quick(array,0,array.length-1);
    }
    public void quick(int[] array,int low, int high){
        if(low>=high){
            return;
        }
        //依据递归的思想,每次都是找基准点,划分区间
        int pivot = partion(array,low,high);
        quick(array,low,pivot-1);
        //如果pivot为最后一个元素,此时pivot+1将会有low大于high的情况
        quick(array,pivot+1,high);
    }
    public int partion(int[] array,int low,int high){
        int tmp =array[low];
        while(low<high){
            //右边找小于tmp的元素,赋给array[low]
            while((low<high)&&array[high]>=tmp){
                high--;
            }
            if(low>=high){
                break;
            }else{
                array[low] = array[high];
            }
            //左边找大于tmp的元素,赋给array[high]
            while((low<high)&&array[low]<=tmp){
                low++;
            }
            if(low>=high){
                break;
            }else{
                array[high] = array[low];
            }
        }
        array[low] = tmp;
        return low;
    }

时间复杂度:
O(nlog2n)
数据有序: O(n^2)
空间复杂度:
O(log2n)

优化方法:

  • 设定区间阈值,当待排序区间长度小于该阈值时,直接采用直接插入排序
  • 优化选取基准值,三数取中法

设定区间阈值:

    // 7.1 快速排序优化1——设置阈值,如果区间长度小于该阈值,则用直接插入排序
    //因为,区间越有序,直接插入排序性能越高,为O(n)

    public void quickSort1(int[] array){
        quick1(array,0,array.length-1);
    }

    public void quick1(int[] array,int low,int high){
        if(low>=high){
            return;
        }
        //区间长度小于该阈值,则用直接插入排序
        if(high-low+1<100){
            insertSort(array,low,high);
            return;
        }
        int pivot = partion(array,low ,high);
        quick1(array,low,pivot-1);
        quick1(array,pivot+1,high);
    }
    public void insertSort(int[] array,int low ,int high){
        for (int i = low+1; i <=high ; i++) {
            int tmp = array[i];
            int j =0;
            for ( j =i-1; j >=low; j--) {
                if(array[j]>tmp){
                    array[j+1]=array[j];
                }else{
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }

三数取中:

// 7.2 快速排序优化2--三数取中。 在区间已经趋于有序的情况下,也是快排最坏的情况
    //时间复杂度为O(n^2),

    public void quickSort2(int[] array){
        quick2(array,0,array.length-1);
    }

    public void quick2(int[] array,int low,int high){
        if(low>=high){
            return;
        }
        ThreeNumOfMiddle(array,low,high);
        int pivot = partion(array,low ,high);
        quick1(array,low,pivot-1);
        quick1(array,pivot+1,high);
    }

    public static void swap(int[] array,int low,int high) {
        int tmp = array[low];
        array[low] = array[high];
        array[high] = tmp;
    }
    public static void ThreeNumOfMiddle
            (int[] array,int low,int high) {
        //构造这种关系array[mid] <= array[low] <= array[high];
        //让中间值最小
        int mid = (low+high)/2;
        //array[mid] <= array[high]
        if(array[mid] > array[high]) {
            swap(array,mid,high);
        }
        //array[mid] <= array[low]
        if(array[mid] > array[low]) {
            swap(array,mid,low);
        }
        //array[low] <= array[high]
        if(array[low] > array[high]) {
            swap(array,low,high);
        }
    }

快速排序的非递归形式:

// 8 快速排序的非递归形式
    public  void quickSort3(int[] array) {
        quick3(array,0,array.length-1);
    }
    public  void quick3(int[] array,int low,int high) {
        int pivot = partion(array,low,high);
        Stack<Integer> stack = new Stack<>();
        //>low+1保证左边有两个元素以上,并将左区间入栈
        if(pivot > low+1 ) {
            stack.push(low);
            stack.push(pivot-1);
        }
        //<high-1保证右边有两个元素以上,并将右区间入栈
        if(pivot < high-1) {
            stack.push(pivot+1);
            stack.push(high);
        }
        while (!stack.empty()) {
            high = stack.pop();
            low = stack.pop();
            pivot = partion(array,low,high);
            if(pivot > low+1 ) {
                stack.push(low);
                stack.push(pivot-1);
            }
            if(pivot < high-1) {
                stack.push(pivot+1);
                stack.push(high);
            }
        }
    }
发布了54 篇原创文章 · 获赞 6 · 访问量 4818

猜你喜欢

转载自blog.csdn.net/glpghz/article/details/103767620