排序(二)堆排序|快速排序|归并排序 代码原理,非递归写法超详细解析

【数据结构】排序算法(快速排序、归并排序、排序算法总结) 这一章节我们继续讲解排序的剩下部分,也是两个比较复杂的排序—快速排序和归并排序,希望大家认真学习,理解透彻~ 阅读详情

前言❤️❤️

hello hello💕,这里是洋不写bug~😄,欢迎大家点赞👍👍,关注😍😍,收藏🌹🌹
这篇博客会解析3个进阶的排序算法:堆排序、快速排序、归并排序,这3种排序的效率是要优于上篇博客中解析的4个基础排序算法的,都是很有实用价值的,很多标准库中的排序算法,都是基于这几个算法来实现的,Java中的Arrays.sort()方法就是快速排序的改进版本,Collections.sort()方法就是归并排序中的改进版本
这个专栏的数据结构是代码都是用Java来写的,JavaSE专栏现在已经全部更新完成,铁汁们复习基础知识时非常推荐使用,可以试一下💪💪💪
在这里插入图片描述
🎇个人主页:洋不写bug的博客
🎇所属专栏:数据结构专栏
🎇复习Java基础知识:Java学习之旅,从入门到进阶
🎇铁汁们对于数据结构基础的各种核心知识(不太常用的也有😆),都可以在上面的数据结构专栏学习,专栏正在持续更新中🐵🐵,有问题可以写在评论区或者私信我哦~

1,堆排序

注:学习堆排序需要的前提是掌握堆相关的知识,要掌握向下调整、建堆操作的原理,并且会写代码,这些知识在数据结构专栏的堆博客中都有详细的解析,想复习下的铁汁可以看下(链接如下)
堆博客链接




要对数组进行升序排序,利用堆就可以这样做:

  1. 对数组的元素进行建堆操作
  2. 循环的删除堆顶元素,删除的时候,不是直接删除,而是和最后一个元素交换顺序,这样当前堆的最大值,就来到了数组的最后一个位置,heapSize- -,堆的元素个数减去1,数组的长度仍然不变
  3. 换到堆顶的那个元素从[0]位置开始向下调整,调整后仍然满足堆的性质,接着重复第2步和第3步的过程,直到heapSize的值减小为1

画图来模拟下这个过程,下图中是一个大堆,交换堆顶元素9和堆的最后一个元素2,heapSize–,再对堆顶元素进行向下调整,如下图,用红色框圈住的就是已经排好序的元素,不用再管了

在这里插入图片描述



数组最后一个元素就是2,交换堆顶元素8和元素2的位置,交换后再进行向下调整

在这里插入图片描述



以此类推,最后heapSize等于1的时候,交换堆顶元素和堆的最后一个元素,交换上来后,这时候堆中已经没有元素了,堆顶元素也就不需要向下调整,就排好序了

在这里插入图片描述

堆排序方法需要先写个shiftDown和createHeap方法,下面只会简单的解析下代码,感觉这部分比较抽象的铁汁,推荐先看上篇堆博客复习一下,里面有这两个方法的超详细解析🐵👍



代码逻辑如下:

  1. shiftDown方法传入3个参数,数组arr,堆的长度size(注意是堆的长度,不是堆的最大下标),要进行向下调整的元素的下标index
  2. 当前要进行向下调整的元素就是父结点parent,用child = parent + 1,计算出左子结点的下标,这里我们要建的是大堆,就找到parent左右子结点中的最大值,跟arr[parent]比较,如果arr[parent]更小,就交换父结点和子结点的位置
  3. 交换parent和child的位置后,新的parent就变成了原来的child,计算出新的child,继续重复这一步骤,直到arr[parent] >= arr[child];或者parent已经没有左右子结点了(也就是parent变成了叶子结点),也就是child >= size了
    public static void shiftDown(int[] arr,int size,int index){
        int parent = index;
        int child = 2 * parent + 1;
        while(child < size){
            if(child + 1 < size&& arr[child] < arr[child + 1]){
                child = child + 1;
            }
            if(arr[parent] >= arr[child]){
                break;
            }else{
                int tmp = arr[parent];
                arr[parent] = arr[child];
                arr[child] = tmp;
                parent = child;
                child = 2 * parent + 1;
            }
        }
    }



