八大排序------数据结构

目录

1.排序是什么?

2.排序的稳定性

 3.七大排序的时间复杂度与空间复杂度

4.选择排序

5.插入排序(O(n^2)) 类似于打牌(稳定)

6.希尔排序:缩小增量的排序(不稳定)

7.归并排序(和希尔排序很像)

8.快速排序(基于算法导论中的分区思想)重要

8.1随机化快排的问题:

8.2快排的非递归实现

8.3二路快排

8.4挖坑法分区方法代码(直到在纸上如何画图即可)

8.5三路快排 (了解)

9.堆排序(非常稳定)

10.冒泡排序(时间复杂度o(n^2))

 11.面试题:海量数据的排序处理(常考)

题外话:设置注释

七大排序代码:

1.排序是什么?

排序就是一串记录,按照其中的某个或者某些关键字的大小,递增或递减的排列起来的操作。

2.排序的稳定性

两个相等的数据,如果经过排序,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法。

八大排序------数据结构_第1张图片

 为什么稳定性这么重要?

 3.七大排序的时间复杂度与空间复杂度

八大排序------数据结构_第2张图片

这里都是内部排序(一次性将所有待排序的数据放入内存中进行排序,基于元素之间的比较的排序)

而外部排序 :依赖硬盘(外部存储器)进行的排序算法,桶排序、基数排序、基数排序(对于数据集合的要求非常高,只能在特定的场合下使用)。时间复杂度都是O(n),应用场景非常局限,只能在特定的场景下去应用。内存放不下,需要外部存储。

器,称为海量排序算法。

4.选择排序

每次从无序区间中选择一个最大或最小值,存放在无需区间的最前或者最后的位置(此位置的元素已经有序) ,直到所有的数据都排序结束。

为什么选择排序不稳定?

八大排序------数据结构_第3张图片

选择排序的进阶版本

双向选择排序,一次排序过程中,同时选出最大值和最小值,放在无需区间的最后和最前。 

5.插入排序(O(n^2)) 类似于打牌(稳定)

思路:将集合分为俩个区间,i是当前已经便利的元素,一个已经排序的区间[0...i),一个待排序的区间(i...n]。

每次从待排序区间找到一个元素,放入已排序的区间的合适位置,直到整个数组有序。

在近乎有序的数组下,插入排序的数组性能非常好,甚至优于nlog(n)的排序。

插入排序和选择排序最大的不同在于:当插入排序当前遍历的元素>前驱元素时,此时可以提前结束内层循环。

极端场景下,当集合是一个完全有序的集合,插入排序内层循环一次都不走,插入排序时间复杂度变为O(n)。

插入排序经常用作高阶排序算法的优化手段之一

了解:插入排序优化的手段:(用二分查找法)

折半插入排序:

首先:二分查找的前提是该集合必须是一个有序的集合。

而在插入排序中,我们每次都是在有序区间中找一个合适的插入位置,所以可以使用二分查找法来定位元素的插入位置。

这里注意这个公式为什么要这么写:(是为了防止溢出)

八大排序------数据结构_第4张图片

6.希尔排序:缩小增量的排序(不稳定)

 先选定一个整数(gap,gap一般都选数组长度的一半或者1/3)

将待排序的数组先按照gap分组,不同组之间使用插入排序,排序之后,再将gap/==2或者gap/=3,重复上述的流程,直到gap=1

当gap=1时,整个数组已经被调整的近乎有序,此时就是插入排序最好的场景,最后再在整个数据上进行一次插入排序。

八大排序------数据结构_第5张图片

八大排序------数据结构_第6张图片

插入排序和希尔排序,当gap=1时,公式完全一样。

八大排序------数据结构_第7张图片

八大排序------数据结构_第8张图片

希尔排序为什么要不断地向前比较,能否从零开始向后看gap步?

例:3-----2-----1

不断向后比较

2-----3-----1

2-----1-----3

这样的话,2就没办法换到合适的位置了 

7.归并排序(和希尔排序很像)

思想:将原数组不断拆分,一直拆到每个子数组只有一个元素,第一个阶段结束,归而为1这个阶段结束。

并:将相邻的两个数组合并为一个有序的数组,直到整个数据有序。

分:用的是递归

并:第一步:先创建一个大小为合并之后数组大小的临时数组aux,将数组的值拷贝过去。

第二步:如下

