数据结构2: 七种常见的排序算法总结篇(Java实现)

排序算法自我总结

排序:使得序列成了按照关键词有序的序列的过程。
排序的种类:
1.稳定性排序和不稳定排序:主关键词排序,是否会改变原有的顺序。
2.内排序和外排序:内排序,待排序的记录均会存入内存中。外排序则部分存入。
3.简单排序和改进排序:简单排序有:冒泡排序,选择排序和插入排序。

1. 冒泡排序(从小到大)

1.1 基本原理

两两比较相邻记录的关键词,如果反序则交换,直到没有反序为止(常常从后向前比较)

1.2 过程步骤

1)比较相邻的两个数据,如果第二个数更小,则交换
2)从后向前两两比较数据大小,一直到第一个数,使得最小值到第一个位置处。
3)重复上述过程,使得第二个数和第三个最小排好位置

1.3 代码实现

public class demo1 {
    public static void main(String[] args) {
        //冒泡排序
        int[] list=new int[]{6,5,4,3,2,1};
        maopao(list);
    }
    //方法:冒泡
    public static void maopao(int [] list){
        int temp;//交换临时变量
        for(int i=0;i<list.length;i++){
            for(int j= list.length-2; j>=i;j--){ //从倒数第二个开始
                if(list[j]>list[j+1]){
                    temp=list[j+1];
                    list[j+1]=list[j];
                    list[j]=temp;
                }
            }
        }
        System.out.println("list="+ Arrays.toString(list));
        //list=[1, 2, 3, 4, 5, 6]
    }
}

1.4 适用场景

  1. 平均时间复杂度为:O(n2) ; 空间复杂度为:O(1) ;稳定排序。
  2. 当大小满足前大于后时,不会改变顺序,是稳定排序;
  3. 时间复杂度较高,不适合海量数据

1.5 代码优化

1)冒泡排序按之前流程,不会中途结束,会比较完所有的数,故可以设置一个变量,如果进行一轮比较,没有交换顺序,则视为已经排好顺序了。
2)修改后:算法在数据基本有序的前提下,最好的时间复杂度为O(N),其他情况为O(N2)。

public class demo1 {
    public static void main(String[] args) {
        //冒泡排序
        int[] list=new int[]{1,2,4,3,5,6};
        maopao(list);
    }
    //方法:冒泡
    public static void maopao(int [] list){
        int temp;//交换临时变量
        for(int i=0;i<list.length;i++){
            boolean flag=false;
            for(int j= list.length-2; j>=i;j--){ //从倒数第二个开始
                if(list[j]>list[j+1]){
                    temp=list[j+1];
                    list[j+1]=list[j];
                    list[j]=temp;
                    flag=true;
                }
            }
            if(flag==false) break;
        }
        System.out.println("list="+ Arrays.toString(list));
    }
}

2. 选择排序

2.1 基本原理

在未排序序列中找到最小(大)元素,交换存放到排序序列的起始位置。

2.2 实现步骤

第一次遍历n-1个数,然后选出最小的数和第一个数进行交换
第二次遍历n-2个数,先出最小的数和第二个数进行交换

第 i 次遍历n-1-i个数,先出最小的数和第 i 个数进行交换

2.3 代码实现

public class 简单选择排序 {
    public static void main(String[] args) {
        //简单选择
        int[] list=new int[]{3,5,6,1,2,4};
        xuanze(list);
    }
    public static void xuanze(int[] list){
        //临时变量
        int temp=0;
        int minindex;
        for(int i=0;i<list.length;i++){
            //1)找出最小
            minindex=i;
            for(int j=i;j<list.length;j++){
                if(list[j]<list[minindex]) minindex=j;
            }
            System.out.println("minindex="+minindex);
            //2)进行交换(i与minindex的数据)
            temp=list[i];
            list[i]=list[minindex];
            list[minindex]=temp;
        }
        System.out.println("List="+Arrays.toString(list)); //List=[1, 2, 3, 4, 5, 6]
    }
}