在createHeap方法中,先找到二叉树中的最后一个非叶子结点,在数组中从后往前,对这些结点进行向下调整,就能得到一个堆

    public static void createHeap(int[] arr){
        int lastLeaf = arr.length - 1;
        int lastParent = (lastLeaf - 1) / 2;
        for(int i = lastParent;i >= 0;i--){
            shiftDown(arr,arr.length - 1,i);
        }
    }



  1. 在heapSort方法中,先调用createHeap方法去构建出一个堆,初始时[0,bound]就是未排序区间,bound的值为arr.length - 1,写个for循环,每次排序后bound的值减1,直到bound的值减为0,就排好序了
  2. shiftDown的第二参数传入的是堆的长度,而bound表示的是下标,下标是比长度要小1的,这里删除堆顶元素后,直接传入bound,刚好就相当于堆的长度减去1
  3. 最后一轮,只需要交换位置,已经不需要向下调整了,传入bound值为0,进入shiftDown方法中,就不会进入while循环
    public static void heapSort(int[] arr){
        createHeap(arr);
        for(int bound = arr.length - 1;bound >= 0;bound--){
            int temp = arr[0];
            arr[0] = arr[bound];
            arr[bound] = temp;
            shiftDown(arr,bound,0);
        }
    }

如果数组需要升序排序,就使用大堆;需要降序排序,就使用小堆,因为每次都是把堆顶的极值放到最后,作为已排序的区间



计算下堆排序的时间复杂度,堆顶元素和堆的最后一个元素交换,新的堆顶元素再向下调整,每次调整时间复杂度为O(logN),总共交换了N次元素,时间复杂度就是O(NlogN),建堆操作的时间复杂度是O(N),合起来就是NlogN + N,因为计算时间复杂度只看最高项,所以堆排序的时间复杂度就是O(NlogN)

堆排序操作都是在数组上进行的,空间复杂度就是O(1)

堆排序属于不稳定排序,因为建堆和向下调整操作,会使值相同的元素,不知道被安排到哪里,最终出堆的顺序是无法预测的



O(NlogN)这个时间复杂度是非常优秀的,像冒泡排序、插入排序、直接选择排序的时间复杂度都是O(N ^ 2),希尔排序进行了优化,也只是优化到了O(N ^ 1.5),随着N的增大,logN的变化是非常非常小的,堆排序算是一个非常高效的算法

2,快速排序

快速排序属于实际开发中,最主要的排序算法,也是面试中最常考的排序算法,快速排序采用的思想是分治,假如要对数组进行升序排序,步骤如下:

  1. 给定一个待排序的数组,从数组中选择一个“基准值”,拿着这个数组的每个元素,和基准值进行比较,把数组整理成三个部分:
    左侧:比基准值小的元素
    中间:基准值
    右侧:比基准值大的元素

  2. 递归的进行这个过程,在左侧区间中再选择一个基准值,把左侧区间也整理成左侧 + 中间 + 右侧三个部分(右侧区间也是如此)

  3. 当递归到一定程度,也就是区间足够小的时候,有序性就可以保证了,例如区间中剩下三个元素了,选择基准值进行整理后,发现一个在左侧一个在右侧;或者区间中剩下两个元素,选择基准值进行整理,另一个元素在区间的左侧或者右侧

  4. 当所有小的区间都有序了,那整个数组元素就也有序了



那具体在代码细节上如何处理,有三种方法,这三种方法的时间、空间复杂度是完全相同的,区别只在分区过程

  1. Hoare法,Hoare是发明快速排序的大佬的名字,这个是最经典、使用最多的写法,快速排序的朴素写法就是Hoare法
  2. 挖坑法
  3. 前后指针法,不推荐使用,理解成本比较高,甚至面试中写出这个版本,面试官都可能看不懂是啥意思

首先解析下Hoare法,如下图,要把数组进行升序排序,选择数组最右侧的元素作为基准值(基准值就是6),再搞个left引用和right引用

  1. left引用从前往后走,找到第一个比基准值大的元素
  2. right引用从后往前走,找到第一个比基准值小的元素

在这里插入图片描述


如果两个引用都找到了符合要求的元素,那就交换两个元素的位置,如下图

在这里插入图片描述


接着left和right继续匹配符合要求的元素,如果都能匹配到符合要求的元素,那就继续交换;也可能两个引用会重合,如下图

在这里插入图片描述



如果两个引用位置重合,就说明找完了,就把重合位置的元素和后面基准值位置的元素进行交换,这时候以6为基准值,6的左区间的元素都小于6,6的右区间的元素都大于6

在这里插入图片描述


再对基准值6的左右区间递归进行这个过程,先看左区间,用最右侧元素2作为基准值,right是匹配不到小于2的元素的,就会在元素3下方和left重合

在这里插入图片描述


交换重合位置和基准值位置的元素,这时候就满足2左区间的元素小于2,右区间的元素大于2,再递归的对2的左右区间的元素进行这个过程,2的左区间为空,就直接跳过
在2的右区间中,以3作为基准值,left和right重合在元素5的位置