八大排序------数据结构_第9张图片

 为何合并过程需要创建一个新的aux数组?

防止在合并过程中,因为小元素要覆盖大的元素,丢失某些元素

归并排序是一个稳定的nlogN排序算法,此处的稳定指的是时间复杂度稳定(无论集合中的元素如何变化,归并排序的时间复杂度一直都是nlogN,不会退化为O(N^2))且归并排序也是一个稳定性的排序算法(相等元素的先后顺序并不会发生改变)。

(递归类似于树结构,n不断的2/2/2/2,直到只剩下一个元素,这就是logN(树的高度)级别的样子)

递归的深度就是我们拆分数组所用的时间,就是树的高度,logN。

 public static void mergeSort(int []arr){
        mergeSortInternal(arr,0,arr.length-1);
    }
    //在arr[l..r]进行归并排序
    private static void mergeSortInternal(int[] arr, int l, int r) {
        if(l>=r){
            //当前数组只剩下一个元素,归过程就结束了
            return;
        }
        int mid=l+((r-1)>>1);
        //将原数组拆分为左右俩个小区间,分别递归进行归并排序
        mergeSortInternal(arr,l,mid);
        mergeSortInternal(arr,mid+1,r);
        //merge操作
        merge(arr,l,mid,r);
    }

而o(n)来自于合并两个子数组的过程merge,就是一个数组的遍历

八大排序------数据结构_第10张图片

综上为非常稳定的nlogN。 

归并排序的两点优化:

1.当左右两个子区间走完子函数后,左右两个区间已经有序了

如果此时arr[mid]

arr[mid]已经是左区间的最大值

arr[mid+1]已经是右区间的最小值

=>整个区间已经有序了,每必要在执行merge过程,只有左右俩个区间还有先后顺序不同时,才merge。

2.在小区间上,我们可以直接使用插入排序来优化,没必要一直拆分到1位置

r-1<=15,使用插入排序性能很好的。

可以减少归并的递归次数 。

8.快速排序(基于算法导论中的分区思想)重要

改进:

八大排序------数据结构_第11张图片

 随机选择的代码:

八大排序------数据结构_第12张图片

8.1随机化快排的问题:

当待排序的集合中出现大量重复问题时,就会导致某个分区的元素过多(相等的元素全在一个区间中),造成递归过程中,递归树严重不平衡,快排倒退为O(n^2)。

解决办法,把相同的元素均衡在俩个区间。(二路快排)

什么是栈溢出?

 八大排序------数据结构_第13张图片

几数取中,一般都是三数取中,在arr[left],arr[mid],arr[right]中选择一个中间值作为基准

2,3都能避免递归过程中在接近有序的数组上,快速排序分区严重不平衡的问题

8.2有大量重复数据的数组上,快排仍然会退化。

原因:极端情况,当100w个元素全都是一个值的时候,分区之后,=v的集合中,二叉树又退化成了单支树。

8.2快排的非递归实现

代码如下:

public static void quickSortNonRecursion(int[] arr){
        Deque stack=new ArrayDeque<>();
        //栈中保存当前集合的开始位置和中止位置
        int l=0;
        int r=arr.length-1;
        stack.push(r);
        stack.push(l);
        while(!stack.isEmpty()){
            //栈不为空,说明子区间还没有处理完毕
            int left=stack.pop();
            int right=stack.pop();
            if(left>=right){
                //区间只有一个元素
                continue;
            }
            int p=partition(arr,left,right);
            //依次将右区间的开始和结束位置入栈
            stack.push(right);
            stack.push(p+1);
            //再将左侧区间的开始和结束位置入栈
            stack.push(p-1);
            stack.push(left);
        }
    }

8.3二路快排

思路:将相等的元素均分到两个子区间

八大排序------数据结构_第14张图片

private static int partition2(int[] arr, int l, int r) {
        int randomIndex=random.nextInt(l,r);
        swap(arr,l,randomIndex);
        int v=arr[l];
        //arr[l+1..i]<=v
        //[l+1..l+1)=0
        int i=l+1;
        //arr(j..r]>=v
        //(r..r]=0;
        int j=r;
        while(true){
            //i从前向后扫描,碰到第一个>=v的元素停止
            while(i<=j&&arr[i]v) {
                j--;
            }
            if(i>=j){
                break;
            }
            swap(arr,i,j);
            i++;
            j--;
        }
        //j落在最后一个<=v的元素身上
        swap(arr,l,j);
        return j;
    }