2.4 适用场景

  1. 平均时间复杂度为:O(n2) ; 空间复杂度为:O(1) ;不稳定排序。
  2. 使用于小数据进行排序。

3. 插入排序

3.1 基本原理

简单而高效的算法。将一个记录插入到已经排好序的有序表中,得到一个新的,记录数加一的有序表

3.2 过程步骤

  1. 把待排序的数分为已排序和未排序两部分。第一个数默认已排序
  2. 从第二个数开始,向前比较,在已经排好序的数中找到合适插入第二个数的位置,并不停交换顺序。
  3. 重复上述过程,直到最后一个数被排好序。

3.3 代码实现

public class 直接插入排序 {
    public static void main(String[] args) {
        //直接插入测试用例
        int[] list=new int[]{3,5,6,1,2,4,8,9,7};
       method(list); //list=[1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
    //直接插入排序实现方法
    public static void method(int[] list){
        for(int i=0;i<list.length;i++){
            int index=i;
            int temp=0;
            //从后向前比较
            while(index>=1 && list[index-1]>list[index]){
                //交换位置
                temp=list[index-1];
                list[index-1]=list[index];
                list[index]=temp;
                //
                index--;
            }
        }
        System.out.println("list="+ Arrays.toString(list));
        //list=[1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
}

方法二:

    public static void method(int[] list){
        int temp;
        for(int i=1;i<list.length;i++){
            for(int j=i;j>=1;j--){
                //后小于前,交换
                if(list[j-1]>list[j]){
                    temp=list[j-1];
                    list[j-1]=list[j];
                    list[j]=temp;
                }else{
                    break;
                }
            }
        }
        System.out.println("list="+ Arrays.toString(list));
    }

方法三(最短):

    public static void method2(int[] list){
        //循环一:需要插入元素的位置遍历
        for(int i=1;i<list.length;i++){   //需要插入的数
            int temp=list[i];  //需要插入的数
            int j=i-1;//指向插入前面的一个元素
            //循环二的作用:从后向前比较,如果大于插入值,就先后移动
            for(j=i-1; j>=0 &&list[j]>temp; j=j-1){
                list[j+1]=list[j];
            }
            list[j+1]=temp;
        }
        System.out.println("list="+ Arrays.toString(list));
    }

3.4 适用场景

  1. 平均时间复杂度为:O(n2) ; 空间复杂度为:O(1) ;稳定排序。
  2. 对于数据量少的情况,常常把插入排序作为快速排序的 补充。
  3. 如果数据序列基本有序或者数据量小,使用插入排序会非常高效。

4. 希尔排序

4.1 基本原理

  1. 对于直接插入排序而言,当数基本有序或者数据量少的时候,能够达到很快的时间复杂度,但是如果是随机无序,那么就会非常慢。
  2. 希尔排序就是改进直接插入排序的功能:首先改变增量的大小,是的对数据少的数进行直接插入排序,随着增量的变小,数据量虽然越来越多,但是数据越来越有序,能够完美达到使德直接插入排序好的功能。

4.2 过程步骤

直接插入排序可看做默认增量变量为x=1,对x个数组,使用直接插入方法进行排序。
希尔排序初始增量变量为k,对k个数组使用直接插入方法进行排序,k的值不断减小,直到为一。

4.3 代码实现

public class 希尔排序 {
    public static void main(String[] args) {
        //直接插入
        int[] list=new int[]{3,5,6,1,2,4,8,9,7};
        method3(list);
    }
    //希尔排序基于方法三:
    public static void method3(int[] list){
        int x;
        for(x=list.length/2;x>=1; x=x/2) {
            //循环一:需要插入元素的位置遍历
            for (int i = x; i < list.length; i++) {   //需要插入的数
                int temp = list[i];  //需要插入的数
                int j = i - x;//指向插入前面的一个元素
                //循环二的作用:从后向前比较,如果大于插入值,就先后移动
                for (; j >= 0 && list[j] > temp; j=j-x) {
                    list[j + x] = list[j];
                }
                list[j + x] = temp;
            }
        }
        System.out.println("list="+ Arrays.toString(list));
    }
/**
 * 核心功能:希尔排序,在插入排序上面的改进
 */
public class demo4  {
    public static void main(String[] args) {
        int[] data={7,6,5,4,3,2,1};
        //设置步数:插入排序即为1
        for(int i=data.length/2;i>=1;i=i/2){
            //这两层循环和插入排序一模一样
            for(int j=i;j<data.length;j+=i){
                for(int z=j;z>=i;z-=i){
                    if(data[z]<data[z-i]){
                        int temp=data[z];
                        data[z]=data[z-i];
                        data[z-i]=temp;
                    }
                }
            }
            System.out.println("i="+i+"  "+Arrays.toString(data));
        }
        System.out.println(Arrays.toString(data));
    }
}

4.4 适用场景

  1. 平均时间复杂度为:O(n1.5-2) ; 空间复杂度为:O(1) ;不稳定排序。
  2. 希尔排序是**第一个突破O(n2)**的排序算法,是简单插入排序的改良版本
  3. 希尔排序的时间复杂度和增量的取值有关,时间复杂度最好可看做:O(n1.5).
  4. 常见的增量序列:[1,2,4,8,10] ; {1, 3, …, 2k-1}; (1, 5, 19, 41, 109,…),4i - 3*2i + 1

5. 堆排序

5.0 概念介绍

堆结构: 具有以下性质的完全二叉树

  1. 大顶堆:每个节点都大于或者等于它的左右孩子节点
  2. 小顶堆:每个节点都小于或者等于它的左右孩子节点

完全二叉树的优秀性质:
4. 完全二叉树有n个节点,那么就有n/2个非叶子节点
5. 第i个节点的双亲节点为/2
6. 第i个节点的连个孩子节点为:2i+1和2i+2(如果存在的话)

5.1 基本原理

  • 通过构建堆,依次取出堆顶元素,然后重新构造,再取出堆顶元素,得到有序序列

5.2 过程步骤

  1. 将待排序的序列构造为一个大顶堆,序列的最大值为堆顶的根节点,将它已到系列末尾,
  2. 然后将剩下的n-1个序列重新构造为一个堆,得到n个元素的次大值,
  3. 反复进行,最终得到有序序列。

代码实际步骤

  1. 首先得到非叶子节点n/2,然后遍历,依次调整位置。(初始值进行堆调整,得到一个大顶堆)
  2. 需要循环n-1次,依次把大顶堆的最大值交换到序列末尾。
  3. 调整函数的书写:首先判断根节点是否是最大(与孩子节点相比),不是最大就要调换顺序,然后进行递归。

5.3 代码实现

public class 堆排序 {
    public static void main(String[] args) {
        //堆排序
        int[] list = new int[]{3, 5, 6, 1, 2, 4, 8, 9, 7};
        Heap_Sort(list);
        System.out.println("list结尾="+ Arrays.toString(list));
    }
    
    //堆排序主方法:
    public static void Heap_Sort(int[] list){
        //1)对初始数据进行堆调整,获得大顶堆
        System.out.println("list初始="+ Arrays.toString(list));
        for(int i=list.length/2;i>=1;i--){
            System.out.println("i-1="+(i-1));
            Heap_Adjust(list,i-1,list.length);   //i-1为下标值
        }
        System.out.println("list初始="+ Arrays.toString(list));

        //2)取出大顶堆的堆顶,然后再进行堆调整(这个过程需要重复n-1次,进行微调)
        for(int j=list.length;j>1;j--){
            swop(list,0,j-1);                     //首位置与末位置进行交换
            Heap_Adjust(list,0,j-1);  //堆进行调整,需要调整的堆大小要减一
        }
    }

    //堆的子方法:堆调整
    public static void Heap_Adjust(int[] list,int head,int length){
        //1)左节点和右节点位置
        int left=2*head+1;
        int right=2*head+2;
        int maxIndex=head;  //假设原来的位置最大
        //2)如果左孩子和有孩子存在,比较值,替换
        if(left<length  &&   list[left]>list[maxIndex])  maxIndex=left;
        if(right<length  &&  list[right]>list[maxIndex]) maxIndex=right;
        //3)如果最大值位置变更了,需要交换,并递归操作下去
        if(maxIndex!=head){
            //交换
            swop(list,head,maxIndex);
            System.out.println("list交换时="+ Arrays.toString(list));
            //递归
            Heap_Adjust(list,maxIndex,length);
        }
    }

    //堆的子方法:交换元素(list[i]与list[i]
    public static void swop(int[] list, int i,int j){
        int temp=list[i];
        list[i]=list[j];
        list[j]= temp;
    }

}

5.4 适用场景

  1. 平均时间复杂度为:O(nlogn) ; 空间复杂度为:O(1) ;不稳定排序。
  2. 尤其是在解决诸如**“前n大的数”一类问题时**,几乎是首选算法。

6. 归并排序

6.1 基本原理

  1. 采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列

6.2 过程步骤

  1. 申请一个和序列相等的空间,用于存放临时数据
  2. 把序列进行对半平分,得到有序的左半边和有序的右半边,之后再进行合并(有合并方法)。
  3. 对步骤二中的左半边和右半边进行递归操作,直到处于有序状态。
  4. 算法核心:处理两个序列的合并部分,需要使用两个指针进行操作。

6.3 代码实现

package paixu;

import java.util.Arrays;

public class 归并排序 {
    public static void main(String[] args) {
        //归并排序
        int[] list = new int[]{3, 5, 6, 1, 2, 4, 8, 9, 7};
        mergeSort(list);
        System.out.println("list="+ Arrays.toString(list));
        
    }
    //1)主函数:顺便开辟新的空间
    public static void mergeSort(int[] arr){
        int[] temp =new int[arr.length];
        internalMergeSort(arr, temp, 0, arr.length-1);
    }
    //2)序列进行划分
    private static void internalMergeSort(int[] arr, int[] temp, int left, int right){
        //当left==right的时,已经不需要再划分了
        if (left<right){
            int middle = (left+right)/2;
            internalMergeSort(arr, temp, left, middle);          //左子数组
            internalMergeSort(arr, temp, middle+1, right);       //右子数组
            mergeSortedArray(arr, temp, left, middle, right);    //合并两个子数组
        }
    }
    // 3)合并两个有序子序列
    private static void mergeSortedArray(int arr[], int temp[], int left, int middle, int right){
        int i=left;
        int j=middle+1;
        int k=0;
        while (i<=middle && j<=right){
            temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }
        while (i <=middle){
            temp[k++] = arr[i++];
        }
        while ( j<=right){
            temp[k++] = arr[j++];
        }
        //把数据复制回原数组(嵌套已经排好序的部分)
        for (i=0; i<k; ++i){
            arr[left+i] = temp[i];
        }
    }
}

6.4 适用场景

  • 平均时间复杂度为:O(nlogn) ; 空间复杂度为:O(n) ;稳定排序。

6.5 代码优化

7. 快速排序

7.1 基本原理

  • 通过一趟排序,将待排记录分割为独立的两个部分,其中一部分关键字均比另一部分关键字小,然后通过递归,再分别对这两个部分进行分割排序,最终达到整个序列有序目的。

7.2 过程步骤

  1. 先从序列中选出一个数作为基准
  2. 两个指针指向序列的头和尾,如果左边指针比基准值大,于此同时右边指针比基准值小,则交换两个数据,直到两个指针走到一起(这个时候指针的位置是基准应该在的位置)
  3. 基准本应该在的位置的值基准实际在的位置的值进行交换。(这时基准位置的左边都是比基准小的数,基准右边都是比基准大的数)
  4. 以基准为分割线,分为两个区,对每个区重复2,3操作。

简便步骤

  1. 先从序列中选出一个数作为基准值;
  2. 将比基准值小的数都放到它的左边,比基准值大的数都放到它的右边。
  3. 以基准值分为两个区,再分别对这两个区重复操作1和2,直到各区间只有一个数。

7.3 代码实现

public class 快速排序 {
    public static void main(String[] args) {
        //直接插入
        int[] list = new int[]{3, 5, 6, 1, 2, 4, 8, 9, 7};
        quick_sort(list, 0, list.length - 1);
        System.out.println("list="+ Arrays.toString(list));
    }

    //方法:快速排序(使用方法递归)
    public static void quick_sort(int[] list, int begin, int end) {
        //1)截止条件
        if (begin > end) return;

        //2)递归中心
        int left = begin;
        int right = end;
        int temp = list[left];  //随机挑选一名幸运观众
            //(0)交换顺序,使得小于基准的都在左边,大于基准的都在右边,直到找到基准适合位置
        while (left < right) {
            //(1)右边要小于,左边要大于
            while(list[right] > temp && right>left) right--;
            while(list[left] <= temp && right>left) left++;
            //(2)交换
            if(right>left){
                int tem = list[left];
                list[left] = list[right];
                list[right] = tem;
            }
        }
        //(3)把找到的幸运观众也放到合适的位置上
        list[begin]=list[left];
        list[left]=temp;
        
        quick_sort(list, begin, left-1); //右边要减一
        quick_sort(list, left+1, end); 	 //左边要加一
    }
}

7.4 适用场景

  • 平均时间复杂度为:O(nlogn) ; 空间复杂度为:O(logn) ;不稳定排序。
  • 快速排序其实能够算作是冒泡排序的改进。
  • 第一个实现O(nlogn) 时间复杂度

7.5 代码优化

  • 优化选取枢轴:前面算法默认选取第一个数,这里可以采用方法:随机选取,三数取中,九数取中
  • 优化小数组时的排序方案:当数比较少的时候,没必要用快排,可以使用直接插入排序,通过一个判断长度判断实现。
  • 优化递归操作:减少不必要的递归。

8. 基于快速排序的TopK

  1. 要点:主要是在递归的时候,left+1的值与K进行比较
    1)当left+1==K,即找到了K个,直接返回。
    2)当left+1>K,即多了,则要再细分,尾巴变为left-1
    3)当left+1<K,即少了,则要扩大范围,头部变为left+1
  2. 思路
    1)特殊情况
    2)一般情况(交换)
    3)把要比较的那个值也放到对应位置
    4)递归操作(TopK还是全排列,区别就在这里)
  3. 代码