在这里插入图片描述


交换3和5的位置,这个区间就符合升序要求了

在这里插入图片描述


对6的右区间也递归进行该过程,整个数组就是有序的了

在这里插入图片描述



对于一些特殊情况,使用这种方法也是可以的,就比如基准值恰好是数组中的最大值,这时候left移动时,就会一直移动到基准值的位置(区间边界位置),也没有找到比基准值大的元素,接着right从基准值的位置开始准备向前移动,发现left和right重合了,riight就不移动了,交换基准值和重合位置元素的位置,相当于还是没有交换

在这里插入图片描述


选用最左侧元素和最右侧元素作为基准值的区别如下:

  • 这里我们选择用最右侧元素作为基准值,就是因为在找的时候,先让left从前往后找大于基准值的元素,再让right从后往前找小于基准值的元素,一旦left和right重合,那重合位置的元素值是大于基准值的(在特殊情况下会等于),交换后重合元素的位置交换到了基准值的右侧,是符合升序规则的

  • 那如果选用最左侧元素作为基准值,就需要让right先从后往前移动,匹配到比基准值小的元素,再让left从前往后移动,匹配比基准值大的元素,这样left和right重合位置的元素是小于基准值的(特殊情况也会等于),交换后重合元素的位置交换到了基准值的左侧,是符合升序规则的

还要根据降序和升序排序,判断下是让left先扫描,还是让right先扫描:

  • 如果是降序排序,那就要left从前往后找小于基准值的元素,right从后往前找大于基准值的元素,都匹配到就交换位置
    如果选择最右侧元素作为基准值,那就要确保left和right重合的位置是小于基准值元素的,就让left先走,这个规则搞懂了,不管是升序还是降序,选择最前面还是最后面的元素作为基准值,都能够分析清楚代码怎么写

代码逻辑如下:

  1. quickSort方法搞两个重载方法,一个里面传入的是数组arr,用于在main方法中调用使用,另一个里面传入arr,left,right,因为要递归对子区间进行快速排序,这里比较推荐使用左闭右闭的方式来写代码,也就是要排序的区间的范围是[left,right],铁汁们这里如果写成左闭右开的话,就会稍微麻烦一些

  2. 在quickSort三个参数的方法中,先判定一下,如果left小于等于right,就说明当前区间为空或者只有一个元素,已经有序了,就直接return

  3. 再写个partition方法,移动left和right,交换元素位置的步骤就在这个方法中,方法的返回值是int,返回最终交换位置后,基准值的下标,还要写个swap方法,负责交换数组中元素的位置,在partition方法中调用swap方法,交换数组元素

public static void quickSort(int[] arr){
        quickSort(arr,0,arr.length - 1);
    }

    public static void quickSort(int[] arr,int left,int right){
        if(left >= right){
            return;
        }
        int index = partition(arr,left,right);
        quickSort(arr,left,index - 1);
        quickSort(arr,index + 1,right);
    }

    public static int partition(int[] arr,int left,int right){
        int value = arr[right];
        int l = left;
        int r = right;
        while(l < r){
            while(l < r && arr[l] <= value){
                l++;
            }
            while(l < r && arr[r] >= value){
                r--;
            }
            swap(arr,l,r);
        }
        swap(arr,l,right);
        return l;
    }

    public static void swap(int[] arr,int l,int r){
        int temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;
    }



分析下快速排序的时间复杂度,快速排序的平均时间复杂度是O(NlogN),但最坏情况下时间复杂度为O(N ^ 2)

当要排序的数组刚好是反序的时候,就是最坏情况,如下图,要对数组中的元素进行升序排序

  1. 选择1作为基准值,left和right移动,left定位到9的位置,right移动到9处,跟left重合,9和1交换位置,这时候1的左区间为空
  2. 接着开始对1的右区间进行排序,以9作为基准值,left会一直移动到9处,和right重合,9的位置就不需要动,9的右区间为空
  3. 对9的左区间进行排序,选择2作为基准值,left定位到8的位置,right一直往前移动,直到跟left重合,交换2和8的位置,这时候left的左区间为空

在这里插入图片描述

写几步就能发现规律:这种情况下每次递归时,区间的长度只减小了1,因此整体的时间复杂度就是O(N ^ 2),这种情况下,每次排序后,基准值左区间或者右区间就会为空



