2024年最全常见排序算法基本原理及实现(快排,归并,堆排,直接插入,2024年最新2024年抓住金三银四涨薪好时机

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

5.堆排序

5.1原理

在这里插入图片描述

基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数,**堆逻辑上是一棵完全二叉树
满足任意结点的值都大于等于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆反之,则是小堆,或者小根堆,或者最小堆,子树中节点的值没有关系。**就升序而言,我们创建一个大堆,每次堆顶的元素都是最大的,我们把它和堆的最后一个元素交换,堆的末尾元素就是最大值,也是数组的最后一个元素就是最大值,有序的元素下次交换调整时排除在排序范围内,之再调整交换堆使它继续为大根堆,之后重复上面的操作,第二次得到第二大元素,直到排序到堆顶元素为止。
在这里插入图片描述
注意: 排升序要建大堆;排降序要建小堆。
再简单总结下堆排序的基本思路:

a.将无序序列构建成一个堆,根据升序降序需求选择大根堆或小根堆;
b.将堆顶元素与末尾元素交换,将最大元素或者最小元素"沉"到数组末端;
c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个数组有序。

5.2实现

 public static void shiftDown(int[] array,int parent,int len) {
        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;//如果子树不为空,操作子树就行,改变的那颗子树
                child = 2\*parent+1;
            }else {
                break;
            }
        }
    }

 public static void createBigHeap(int[] array) {
        for (int i = (array.length-1-1)/2; i >= 0 ; i--) {
            shiftDown(array,i,array.length);
        }
    }
  /\*\*
 \* 时间复杂度:O(N\*logn)
 \* 空间复杂度:O(1)
 \* 稳定性:不稳定
 \* @param array
 \*/
    public static void heapSort(int[] array) {
        createBigHeap(array);
        int end = array.length-1;
        while (end > 0) {
            int tmp = array[0];
            array[0] = array[end];
            array[end] = tmp;
            shiftDown(array,0,end);
            end--;
        }
    }


5.3性能分析

时间复杂度:**O(n * log(n)),**可以看成一颗完全二叉树,每个节点都要进行交换,交换之后要进行向下调。整空间复杂度:O(1)

6冒泡排序

6.1原理

在这里插入图片描述
在待排序序列中,通过相邻数的比较,逐渐的将较大的数据移动到待排序序列的后面,持续这个过程,直到数组整体有序。假设有n个数据只需要比较n-1趟,每一次冒泡出无序序列中的最大值,对于第一趟的比较比较的次数依然是n-1次,之后每趟比较的次数依次减少1。直到n-1趟比较完,数据有序了,下面的代码进行了一些优化,当待排序序列本身有序的时候,完成了一次排序就结束了。

 public static void bubbleSort(int[] array) {
        for (int i = 0; i < array.length-1; i++) {
            boolean flg = false;//做一个标记防止数据本身有序
            for (int j = 0; j < array.length-1-i; j++) {//每一趟的比较次数都在减少所以要减i
                if(array[j] > array[j+1]) {
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                    flg = true;
                }
            }
            if(flg == false) {
                break;
            }
        }
    }


6.2性能分析

时间复杂度:O(N^2)数据无序的情况下,O(N)数据有序的情况下,空间复杂度O(1),稳定性:稳定的排序。

7快速排序(重要)

7.1原理-总览

在这里插入图片描述

  1. 从待排序区间选择一个数,作为基准值(pivot);
  2. Partition:为划分函数,遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
  3. 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度 == 0,代表没有数据。
7.1.1挖坑法:
 public static int partition(int[] array,int start,int end) {
        int tmp = array[start];
        while (start < end) {
            //1、先判断后面
            while (start < end && array[end] >= tmp) {//找到一个小于基准点的值
                end--;
            }
            //1.1 后面的给start array[start] = array[end]
            array[start] = array[end];

            //2、再判断前边
            while (start < end && array[start] <= tmp) {
                start++;
            }
            //2.1 把这个大的给end array[end] = array[start]
            array[end] = array[start];
        }
        //start=end
        array[start] = tmp;
        return start;
    }
    //类似二叉树搜索树的前序遍历,基准点就是二叉树的根结点
    public static void quickSort(int[] array,int left,int right) {
        if(left >= right) {//递归结束的条件
            return;
        }
        int pivot = partition(array,left,right);
        quickSort(array,left,pivot-1);
        quickSort(array,pivot+1,right);
    }


    public static void main(String[] args) {
        int []arr={6,0,1,2,7};
        System.out.println(Arrays.toString(arr));
        quickSort(arr,0,arr.length-1);
        System.out.println(Arrays.toString(arr));

    }

假设我以数组5,1,2,4,3,6为例排升序,大致过程如下,将就看下,第一次以5为基准一次划分如下:
在这里插入图片描述
接着再以同样的方式,先划分左再划分右一直递归下去,有一个数字的序列不用划分了,默认是有序的。下面以3为基准的划分,以2基准划分,以1基准划分划分完之后,最开始的基准的左边是不是有序了,再开始右边排序,之后整个序列就有序了。
在这里插入图片描述

