轻松掌握排序算法

1.插入排序

基本思想:

直接插入排序是一种简单的插入排序法,

其基本思想是: 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。实际中我们玩扑克牌时,就用了插入排序的思想。

代码:

 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 {
                    array[j+1]=tmp;
                    break;
                }
            }
            array[j+1]=tmp;
        }
    }

1.定义两个“指针”,i和j

2.将i初始1为1,j初始为0

3.定义一个tmp来接收i的每次循环是i下标所指数组元素的值

4.而每次i的循环,i下标前面的数都是有序的

5.每次i循环让j从i-1下标开始往前遍历,如果j下标的值大于tmp,那么就让j下标的值1往后走一步(相当于每次将j下标的位置腾出来,如果下次j循环,j-1下标的值大于就将其往后移,否则就将该位置设置为tmp),否则将j+1下标位置的值修改为tmp

6.如果j遍历完tmp还没有放置,那么tmp就是最小的数,这时j为-1,那么j+1也为0,就是第一个数的位置,如果j没走完执行array[j+1]=tmp,这时的操作相当于多余的,因为在循环里面执行了一次array[j+1]=tmp,已经将tmp放置,并且跳出循环,j的值没有改变

总结:

直接插入排序的特性总结:

1. 元素集合越接近有序,直接插入排序算法的时间效率越高

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

3. 空间复杂度:O(1),它是一种稳定的排序算法

4. 稳定性:稳定

2.希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成多个组, 所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达 =1时,所有记录在同一组内排好序。

希尔排序是对直接插入排序的一种变形,实行的是先分组在各组进行插入排序

    public static void shellSort(int[]array){
        int gap=array.length;
        while(gap>1){
gap=gap/2;//gap表示每组之间相邻元素之间的下标差
shell(array,gap);
        }
    }

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

希尔排序每次分组排序完,再次分组时,gap会变小,也就是每一组的元素会变多,直到最后一次gap=1,这时就是一个插入排序

希尔排序的特性总结:

1. 希尔排序是对直接插入排序的优化。

2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定

4.稳定性:不稳定

3.选择排序

基本思想:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

2.2.2 直接选择排序:

在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素 ,若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换 在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

1.按从小到大排序:

    public static void selectSort(int[]array){
        for (int i = 0; i < array.length ; i++) {
            int max=0;
            int j = 0;
            for (; j <array.length-i-1 ; j++) {
                if(array[j]>array[max]){
                    max=j;
                }
            }
            swap(array,j,max);
        }
    }

1.每次循环用max记录为筛选的最大值下标,并将其与该次循环最后一个位置的值进行交换

2.每一次循环结束i++,j-i得到当前循环的个数

3.按从大到小排,只需要改变对应的大于小于号

2.从两头开始选择排序:

   public static void selectSort2(int[]array){
       int left=0;
       int right= array.length-1;
       while(left<right){
           int max=left;
           int min=left;
           for (int i = left; i <=right ; i++) {
               if(array[i]>=array[max]){
                   max=i;
               }
               if (array[i]<array[min]){
                   min=i;
               }
           }
           swap(array,left,min);
           swap(array,right,max);
           left++;
           right--;
       }
    }

1.定义一个left和right分别指向未排数组的最左和最右下标

2.循环遍历未排数组的值,找到最大值和最小值的下标,分别用max和min记录下标

3.最后用left和min交换,rigth和max交换

4.right--,left++循环往复,直到left==right

直接选择排序的特性总结:

1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

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

3. 空间复杂度:O(1)

4. 稳定性:不稳定

4.堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

1.将数组创建成大根堆或小根堆

      create(array);

 从最后一个节点的父亲节点开始,向下调整,调整完毕父亲节点向前走,直到走到第一个节点

 private static void create(int[]array){
        for (int parent = (array.length-2)/2; parent >=0 ; parent--) {
            sifDown(array,parent,array.length);
        }
    }

向下调整:

  private static void sifDown(int[]array,int parent,int end){
        int child=parent*2+1;
        while(child<end){
            if(child+1<end&&array[child+1]>array[child]){
                child++;
            }
            if(array[parent]<array[child]){
                swap(array,parent,child);
                parent =child;
               child=child*2+1;
            }else {
                break;
            }
        }
    }

向下调整每次将父亲节点的作为根节点,将以父亲节点作为根节点的树,调整为大根堆或小根堆,从后往前遍历每一个父亲节点,即可将整棵树变为大根堆或小根堆

2.对大根堆进行排序