接着分析下空间复杂度,可能有的铁汁会觉得代码中没有额外创建数组什么的,因此空间复杂度就是O(1),但快速排序是通过递归来实现的,递归中每次方法的调用,都会产生出一系列的栈帧,这都是占用空间的
那就要分析递归的深度是多少,平均情况下,每次递归的区间会缩小一半,空间复杂度就是O(logN),最坏情况下,也就是刚好数组是反序时,每次递归,区间的长度只会减去1,空间复杂度就是O(N)




快速排序可以从三个方面来进行优化:

  1. 极端情况出现的原因就是取基准值取到了当前区间中的最值,导致操作后区间并没有缩小很多,可能有的铁汁会想到算中位数或者平均值作为基准值,但是计算本身也是有开销的
    就可以使用三数取中法,代码简单,效率高,对于待排序区间,取出最左侧元素,中间位置元素,最右侧元素,用这三个元素的中间值作为基准值,这个中间值元素再放到区间最右侧
  2. 当快速排序递归到一定程度时,每个子问题已经比较小了,继续递归虽然可以解决问题,但可能仍然要递归很多次,如果待排序区间比较小的话,就可以通过插入排序来解决问题,速度特别快(例如要走1000KM,肯定飞机快,但是走1KM,就是汽车快,因为这么短的距离,飞机在天上微调1KM,效率也不高)
  3. 如果针对特别大的数组进行快速排序,递归深度就可能很高,如果发现递归深度到了一定值以后,就可以使用堆排序,针对分出来的每个区间,直接进行堆排序,一步到位,C++标注库中的sort方法就是这样搞的,根据具体情况切换排序方法
    在平均情况下,快速排序的效率是更高的,因为堆排序需要更多次比较和移动元素,但是堆排序的时间复杂度比较稳定

快速排序是通过递归来实现的,面试时为了考察代码能力,可能会考如何非递归的实现快速排序

非递归就是用栈来模拟递归过程,递归的过程,就是在倒腾待排序区间的下标,创建一个Range类表示区间的范围,在栈中存储Range对象,元素出栈,子区间入栈,就可以模拟递归过程

    public static void quickSortByLoop(int[] arr){
        Stack<Range> stack = new Stack<>();
        stack.push(new Range(0,arr.length - 1));
        while(!stack.isEmpty()){
            Range range = stack.pop();
            if(range.left >= range.right){
                continue;
            }
            int index = partition(arr,range.left,range.right);
            stack.push(new Range(range.left,index - 1));
            stack.push(new Range(index + 1,range.right));
        }
    }
public class Range {
    public int left;
    public int right;

    public Range(int left,int right) {
        this.left = left;
        this.right = right;

    }
}

3,归并排序

在前面的链表博客中,解析过这样一个题目,就是给出两个有序链表,将两者合并成一个有序链表,归并排序也是这样,只是把两个有序的数组,合并成了一个有序的数组

在这里插入图片描述



归并排序使用了分治思想,把待排序的数组分成两份,称为A和B,此时A和B大概率是无序的,就继续拆分,把A拆分为C和D,把B拆分为E和F,以此类推,当拆出的两个数组长度为1时,这两个数组就是有序数组了,就可以直接合并,得到了长度为2的有序数组
简单来说,就是在分治过程中,把长度为N的数组,拆分为N个长度为1的数组

  1. 把这些长度为1的数组两两合并,就得到了N / 2个长度为2的数组
  2. 把这些长度为2的数组两两合并,就得到了N / 4个长度为4的数组
  3. 把这些长度为4的数组两两合并,就得到了N / 8个长度为8的数组
  4. 当子数组的长度为1时,就开始合并



代码逻辑如下:

  1. mergeSort方法搞两个版本,一个参数是arr,用于main方法中直接调用,另一个参数是arr,left,right,表示要进行归并排序的区间,在有三个参数的mergeSort方法中,把当前区间分成两份,再对左右区间继续递归的进行排序,左右区间排序完成后,调用merge方法,来合并两个有序区间

  2. merge就是将两个有序数组合并为一个有序数组,传入四个参数,arr,left,mid,right(当然,这个mid也可以不传,根据传入的left和right计算一下即可),再创建一个数组result,用于将两个数组进行合并

  3. merge方法中搞两个引用,cur1和cur2,分别指向两个有序数组第一个元素的位置,初始时,cur1就等于left,cur2就等于mid + 1,接着对比arr[cur1]和arr[cur2]的大小,将较小的存储到result中,接着cur1++或者cur2++;写在while循环中,当两个有序数组引用走到末尾,就结束while循环

  4. 结束while循环后,两个已排序数组,一定会有一个数组的引用没有走到末尾,这个数组后面的元素值比较大,直接把这些元素加到result数组的末尾,result数组这时候就已经完成两个有序数组的合并了,还要再赋值给arr数组

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

    public static void mergeSort(int[] arr,int left,int right){
        if(left >= right){
            return;
        }
        int mid = (left + right) / 2;
        mergeSort(arr,left,mid);
        mergeSort(arr,mid + 1,right);
        merge(arr,left,mid,right);
    }

    public static void merge(int[] arr,int left,int mid,int right){
        int[] result = new int[right - left + 1];
        int resultSize = 0;
        int cur1 = left;
        int cur2 = mid + 1;
        while(cur1 <= mid && cur2 <= right){
            if(arr[cur1] <= arr[cur2]){
                result[resultSize] = arr[cur1];
                resultSize++;
                cur1++;
            }else{
                result[resultSize] = arr[cur2];
                resultSize++;
                cur2++;
            }
        }
        while(cur1 <= mid){
            result[resultSize] = arr[cur1];
            resultSize++;
            cur1++;
        }
        while(cur2 <= right){
            result[resultSize] = arr[cur2];
            resultSize++;
            cur2++;
        }
        for(int i = 0;i < result.length;i++){
            arr[left + i] = result[i];
        }
    }