7.1.2:Hoare 法:

基本思路和挖坑法一致,只是不再进行进行赋值,而是进行两个数的交换,实现了一个交换函数:

public static void quickSort1(int[] array,int left,int right) {
    if(left >= right) {//递归结束的条件
        return;
    }
    int pivot = partition1(array,left,right);
    quickSort(array,left,pivot-1);
    quickSort(array,pivot+1,right);
}
    public static int partition1(int[] array, int left, int right) {
        int i = left;
        int j = right;
        int pivot = array[left];
        while (i < j) {
            while (i < j && array[j] >= pivot) {
                j--;
            }
            while (i < j && array[i] <= pivot) {
                i++;
            }
            swap(array, i, j);
        }
        swap(array, i, left);
        return i;
    }
    // //交换的方法
public  static void swap(int[]array,int i,int j){
        int tem=array[i];
        array[i]=array[j];
        array[j]=tem;
}


7.2性能分析

时间复杂度:可以把快速排序看成二叉树的前序遍历,每个节点做为基准都遍历了都排好序了,整个数组就有序了,最好情况下O(n * log(n))每次递归都将待排序序列均匀分割,树的深度和每一层的遍历区间相乘。最坏情况下O(n^2),此时数据有序,就是一个单分支的树了,空间复杂度最坏情况下:O(n)。稳定性:不稳定。

7.3快速排序的优化

对于分治算法,当每次划分时,算法若都能分成两个等长的子序列时,那么分治算法效率会达到最大。也就是说,基准的选择是很重要的。选择基准的方式决定了两个分割后两个子序列的长度,进而对整个算法的效率产生决定性影响。最理想的方法是,选择的基准恰好能把待排序序列分成两个等长的子序列。

7.3.1固定位置法:

基本的快速排序选取第一个或最后一个元素作为基准。但不是一种好方法,**因为如果当数据有序的时候,这样的算法效率很低,每次排序完只能排除一个数字,时间复杂度很大,**就像上面写的快速排序就是固定位置法的快速排序,因此下面介绍效率更高的两种基准选取的方法。

7.3.2随机选取基准法(了解)

思想:利用随机数,取待排序列中任意一个元素作为基准

public static void quickSort(int[] array,int left,int right) {
        if(left >= right) {//递归结束的条件
            return;
        }

       // 1、随机选择基准-》先将left下标的值换一下
        //rand 交换array[left] array[rand]
        Random random = new Random();
        int rand = random.nextInt(right-left)+left+1;
      swap(array,left,rand);
        int pivot = partition(array,left,right);
        quickSort(array,left,pivot-1);
        quickSort(array,pivot+1,right);
    }

随机选取基准法,较固定位置法效率有所改变,但存在极大的偶然性,不排除你每次选的基准是待排序序列里面的最大值或者最小值,也不能将待排序序列进行均匀的分割。

7.3.3三数取中(median-of-three)(优化有序的数据)

其实最好的做法就是找到待排序数组的中间值以它进行划分,当这样是很难得到的,一般的做法是使用左端、右端和中心位置上的三个元素的中值作为基准值。显然使用三数中值分割法消除了预排序输入的不好情形。

  public static void medianOfThree(int[] array,int left,int right) {
        int mid = (left+right)/2;
        if(array[mid] > array[left]) {
            int tmp = array[mid];
            array[mid] = array[left];
            array[left] = tmp;
        }//array[mid] <= array[start]
        if(array[left] > array[right]) {
            int tmp = array[left];
            array[left] = array[right];
            array[right] = tmp;
        }//array[start] <= array[right]
        if(array[mid] > array[right]) {
            int tmp = array[mid];
            array[mid] = array[right];
            array[right] = tmp;
        }
        //array[mid] <= array[start] <= array[right]
    }
    //类似二叉树搜索树的前序遍历,基准点就是二叉树的根结点
    public static void quickSort(int[] array,int left,int right) {
        if(left >= right) {//递归结束的条件
            return;
        }
        //2、三数取中法
        medianOfThree(array,left,right);
        int pivot = partition(array,left,right);
        quickSort(array,left,pivot-1);
        quickSort(array,pivot+1,right);
    }

7.3.4使用插入排序