由于大根堆的第一个元素是整棵树中最大的,我们只需要将位置的值和未排序的最后一个位置的值进行交换,然后再将第一个元素向下调整,调整长度在未调整长度的基础上减一

循环往复,直到未排序长度为0时,堆排序完成

   public static void heapSort(int[]array){
        create(array);
        int end=array.length-1;
        while(end>0){
            swap(array,0,end);
            sifDown(array,0,end);
            end--;
        }
    }

堆排序整体代码:

    public static void heapSort(int[]array){
        create(array);
        int end=array.length-1;
        while(end>0){
            swap(array,0,end);
            sifDown(array,0,end);
            end--;
        }
    }
    
    private static void create(int[]array){
        for (int parent = (array.length-2)/2; parent >=0 ; parent--) {
            sifDown(array,parent,array.length);
        }
    }


    private static void sifDown(int[]array,int parent,int end){
        int child=parent*2+1;
        while(child<end){
            if(child+1<end&&array[child+1]>array[child]){
                child++;
            }
            if(array[parent]<array[child]){
                swap(array,parent,child);
                parent =child;
               child=child*2+1;
            }else {
                break;
            }
        }
    }

堆排序特性总结:

1. 堆排序使用堆来选数,效率就高了很多。

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(1)

4. 稳定性:不稳定

5.冒泡排序

基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特 点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

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

冒泡排序的特性总结:

1. 冒泡排序是一种非常容易理解的排序

2. 时间复杂度:O(N^2),没有优化时,优化后可能会达到O(N)

3. 空间复杂度:O(1)

4. 稳定性:稳定

6.快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:

任取待排序元素序列中的某元 素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有 元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

// 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int[] array, int left, int right)
{
    if(right - left <= 1)
        return;
    
    // 按照基准值对array数组的 [left, right)区间中的元素进行划分
    int div = partion(array, left, right);
    
    // 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right)
    // 递归排[left, div)
    QuickSort(array, left, div);
    
    // 递归排[div+1, right)
    QuickSort(array, div+1, right);
}

上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,同学们在写递归框架时可想想二叉树前序 遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。 将区间按照基准值划分为左右两半部分的常见方式有:

1.挖坑法

    private static int partition(int[]array,int left,int right){
        int tmp=array[left];
        while (left<right){
            while (left<right&&array[right]>=tmp){
                right--;
            }
            array[left]=array[right];
            while (left<right&&array[left]<=tmp){
                left++;
            }
            array[right]=array[left];
        }
array[left]=tmp;
        return left;
    }

1.挖坑法是每次将区间的最左的元素作为分界点,用tmp记录下最左边下标的元素值

2.从该区间内,最右边的位置开始循环,如果right所指元素小于tmp,把该元素放到left下标所指位置,然后循环left,如果left所指元素大于tmp,把该元素放到right下标所指位置,循环往复

3.最后将tmp放到left下标所指位置

2.Hoare法

   private static int partitionHoare(int[]array,int left,int right){
        int tmp=array[left];
        int tmpLeft=left;
        while(left<right){
            while(left<right&&array[right]>tmp){
                right--;
            }
            while (left<right&&array[left]<tmp){
                left++;
            }
            swap(array,left,right);
        }
        swap(array,tmpLeft,left);
        return left;
   }

Hoare法和挖坑发很像,都是以最左边的元素为基准,将数组分开,比基准小的放左边,比基准大的放右边

3.前后指针法

写法一: 

   private static int partition2(int[]array,int left,int right){
        int prv=left;
        int cur=left+1;
        while(cur<=right){
            if(array[cur]<array[left]&&array[++prv]!=array[cur]){
                swap(array,prv,cur);
            }
            cur++;
        }
swap(array,left,prv);
        return prv;
   }

写法二:

   private static int partition3(int[]array,int left,int right){
        int d=left+1;
        int pivod=array[left];
       for (int i = left+1; i <array.length ; i++) {
           if(array[i]<pivod){
               swap(array,d,i);
               d++;
           }
       }
       swap(array,d-1,left);
       return d-1;
   }
    

快速排序优化:

1.    对于较小的数组使用插入排序:

    ●    当待排序的子数组长度小于一定阈值时,切换到插入排序。因为在小数组情况下,插入排序通常比快速排序更高效。

    private static void quick(int[]array,int left,int right){
       if(left>=right){
           return;
       }
       if(right-left<10){
           insertSortRange(array,left,right);
           return;
       }
       int pivot=partition3(array,left,right);
quick(array,left,pivot-1);
quick(array,pivot+1,right);
    }

    private static void insertSortRange(int[]array,int left,int right){
        int tmp=array[left];
        int i=left+1;
        for (; i <=right ; i++) {
            int j = i-1;
            for (; j >=left ; j--) {
                if(array[j]>tmp){
                    array[j+1]=array[j];
                }else {
                    array[j+1]=tmp;
                    break;
                }
            }
            array[j+1]=tmp;
        }
    }