接着分析下时间复杂度,归并排序就不存在什么最坏情况了,每次都是把区间分成两份,递归的次数是和logN相关的,时间复杂度就是O(NlogN),发挥是比快速排序稳定的

因为在合并区间时,创建一个result数组,所以时间复杂度就是O(N)

归并排序属于是稳定排序,因为不涉及大范围的元素交换,在合并时,如果arr[cur1]和arr[cur2]的值相同,就把arr[cur1]的值先加入到result中

 if(arr[cur1] <= arr[cur2]){
                result[resultSize] = arr[cur1];
                resultSize++;
                cur1++;
            }



非递归版本的归并排序,就不需要使用栈来模拟递归过程了,本质上就是把长度为1的空间进行两两合并,长度为2的空间进行两两合并,以此类推,就可以直接用两层for循环来实现,代码逻辑如下:

  1. 外层循环中的size表示要合并的数组的大小,size的初始值是1,接着每次size都乘以2,也就是先合并长度为1的数组,再去合并长度为2的数组,以此类推
  2. 内存循环就是完成合并操作,i的初始值是0,要合并的区间就是[i,i + size - 1]和[i + size,i + 2 * size - 1],每次i的值加上size * 2
  3. left或者right的值是有可能超过arr.length - 1的,超过的话,就直接值等于arr.length - 1即可,后面传入到merge方法中,是可以正确的排序的
    public static void mergeSortByLoop(int[] arr){
        for(int size = 1;size < arr.length;size *= 2){
            for(int i = 0;i < arr.length;i += size * 2){
                int left = i;
                int mid = i + size - 1;
                int right = i + 2 * size - 1;
                if(left > arr.length - 1){
                    left = arr.length - 1;
                }
                if(right > arr.length - 1){
                    right = arr.length - 1;
                }
                merge(arr,left,mid,right);
            }
        }
    }



归并排序有两大优点:

  1. 归并排序是可以针对链表进行高效排序的,堆排序/快速排序只能针对数组,不能针对链表进行排序,因为堆排序中的向上和向下调整是非常依赖数组下标的,快速排序中right和left引用移动匹配符合要求的值,在单链表中是不支持从右往左扫描的
  2. 归并排序对于海量数据,也是能够处理的,例如有1000GB的数据需要排序,没办法都加载到内存中进行排序,就可以先取出两个1GB文件,合并成一个2GB的文件,再存到硬盘中,同时就可以把之前两个1GB的文件删掉,以此类推(外部排序效率是很低的,最后合并两个500G的时候,每次只能读取一小块来合并,要读取很多次)

结语

这三种排序总结如下:

  1. 堆排序:先建堆,再循环的删除堆顶元素,也就堆顶元素和堆的最后一个元素交换位置,堆的长度减去1
    时间复杂度:O(NlogN)
    空间复杂度:O(1)
    稳定性:不稳定,建堆和向下调整的过程中,值相同的元素的先后顺序无法预测

  2. 快速排序:选定基准值,分为左区间,基准值,右区间三个部分,当递归到区间足够下的时候,数组整体就有序了
    时间复杂度:平均为O(NlogN),最坏为O(N ^ 2)
    空间复杂度:平均为O(logN),最坏为O(N)
    稳定性:不稳定,partition方法远距离交换元素位置时,可能会打乱值相同元素的先后顺序

  3. 归并排序:一直拆分数组,直到子树组的长度为1,接着把子树组进行两两合并,合并为有序数组
    时间复杂度:O(NlogN)
    空间复杂度:O(N)
    稳定性:稳定

  • 这篇博客解析了3种进阶排序,算上排序(一)中解析的4种基础排序,一共是7种:冒泡、插入、直接选择、希尔、堆、快速、归并排序;无论是408还是就业面试,都是高频考点,所以初学的铁汁一定要搞清楚原理,多写几次代码🐵💪