8.4挖坑法分区方法代码(直到在纸上如何画图即可)

这种方法没有元素交换,只有赋值,理论上会快一点。

这个思路主要用在笔试选择题。

注意:挖坑法必须要先从后向前扫描,再从前向后扫描。

八大排序------数据结构_第15张图片

 如果是从前向后扫描,就会有问题,会把大的值换到最前面去

结果:

八大排序------数据结构_第16张图片

8.5三路快排 (了解)

在一次分区函数的操作中,把所有相等的元素都放在最终位置,只需要在大于v和小于v的子区间上进行快排,所有相等的元素就不再处理了。

八大排序------数据结构_第17张图片

9.堆排序(非常稳定)

思想:以最大堆为例,依次取出最大值,可得到降序数组,无法在原数组上进行排序,还得创建一个和当前数组相同的堆,堆的时间复杂度为o(n)。

八大排序------数据结构_第18张图片

八大排序------数据结构_第19张图片

10.冒泡排序(时间复杂度o(n^2))

冒泡排序代码:

八大排序------数据结构_第20张图片

 11.面试题:海量数据的排序处理(常考)

假设现在待排序的数据有100G,但是内存只有1GB,如何排序这100GB的数据呢?

八大排序------数据结构_第21张图片

12 几种快排总结

快速排序是不稳定的。

nlogn,n是插入排序的insertionSort()方法,logn是递归函数的调用次数(树的高度)。

八大排序------数据结构_第22张图片

 

题外话:设置注释

 八大排序------数据结构_第23张图片

七大排序代码:

package sort;

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.concurrent.ThreadLocalRandom;