public static void method(int[] data,int first,int end,int K){

        //1)特殊情况
        if(first>end) return;

        int left=first;
        int right=end;
        int comindex=first;

        //2)一般情况(先右边,后左边,左边需要去等号)
        while(left<right){
            while(data[right]>data[first] && left<right) right--;
            while(data[left]<=data[first] && left<right) left++;

            //交换
            int temp=data[left];
            data[left]=data[right];
            data[right]=temp;
        }

        //3)与比较值交换first,left.
        int temp1=data[first];
        data[first]=data[left];
        data[left]=temp1;
        
        //4)递归(全排序)
//        method(data,first,left-1,K);
//        method(data,left+1,data.length-1,K);

        //4)递归规则的改变(TopK排序)(K不用边,因为序标是不变的)
        //注释:这里递归不能使用原来的left,不然相同时会陷入死循环
        if(left+1==K){
            return;
        }else if(left+1>K){
            method(data,first,left-1,K);
        }else{
            method(data,left+1,data.length-1,K);
        }
    }

9. 总结

9.1 算法的时间空间复杂度

综合:各大排序算法的时间复杂度和空间复杂度

不稳定的排序:快选希堆(快排,选择排序,希尔排序和堆排序)
最好用的排序(快速排序)

9.2 各类排序算法的使用场景

????
主要参考文章(如有侵权,联系立删):

https://zhuanlan.zhihu.com/p/42586566
https://www.runoob.com/w3cnote/sort-algorithm-summary.html
https://blog.csdn.net/weixin_43824059/article/details/88238177
快速排序:讲解详细
堆和堆排序:讲解详细
https://blog.csdn.net/zxzxzx0119/article/details/79826380
希尔排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值