当数组元素小于10时,我们就用插入排序法


    2.    三数取中选择基准值:

    ●    从待排序序列的首、尾和中间位置选取三个元素,然后取这三个元素的中位数作为基准值。

    ●    这样可以更好地平衡基准值的选择,避免极端情况。例如,对于序列{2, 10, 8, 15, 5},首元素为 2,尾元素为 5,中间元素为 8,取这三个数的中位数 5 作为基准值。

    private static void quick(int[]array,int left,int right){
       if(left>=right){
           return;
       }
       if(right-left<10){
           insertSortRange(array,left,right);
           return;
       }
       int mid=getMidNum(array,left,right);
       swap(array,mid,left);
       int pivot=partition3(array,left,right);
quick(array,left,pivot-1);
quick(array,pivot+1,right);
    }

    private static int getMidNum(int[]array,int left,int right){
        int mid=(left+right)/2;
        if(array[left]<array[right]){
            if(array[mid]<array[left]){
                return left;
            }else if(array[mid]>array[right]){
                return right;
            }else {
                return mid;
            }
        }else {
            if(array[mid]<array[right]){
                return right;
            } else if (array[mid]>array[left]) {
                return left;
            }else {
                return mid;
            }
        }
    }

快速排序非递归写法:

    private static void quick1(int[]array,int left,int right){
        if(left>=right){
            return;
        }
        Stack<Integer>st=new Stack<>();
        st.push(left);
        st.push(right);
while (!st.empty()){
    int r=st.pop();
    int l=st.pop();
    if(r-l<=1){
       continue;
    }
   int d= partition(array,l,r);
    st.push(d+1);
    st.push(r);
    st.push(l);
    st.push(d);
}
    }

快速排序总结:

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(logN)

4. 稳定性:不稳定

7.归并排序

基本思想:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使 子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

代码:

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

    private static void mergeSortTmp(int[]array,int left,int right){
if(left>=right){
    return;
}
int mid=(left+right)/2;
mergeSortTmp(array,left,mid);
mergeSortTmp(array,mid+1,right);

merge(array,left,mid,right);
    }
    
    private static void merge(int[]array,int left,int mid,int right){
        int k=0;
        int[]tmp=new int[right-left+1];
        int s1=left;
        int s2=mid+1;
        while (s1<=mid&&s2<=right){
            if(array[s1]<array[s2]){
                tmp[k++]=array[s1++];
            }else {
                tmp[k++]=array[s2++];
            }
        }
        while(s1<=mid){
            tmp[k++]=array[s1++];
        }
        while (s2<=right){
            tmp[k++]=array[s2++];
        }
        for (int i = 0; i <k; i++) {
            array[left+i]=tmp[i];
        }
    }

归并排序总结:

1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(N)

4. 稳定性:稳定

8.海量数据的排序问题

外部排序:

排序过程需要在磁盘等外部存储进行的排序

前提:内存只有 1G,需要排序的数据有 100G 因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序

1. 先把文件切分成 200 份,每个 512 M

2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以

3. 进行 2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

9.排序算法复杂度及稳定性分析

排序算法最好平均最坏空间复杂度稳定性
冒泡排序O(n)O(n^2)O(n^2)O(1)稳定
插入排序O(n)O(n^2)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
希尔排序O(n)O(n^1.3)O(n^2)O(1)不稳定
堆排序O(n*log(n))O(n*log(n))O(n*log(n))O(1)不稳定
快速排序O(n*log(n))O(n*log(n))O(n*log(n))O(n^2)不稳定
归并排序O(n*log(n))O(n*log(n))O(n*log(n))O(n)稳定

 

10.其他非基于比较排序(了解)

10.1计数排序

思想:

计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

1. 统计相同元素出现次数

2. 根据统计的结果将序列回收到原来的序列中

计数排序:

https://zhuanlan.zhihu.com/p/26595385?group_id=842495057868226560

计数排序的特性总结:

1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。

2. 时间复杂度:O(MAX(N,范围))

3. 空间复杂度:O(范围)

4. 稳定性:稳定

基数排序:

https://www.runoob.com/w3cnote/radix-sort.html

桶排序:

【排序】图解桶排序_桶排序图解-CSDN博客

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值