public class SevenSort {
    public static final ThreadLocalRandom random=ThreadLocalRandom.current();
    /*
    * 选择排序,时间复杂度o(n)
    * */
    public static void selectionSort(int [] arr){
        //最开始无序区[0...n],有序区间为[]
        //当无序区间只剩下一个元素时,整个集合已经有序。所以length-1
        for(int i=0;i arr[max]) {
                    max = i;
                }
            }
            //min索引现在一定是当前区间的最小值索引,与low交换位置
            swap(arr, low, min);
            if (max == low) {
                //最大值已经被换到min的位置
                max = min;
            }
            //最大值落在了high的位置
            swap(arr, max, high);
            low += 1;
            high -= 1;
        }
    }
    /*
    * 直接插入排序
    * 每次从无序区间中拿第一个值插入到已经排序区间的合适位置,直到整个数组有序。
    *已排序区间[0..i)
    * 待排序区间[i...n]
    * */
        public static void insertionSort(int [] arr) {
            for (int i = 1; i < arr.length; i++) {
                //待排序区间的第一个元素arr[i]
                //从待排序区间的第一个元素向前看,找到合适的插入位置
                //               for (int j = i; j >0; j--) {
//                    //arr[j-1]已排序区间的最后一个元素
//                    if(arr[j]>=arr[j-1]){
//                        //相等也不交换,保证稳定性
//                        //此时说明arr[j]>已排序区间的最大值,说明arr[j]已经有序了
//                        //直接下次循环
//                        break;
//                    }else{
//                        swap(arr,j,j-1);
//                    }
//                }
                //更简单的写法,和上面一样
                for (int j = i; j >= 1 && arr[j] < arr[j - 1]; j--) {
                    swap(arr, j, j - 1);
                }
            }
        }
    /*
    * 折半查找法
    * */
    public static void insertionSortBS(int [] arr){
        //有序区间[0..i)
        //无序区间[i...n]
        for (int i = 1; i < arr.length ; i++) {
            //val当前无序区间中的一个值
            int val=arr[i];
            int left=0;
            int right=i;
            while(left>1);

                if(val=arr[mid]
                    left=mid+1;
                }
            }
            //搬移left...i的元素
            for (int j=i;j>left;j--){
                arr[j]=arr[j-1];
            }
            //left就是val插入的位置
            arr[left]=val;
        }
    }
    /*
    * 希尔排序
    * */
    public static void shellSort(int [] arr){
        int gap= arr.length >>1;
        while(gap>1){
            //预处理阶段
            insertionSortByGap(arr,gap);
            gap=gap>>1;
        }
        //此时gap等于1,整个集合已经接近有序,只需要全集合来一次插入排序即可
        insertionSort(arr);
    }
    /*
    *按gap分组进行插入排序
    * */
    private static void insertionSortByGap(int[] arr, int gap) {
        for (int i = gap; i < arr.length ; i++) {
            //不断向前扫描相同的gap的元素
            //j-gap从j位置开始向前还有相同步数的元素
            for (int j = i; j-gap>=0&&arr[j]=r){
            //当前数组只剩下一个元素,归过程就结束了
            return;
        }
        int mid=l+((r-1)>>1);
        //将原数组拆分为左右俩个小区间,分别递归进行归并排序
        //走完这个函数之后arr[l..mid]已经有序
        mergeSortInternal(arr,l,mid);
        mergeSortInternal(arr,mid+1,r);
        //只有左右俩个区间还有先后顺序不同时,才merge。
        //merge操作
        if(arr[mid]>arr[mid]+1) {
            merge(arr, l, mid, r);
        }
    }
    /*
    * 合并俩个子数组arr[l..mid]和arr[mid+l...r]
    * 为一个大的有序数组
    * */
    public static void merge(int[] arr,int l,int mid,int r){
        if(r-l<=15){
            //小区间直接插入排序
            insertionSort(arr,l,r);
            return;
        }
        //先创建一个临时数组
        int[] aux =new int[r-l+1];
        //将arr元素拷贝到aux上
        for (int i = 0; i < aux.length; i++) {
            //新数组与原数组差了l个偏移量
            arr[i] = arr[i + l];
        }
        //i 表示左侧小数组的开始索引
        //j 表示右侧小数组的开始索引
            int i=1;
            int j=mid+1;
        //原地进行arr数组的合并,k表示当前合并原数组的索引下标
            for (int k = l; k <=r ; k++) {
                if(i>mid){
                    //左侧期间已经处理完毕,只需要将右侧区间的值拷贝到原数组即可
                    arr[k]=aux[j-1];
                    j++;
                }else if(j>r){
                    //右侧区间已经处理完毕,只需要将左侧区间的值拷贝到原数组即可
                    arr[k]=aux[i-1];
                }else if(aux[i-1]<=aux[j-1]){
                    //此时左侧区间的元素值较小,相等元素放在左区间保持稳定性
                    arr[k]=aux[i-1];
                    i++;
                }else{
                    //右侧区间的元素较小
                    arr[k]=aux[j-1];
                    j++;
                }
            }
        }
/*
* 在arr[l..r]使用插入排序
* */
    private static void insertionSort(int[] arr, int l, int r) {
        for (int i = l+1; i <=r; i++) {
            for (int j=i;j=r){
        // return;
        // }
        //先获取分区点
        //所谓分区点就是经过分区函数后,某个元素落在了最终位置
        //分区点左侧全都是小于该元素的区间,分区点右侧全都是>=该元素的区间
        int p=partition(arr,l,r);
        //递归重复在左区间和右区间重复上述流程
        quickSortInternal(arr, l, p-1);
        quickSortInternal(arr, p+1, r);
    }
    //在arr[l..r]上的分区函数,返回分区点的索引
    private static int partition(int[] arr, int l, int r) {
        //随机在当前数组中选一个数
        int randomIndex=random.nextInt(l,r);
        swap(arr,l,randomIndex);
        int v=arr[l];
        //arr[l+1,j]=v
        //i表示当前正在扫描的元素
        int j=l;
        for (int i = l+1; i <=r ; i++) {
            if(arr[i] stack=new ArrayDeque<>();
        //栈中保存当前集合的开始位置和中止位置
        int l=0;
        int r=arr.length-1;
        //先入右后入左,所以先出左后出右
        stack.push(r);
        stack.push(l);
        while(!stack.isEmpty()){
            //栈不为空,说明子区间还没有处理完毕
            int left=stack.pop();
            int right=stack.pop();
            if(left>=right){
                //区间只有一个元素
                continue;
            }
            int p=partition(arr,left,right);
            //依次将右区间的开始和结束位置入栈
            stack.push(right);
            stack.push(p+1);
            //再将左侧区间的开始和结束位置入栈
            stack.push(p-1);
            stack.push(left);
        }
    }
 /*
 *双路快排
 * */
    public static void quickSort2(int []arr){
            quickSortInternal2(arr,0,arr.length-1);
    }

    private static void quickSortInternal2(int[] arr, int l, int r) {
        if(r-l<=16){
            insertionSort(arr,l,r);
            return;
        }
        int p=partition2(arr,l,r);
        quickSortInternal2(arr,l,p-1);
        quickSortInternal(arr,p+1,r);
    }
/*
* 二路开排的分区
* */
    private static int partition2(int[] arr, int l, int r) {
        int randomIndex=random.nextInt(l,r);
        swap(arr,l,randomIndex);
        int v=arr[l];
        //arr[l+1..i]<=v
        //[l+1..l+1)=0
        int i=l+1;
        //arr(j..r]>=v
        //(r..r]=0;
        int j=r;
        while(true){
            //i从前向后扫描,碰到第一个>=v的元素停止
            while(i<=j&&arr[i]v) {
                j--;
            }
            if(i>=j){
                break;
            }
            swap(arr,i,j);
            i++;
            j--;
        }
        //j落在最后一个<=v的元素身上
        swap(arr,l,j);
        return j;
    }
    /*
    * 三路快排
    * */
    public static void quickSort3(int[]arr){
        quickSortInternal3(arr,0,arr.length-1);
    }
    public static void quickSortInternal3(int[] arr,int l,int r){
        if(r-l<=15){
            insertionSort(arr,l,r);
            return;
        }
        int randomIndex =random.nextInt(l,r);
        swap(arr,l,randomIndex);
        int v=arr[l];
        //这些变量的取值一定满足区间定义
        //arr[l+1..lt]v
        //gt是第一个>v的元素
        int gt=r+1;
        //i从前向后扫描和gt重合时,所有元素就处理完毕
        while(iv){
               //交换到gt-1
               swap(arr,i,gt-1);
               gt--;
               //此处i不++,交换来的gt-1还没有处理
           }else{
               //此时arr[i]=v
               i++;
           }
        }
    //lt落在最后一个v
        quickSortInternal3(arr,gt,r);
    }
    /*
    * 堆排序
    * */
    public static void heapSort(int []arr){
        for (int i =(arr.length-1-1)/2; i >=0 ; i--) {
            siftDown(arr,i,arr.length);
        }
        //此时数组已经被调整为最大堆
        for(int i=arr.length-1;i>0;i--){
            //arr[0]栈顶元素,为当前堆的最大值
            swap(arr,0,i);
            siftDown(arr,0,i);
        }
    }
    /*
    * 冒泡排序
    * */

    public static void bubbleSort(int []arr){
        for (int i = 0; i < arr.length; i++) {
            boolean isSwaped =false;
            for(int j=0;j< arr.length-1-i;j++){
                if(arr[j]>arr[j+1]){
                    swap(arr,j,i+1);
                    isSwaped=true;
                }
            }
        }
    }
    /*元素下沉操作
     * */
    //length数组长度
    public static void siftDown(int[] arr,int i,int length){
        while(2*i+1arr[j]) {
                j=j+1;
            }
            //j是左右子树的最大值
            if(arr[i]>arr[j]){
                //下沉结束
                break;
            }else{
                swap(arr,i,j);
            }
        }
    }
    private static void swap(int []arr,int i,int j){
        int temp =arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
    //普通测试
    public static void main(String[] args){
        int []arr={9,2,5,7,5,4,3,6};
        selectionSort(arr);
        System.out.println(Arrays.toString(arr));
    }
   /* //运行测试代码
    public static void main(String [] args ) {
        int n = 10000;
        int[] arr = new int[n];
        ThreadLocalRandom random = ThreadLocalRandom.current();
        for (int i = 0; i < n; i++) {
            arr[i] = random.nextInt(0, Integer.MAX_VALUE);
        }
        int [] arr1 =Arrays.copyOf(arr,n);
        //当前的系统时间
        long start =System.nanoTime();
        heapSort(arr1);
        long end =System.nanoTime();
       if(isSorted(arr1)){
           System.out.println("冒泡排序共耗时:"+(end-start)/1000000+"ms");
       }
    }
       *//* int[] arr ={7,4,5,3,2,1,9,10,8,6};
        heapSort(arr);
        System.out.println(Arrays.toString(arr));*//*
    //性能测试代码
    public static boolean isSorted(int []arr){
        for(int i=0;iarr[i+1]){
                System.out.println("sort error");
                return false;
            }
        }
        return true;
    }*/

}

你可能感兴趣的:(数据结构,数据结构,排序算法,算法,java)