.以上就是今天的所有内容啦~完结撒花~🥳🎉🎉

在这里插入图片描述

ModusToolbox 实战避坑指南|从环境配置到项目构建 本文是一份详尽的ModusToolbox实战避坑指南,手把手教你从环境配置到项目构建。重点解决了Windows系统下的安装、关键环境变量CY_TOOLS_PATHS的设置、PowerShell执行策略调整以及利用URL Modifier工具优化GitHub资源下载等核心难题,旨在帮助开发者,特别是英飞凌(Infineon)芯片用户,顺利搭建开发环境并成功运行第一个Hello World程序。 阅读详情

相关推荐

影刀RPA:轻松搞定Excel表格自动化操作

影刀RPA是一款功能强大的自动化工具,通过模拟人类操作实现各种重复性任务的自动化。零代码操作:通过拖拽式流程设计器,无需编程基础即可快速上手。功能丰富:提供丰富的组件库,涵盖文件操作、数据处理等多种功能。高效稳定:支持多任务并行,确保任务的高效执行和稳定性。支持Python语法:内置数据表格,支持Python语法,对新手友好,同时具备循环功能。通过影刀RPA,您可以轻松实现Excel表格的拆分、列数据合并和归档工作的自动化。这不仅节省了手动操作的时间,还能确保数据处理的高效和准确,提升工作效率和管理水平。

watwrmoon的博客 2560

【堆排序的递归和非递归实现】Java实现

1、堆是一种重要的数据结构,为一棵完全二叉树,堆排序则是借助堆的概念实现的一种排序算法。若使用数组存储数据的话,则对于数组中下标为 i 的元素,其对应的左子树的下标为 2i+1,右子树的下标为 2i+2; 2、堆可以分为大顶堆和小顶堆: (1)大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列; (2)小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列; 3、堆排序的最坏、最好和平均时间复杂度都为O(nlogn),空间复杂度为O(1); 4、对数组进行堆排序的整体

weixin_44651555的博客 1409

数字后端流程--place

Floorplan完成后芯片的大致框架已经出现,万里长征已经迈出特别重要的第一步了,前面的步骤我们已经完成了io port,macro的摆放,现在core外只剩下std cell(标准单元)了,Place这一步就是要把std cell放进core里。看到密密麻麻的std cell也不用紧张,因为放std cell不用像macro手动一个一个摆,工具就可以帮我们完成大部分工作。虽然这一步的工作只有一个,但也不是只要Place完就能进行下一步的,衡量Place质量是否满足标准会通过以下几点判断。

qq_68400574的博客 771

堆排序 java实现_堆排序Java实现(递归方式&非递归方式)

很早就学习了堆排序但当时没有用代码实现,现在再去想实现已经忘光光啦┑( ̄Д  ̄)┍,于是就去网上搜了一番,发现没有一篇我能认真看完的文章,没办法就是没耐心,就是笨呗。。。好了,言归正传= ̄ω ̄=了解概念首先明白什么是堆,什么是完全二叉树,什么是大顶堆,相信百度一下很容易理解o(^▽^)o。堆可以用数组来存储,如下图,数组 a[0,...,9] 表示一个堆在数组中的存储模式。数组中下标为i的节点的子...

weixin_32109409的博客 278

解密CANoe数据回放:从BLF到ASC的格式转换与优化策略

本文深入解析CANoe数据回放中BLF与ASC格式的转换与优化策略,帮助工程师高效处理汽车电子测试数据。详细介绍了两种格式的特性、转换方法及性能优化技巧,包括CANoe内置功能、命令行自动化和第三方工具应用,提升故障诊断和系统验证效率。

weixin_28331555的博客 535

别再只会插卡了!用示波器实测SIM卡上电时序与通信波形(附故障排查)

本文深入解析了SIM卡通信的电气信号与时序规则,通过示波器实测波形揭示SIM卡上电时序与通信协议的关键细节。从VCC、CLK、RST、DATA四路信号的协同工作,到ATR(Answer to Reset)的解析,再到常见故障的波形诊断与排查方法,为硬件工程师提供了实用的调试指南。文章还分享了高级调试技巧与现代化工具链搭建建议,帮助提升物联网设备SIM卡通信的可靠性与效率。

weixin_42567279的博客 325

java堆排序非递归代码,无原理版,比较好理解