我们知道插入排序的特点是,数据越有序排序速度越快,当待排序序列的长度分割到一定大小后,待排序序列长度在5~20之间任一截止范围都可以使用直接插入排序,而不使用快速排序。

  public static void insertSort2(int[] array,int left,int right) {
        //参数判断
        if(array == null) return;
        for (int i = left+1; i <= right; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= left ; j--) {
                if(array[j] > tmp) {
                    array[j+1] = array[j];
                }else {
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }
//3、递归执行到一个区间之后 进行直接插入排序,加入到导快速排序之中
        if((right - left + 1) <= 20) {
            insertSort2(array,left,right);
            return;//
        }

7.3.5快速排序的非递归

我们通常见到的快速排序为递归版本,并没有关注非递归的方式。但非递归版本也是很好实现的,因为递归的本质是。在非递归实现的过程中,借助栈来保存中间变量就可以实现非递归了。下面的非递归也瞅瞅(* ̄︶ ̄)。

public static void quickSort2(int[] array) {
    Stack<Integer> stack = new Stack<>();
    int start = 0;
    int end = array.length-1;
    int pivot = partition(array,start,end);
    if(pivot > start+1) {
        stack.push(start);
        stack.push(pivot-1);
    }
    if(pivot < end-1) {
        stack.push(pivot+1);
        stack.push(end);
    }
    while (!stack.empty()) {
        end = stack.pop();
        start = stack.pop();
        pivot = partition(array,start,end);
        if(pivot > start+1) {
            stack.push(start);
            stack.push(pivot-1);
        }
        if(pivot < end-1) {
            stack.push(pivot+1);
            stack.push(end);
        }
    }
}

8.归并排序(重要)

8.1原理

在这里插入图片描述
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。==核心就是先分解再合并为有序序列,先把待排序序列分成左右两部分,使这两部分分别有序,再合并这样整体就有序了,对于左边和右边的序列将其进行相同的操作又是一次归并排序,是具有相同结构的子问题。==分解过程我们可以类比二叉树的前序遍历,每一个序列就是一颗二叉树,先根再左后右。先分解成一个一个数字,一个数字是有序的,然后合并为两个数字是有序的,之后两个两个合并四个数字的序列就是有序的了,依次类推再合并,整个序列有序了。

  public static void merge(int[] array,int left,int right,int mid) {
//下面就是合并两个有序数组的操作
        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[s1++];
            }else {
                tmp[k++] = array[s2++];
            }
        }
        while (s1 <= e1) {
            tmp[k++] = array[s1++];
        }
        while (s2 <= e2) {
            tmp[k++] = array[s2++];
        }
        for (int i = 0; i < k; i++) {
            //排好序的数字 放到原数组合适的位置
            array[i+left] = tmp[i];

        }
    }

    public static void  mergeSort1(int[] array,int left,int right) {
        if(left >= right) {//递归结束的条件,也就是把待排序序列分成一个一个数字就终止递归
            return;
        }
        int mid = (left+right)/2;
        mergeSort1(array,left,mid);//递归左边的序列
        mergeSort1(array,mid+1,right);//递归右边的序列
        merge(array,left,right,mid);//合并有序序列
    }

在这里插入图片描述

8.2性能分析

时间复杂度:O(n * log(n)),二叉树的层数乘上每一层的数据,空间复杂度:O(n),稳定性:稳定。

8.3非递归版本

归并排序的非递归是从小到大算起的和递归的思路相反,核心思路也是合并两个有序数组,两个段两个段进行比较合并为一个有序段,再把前面的有序段合并,首先进行比较的时候一个段里面是一个数,之后分段的个数以二倍递增,之后划分的段数大于等于数组的长度此时,待排序序列也就有序了。
非递归时间复杂度::,O(n * log(n)),空间复杂度:O(n)

//非递归版本
    public static void merge2(int[] array,int gap) {

        int[] tmp = new int[array.length];
        int k = 0;
        int s1 = 0;
        int e1 = s1+gap-1;//0+1-1
        int s2 = e1+1;
        int e2 = s2+gap-1 >= array.length ? array.length-1 : s2+gap-1;//e2超出的情况
        //一定要有2个段 s2判断 不能拿e2 用s2至少第二个段有1个数据
        while (s2 < array.length) {//

            while (s1 <= e1 && s2 <= e2) {
                if (array[s1] <= array[s2]) {
                    tmp[k++] = array[s1++];
                }else {
                    tmp[k++] = array[s2++];
                }
            }

            while ( s1 <= e1 ) {
                tmp[k++] = array[s1++];
            }
            while (s2 <= e2) {
                tmp[k++] = array[s2++];
            }

            s1 = e2+1;
            e1 = s1+gap-1;
            s2 = e1+1;
            e2 = s2+gap-1 >= array.length ? array.length-1 : s2+gap-1;

        }

        //只剩下第一个段了
        while (s1 <= array.length-1) {//s1,e1也可能超过数组的范围
            tmp[k++] = array[s1++];
        }
//一个段都不剩就不管了
        for (int i = 0; i < k; i++) {


![img](https://img-blog.csdnimg.cn/img_convert/f9ebb0cee72df1d574a9578b3807365d.png)
![img](https://img-blog.csdnimg.cn/img_convert/682d7dcf56f8714d577d1b55f1e3c7d2.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

       }

        //只剩下第一个段了
        while (s1 <= array.length-1) {//s1,e1也可能超过数组的范围
            tmp[k++] = array[s1++];
        }
//一个段都不剩就不管了
        for (int i = 0; i < k; i++) {


[外链图片转存中...(img-Wju2cyVp-1715761305608)]
[外链图片转存中...(img-69UnDTJJ-1715761305608)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值