public class HeapOperate { /* * 建立堆时只需要保证根结点小于两个子结点或者大于两个子结点,对两个子结点大小没有要求 */ public static void main(String[] args) { //数组0下标元素作为暂存单元 int data[] = {0, 3, 8, 15, 2, 7, 9, 1, 38}; int len =

聚焦AI技术前沿,深度解析大模型原理与多场景应用,探索最新突破与落地实践,助力行业洞悉趋势,驱动智能转型,赋能千行百业。 1363

27、超声成像技术与原理全面解析

本博客全面解析了超声成像技术与原理,涵盖声学材料特性、超声成像系统的基本概念与组成、关键技术如多普勒和聚焦技术、超声换能器的设计与原理、图像处理方法、系统校准与质量控制,以及超声成像的应用领域和发展趋势。旨在为医学诊断、工业检测和海洋声学等领域的研究人员和工程师提供深入的技术参考和指导。

z2a3b4c5d的博客 490

六大排序算法:插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序

1.2 希尔排序 二.选择排序 2.1 单向选择排序 2.2双向选择排序 2.3 堆排序 堆序详情堆排序 三.交换排序 3.1 冒泡排序 3.2 快速排序 3.2.1 Hoare排序 这里定义一个left为左,right为右,将任意左右位置两边定义一个基准值,根据基准值的大小,直到left为大于基准值数,right为小于基准值数停下,若定义左边为基准值则右边先走,同理右边为基准值左边先走。

Freedom的博客 1万+

乐鑫科技 EchoEar 喵伴修改自定义唤醒词的方法 EchoEar喵伴、唤醒词修改、ESP32、AI萌宠、智能音箱、开发者指南

标签:乐鑫科技、EchoEar喵伴、唤醒词修改、ESP32、AI萌宠、智能音箱、开发者指南大家好,我是一名前端开发工程师,最近在玩乐鑫科技推出的时,发现它不仅萌趣十足,还能进行全双工语音交互、多模态识别,甚至能识别你的情绪和与你互动。EchoEar 喵伴是由乐鑫科技和火山引擎扣子大模型团队合作开发的,它使用乐鑫的芯片方案(ESP32 系列)+ 火山引擎扣子大模型,支持 WiFi 连接和本地处理,长记忆能力可以让它记住用户的偏好与习惯。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1879

SAP CPI 之使用国密算法实现SM系列算法加解密

System.out.println("SM2-私钥:" + privateKey);System.out.println("SM2-公钥:" + publicKey);System.out.println("SM2-密文:" + encrypts);System.out.println("SM2-明文:" + data2);新建groovy,通过import之后,调用jar包中的方法。在CP中导入jar包,选择Upload,选中导入的jar包。的密钥和向量值,不安全!的公钥和私钥,不安全!

u012830826的博客 1334

<八大排序>万字详解(Java实现).插入排序、希尔排序、堆排序、快速排序、归并排序、计数排序...

一.插入排序 (一).直接插入排序 (二).堆排序 希尔排序 1.堆的铺垫 2.向下调整算法(建小堆为例) 3.建堆 4.排升序建大堆还是建小堆? 三.交换排序(一).冒泡排序 4.冒泡与直接插入排序相 (二).快速排序 1.挖坑法 2.左右指针法 3.前后指针法 四.归并排序 (一).归并排序 1.思路: 2.代码: 3.对文件中的数据排序: 五.非递归的归并排序与快排 六.计数排序 (一).计数排序 1.思路: 2.代码: 3.时间/空间复杂度: 七.七大排序的总结对比

weixin_63895720的博客 3016

芒果YOLO11改进146:OBB旋转检测头Head篇:即插即用|轻量化非对称多级压缩LOADH旋转检测头,原创改进适配YOLOv8高效检测头,检测头新颖改进,全网首发独一份

OBB旋转检测头Head篇:即插即用|轻量化非对称多级压缩LOADH旋转检测头,原创改进适配YOLOv8高效检测头,检测头新颖改进,全网首发独一份

包括YOLOv5、YOLOv7、YOLOv8等模型改进 641

结合代码理解:快速排序、归并排序、堆排序算法的一些优缺点

这几天突然想研究一下一些常用算法,今天就记录下最早接触到的排序算法吧。快速排序、归并排序和堆排序都是常用的排序算法,它们各自有独特的特点和适用场景,当然排序算法很多,比如冒泡啊,插入啊,选择排序啊等,不过他们的时间复杂度为O(n^2),通常不适用于大型数据集,就不提及了。

weixin_44144455的博客 3126

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

常用排序算法总结(直接插入排序、选择排序、冒泡排序、堆排序、快速排序、希尔排序、归并排序)

本篇文章总结了直接插入排序、选择排序、冒泡排序、堆排序、快速排序、希尔排序、归并排序,包括其实现思路、特点、代码等

m0_74910646的博客 4182

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

超详细八大排序+基数排序(图文并茂+动图演示+C语言代码演示)

图文详解秒懂各大排序

总结日常学习内容 8334

【数据结构与算法】万字剖析八大排序(直接插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序)

八大排序详解

喜欢就坚持吧 1987

快速排序、归并排序、堆排序的实现原理以及适用场景

之前两篇关于排序算法的综述以及平方阶复杂度的3种具体类型的排序算法,这一篇将具体介绍其中平均时间复杂度在平方阶O(nlog2n)的三个排序算法,以及各种算法的代码实现(亲测正确)。 快速排序 快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 O(nlogn)次比较。在最坏状况下则需要O(n^2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 O(n...

寂蝶难双,窃窃思羽. 5970

【详解八大排序算法】直接插入排序||希尔排序(缩小增量排序)||直接选择排序||堆排序||冒泡排序||快速排序||归并排序||计数排序

本篇将讲述常用的排序算法,包括直接插入排序、希尔排序、直接选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序,使用动图解释,并且画图阐明了递归的写法,同时实现了与非递归的写法复杂度和稳定性分析等等,看完绝对不亏!!!

进一步有一步的欢喜 4349

数据结构:八大排序——直接插入、希尔、选择、堆、冒泡、快速、归并、计数排序

文章目录常见排序及分类直接插入排序希尔排序 常见排序及分类 这里暂时先只总结 直接插入排序 希尔排序 选择排序 堆排序 冒泡排序 快速排序 二路归并排序 计数排序 直接插入排序 动图演示: 分析: 直接插入最坏为O(N^2) 最好可以达到O(N) void InserSort(int* a, int n) { for(int i = 0; i < n - 1; ++i) { int end = i; int x = a[end+1]; while(end >= 0 )

m0_56611833的博客 6127

常见排序算法详解:插入,冒泡,希尔,选择,快速排序,归并,计数排序,堆排序(已完结)

一.插入排序 void PrintArray(int* a, int n) { for (int i = 0; i < n; ++i) { printf("%d ", a[i]); } printf("\n"); } void Swap(int* pa, int* pb) { int tmp = *pa; *pa = *pb; *pb = tmp; } void InsertSort(int* a, int n) { for (int i = 0; ...

zhang_si_hang的博客 5200

堆排序、快速排序、归并排序算法详解

手把手带你拿捏终极排序算法。

LibraRhy的博客 1877

【C语言】实现插入排序、选择排序、冒泡排序、希尔排序、归并排序、堆排序、快速排序

【C语言】实现插入排序、选择排序、冒泡排序、希尔排序、归并排序、堆排序、快速排序

m0_62714628的博客 2296

【排序算法】六大比较类排序算法——插入排序、选择排序、冒泡排序、希尔排序、快速排序、归并排序【详解】

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减地排列起来地操作。稳定性:假定在待排序地记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,而在排序后的序列中。r[i] 仍在 r[j] 之前。则称这种排序算法是稳定的。否则称为不稳定的。内部排序:数据元素全部放在内存中的排序。(我们下面讲的排序都是属于内部排序)外部排序。

2401_87820834的博客 2936

超详细十大排序算法(冒泡排序、插入排序、选择排序、希尔排序、堆排序、快排、归并排序、桶排序、计数排序、基数排序)

一万五千字超详细解读十大排序,手把手教会你排序的基本原理,保证看完这篇不用再看第二遍!!!

Jammy_1的博客 2991

五大排序算法:插入、交换、选择、归并排序以及堆排序

四大排序算法的介绍以及代码实现

张小艾博客 1万+

冒泡排序,选择排序,插入排序,希尔排序,堆排序,归并排序,快速排序实现

各种排序的时间复杂度 排序方法 平均情况 最好情况 最坏情况 辅助空间 稳定性 冒泡排序 O(n^2) O(n) O(n^2) O(1) 稳定 简单选择排序 O(n^2) O(n^2) O(n^2) O(1) 稳定 直接插入排序 O(n^2) O(n) O(n^2) O(1) 稳定 希尔排序 O(nlogn)~O(n^2) O(n^1.3) O(n^2) O(1) 不稳...

没有梦想,何必远方 2466
上一篇: 排序(一)基础排序,插入|希尔|冒泡|直接选择排序详解
下一篇: Map&Set(一)Map、Set使用,经典算法题目解析
洋不写bug
博客等级 码龄2年 87粉丝 102原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值