七大排序算法和计数排序

这篇具有很好参考价值的文章主要介绍了七大排序算法和计数排序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


以下排序以从小到大排序为例

一、直接插入排序

时间复杂度:
最好情况:完全有序的情况 1 2 3 4 5 O(N)
最坏情况:完全逆序的情况 5 4 3 2 1 O(N^2)(相当于等差数列求和)
空间复杂度:O(1)
稳定性:稳定
当所给的数据越有序,直接插入排序越快
有一组基本有序的数据时,用直接插入排序较好

public static void insertSort(int[] array) {
        for(int i = 1; i < array.length; i++) {
            int j = i - 1;
            int tmp = array[i];
            for(; j >= 0; j--) {//跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {//如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + 1] = array[j];
                }else {
                    break;
                }
            }
            array[j + 1] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }

二、希尔排序

希尔排序是对直接插入排序的优化,跳跃式的分组可能会将更小的元素尽可能往前方
增量为多少(gap为多少),则被分为多少组
时间复杂度:N^1.3 ~ N^1.5
空间复杂度:O(1)
稳定性:不稳定

public static void shellSort(int[] array) {
        int gap = array.length;//增量为多少(gap为多少),则被分为多少组
        while(gap > 1) {//里面已经包括了排序gap为1的情况
            gap /= 2;
            shell(array, gap);
        }
    }
    private static void shell(int[] array, int gap) {
        for(int i = gap; i < array.length; i++) {//i+=gap也行,因为gap最后会变为1
            int j = i - gap;
            int tmp = array[i];
            for(; j >= 0; j -= gap) {//跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {//如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + gap] = array[j];
                }else {
                    break;
                }
            }
            array[j + gap] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }

三、直接选择排序

时间复杂度:不管情况是好还是坏,下面两种写法都是O(N^2)(相当于等差数列求和)
空间复杂度:O(1)
稳定性:不稳定

public static void selectSort1(int[] array){
        for(int i = 0; i < array.length; i++) {
            int minIndex = i;
            for(int j = i + 1; j < array.length; j++) {
                if(array[j] < array[minIndex]) {
                    minIndex = j;//在所有的j下标元素中找比array[minIndex]还要小的元素的下标
                }
            }
            swap(array, i, minIndex);
        }
    }
    private static void swap(int[]array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
    //写法二
    public static void selectSort2(int[] array){
        int left = 0;
        int right = array.length - 1;
        while(left < right) {//与写法一相比将写法一最外层的for循环改为了while循环
                int minIndex = left;
                int maxIndex = left;//maxIndex一定是left,因为下面的j下标是从left+1开始从前往后遍历
                for(int i = left + 1; i <= right; i++) {
                    if(array[i] < array[minIndex]) {
                        minIndex = i;//在所有的j下标元素中找比array[minIndex]还要小的元素的下标
                    }
                    if(array[i] > array[maxIndex]) {
                        maxIndex = i;//在所有的j下标元素中找比array[maxIndex]还要大的元素的下标
                    }
                }
                swap(array, left, minIndex);
                //当最大值原来刚好在最小值的位置(left位置)时,则上一步已经将最小值与left交换,此时最大值在原来最小值的位置,
                if(maxIndex == left) {//所以要做这一步操作
                    maxIndex = minIndex;
                }
                swap(array, right, maxIndex);
                left--;
                right++;
        }
    }

四、堆排序

时间复杂度:O(N)(创建大根堆)+ O(NlogN) (堆的每个节点进行向下调整) 即O(NlogN)
空间复杂度:O(1)
稳定性:不稳定
数据量非常大的时候,堆排序一定比希尔排序快,因为希尔排序的时间复杂度为N^1.3 ~ N^1.4,堆排是对数希尔排是指数

public static void heapSort(int[] array){
        creatBigHeap(array);
        int end = array.length - 1;
        while(end > 0) {
            swap(array, 0, end);//因为是大堆,堆顶的元素最大,将堆顶元素与队尾元素交换,使队尾元素变成队内最大元素
            shiftDown(array, 0, end);//在这里end=array.length-1,而在shiftDown中end代表数组的总长度,
            end--;                          //相当于去掉队尾元素再进行向下调整
        }
    }
    private static void creatBigHeap(int[] array) {
        for(int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
            shiftDown(array, parent, array.length);
        }
    }
    private static void shiftDown(int[] array, int parent, int end) {
        int child = 2 * parent + 1;
        while(child < end) {//因为end传的是array.length,所以条件用<而不用<=
            if(child + 1 < end && array[child] < array[child + 1]) {
                child++;
            }
            if(array[child] > array[parent]) {
                swap(array, child, parent);
                parent = child;
                child = 2 * parent + 1;
            }else {
                break;
            }
        }
    }

五、冒泡排序

时间复杂度:O(N^2) 加了优化之后,最好的情况(只比较一趟)则是O(N)
空间复杂度:O(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++) {
                if(array[j] > array[j + 1]) {
                    swap(array, j, j + 1);
                    flg = true;
                }
            }
            if(!flg) {
                return;
            }
        }
    }

六、快速排序

快速排序(相当于以基准为根创建二叉树)
时间复杂度:
最好情况:O(N*logN) 满二叉树/完全二叉树
最坏情况:O(N^2)(相当于等差数列求和) 单分支的树
空间复杂度:
最好情况:O(logN) 满二叉树/完全二叉树的高度
最坏情况:O(N) 单分支的树创建N个节点
稳定性:不稳定
三种求基准的方法:Hoare法,挖坑法,前后指针法

6.1递归实现快速排序

在这里递归实现的快速排序做了两个优化,第一个优化是使用三数取中法求原始基准,使创建出来的树更像满二叉树,降低了树的高度,降低了空间复杂度
七大排序算法和计数排序,排序算法,算法,数据结构,开发语言,java,学习
第二个优化是递归到一定程度时对每个区间使用插入排序,因为区间越来越小且区间的内容越来越有序,这时这部分区间可以用插入排序以减少递归次数

public static void quickSort(int[] array){
        quickSortF(array, 0, array.length - 1);//参数right传的是array.length-1
    }
    private static void quickSortF(int[] array, int left, int right) {
        if(left >= right) return;//这是递归的结束条件,有=,因为left=right时不用再创建节点了,这个节点已经有序
        //递归到后面的较小区间时用插入法
        if(right - left + 1 <= 7) {//随着快速排序的进行,整个数据正在趋于有序,当区间越来越小且区间的内容越来越有序时,
            insertSort1(array, left, right);//这部分区间可以用插入排序,这样做可以减少递归的次数
            return;
        }
        //三数取中法,降低了二叉树的高度,降低了空间复杂度,使空间复杂度变为O(logn)
        int midIndex = midOfTree(array, left, right);
        swap(array, midIndex, left);//交换完之后保证left下标是三个数中中间大的数字
        int pivot = partition2(array, left, right);//找到一次基准相当于排好了当前基准这个元素在数组中的顺序,找到基准后,基准左边都是比基准小的,基准右边都是比基准大的
        //先递归左边,递归完左边再递归右边
        quickSortF(array, left, pivot - 1);
        quickSortF(array, pivot + 1, right);
    }
    //插入法
    private static void insertSort1(int[] array, int left, int right) {
        for(int i = left + 1; i <= right; i++) {
            int j = i - 1;
            int tmp = array[i];
            for(; j >= left; j--) {//跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {//如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + 1] = array[j];
                }else {
                    break;
                }
            }
            array[j + 1] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }
    //在三数中找到中间大小数的下标
    private static int midOfTree(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[left]) {
                return left;
            }else if(array[mid] < array[right]) {
                return right;
            }else {
                return mid;
            }
        }
    }
    //三种找基准的方法,在排序的过程中序列可能会不一样,但最终排好序的结果一样,建议优先使用挖坑法,right参数传的都是array.length-1
    //Hoare法找基准
    private static int partition1(int[] array, int left, int right) {
         int key = array[left];
         int i = left;//将基准的下标保存到i中,因为后面要用left与right相交位置的元素与基准进行交换,这里的交换函数只传下标
        while(left < right) {
            while (left < right && array[right] >= key) {//left<right这个条件很重要,因为right不断--,此时再判断array[right]>=key时array[right]有可能会越界
                right--;//先走right,因为一开始基准为left,先走right,left和right相遇的地方才能是比基准小的,才能把比基准小的与基准交换放在基准前
            }
            while (left < right && array[left] <= key) {//array[left]<=key或上面array[right]>=key的=一定要有,否则可能会进入死循环(如当left和right的值相同时)
                left++;
            }
            swap(array, left, right);
        }
        swap(array, left, i);
        return left;
    }
    //挖坑法找基准
    private static int partition2(int[] array, int left, int right) {
        int key = array[left];
        while(left < right) {
            while (left < right && array[right] >= key) {
                right--;
            }
            array[left] = array[right];
            while (left < right && array[left] <= key) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = key;
        return left;
    }
    //前后指针法找基准
    private static int partition3(int[] array, int left, int right) {
       int prev = left;
       int cur = left + 1;
       while(cur <= right) {
           if(array[cur] < array[left] && array[++prev] != array[cur]) {//cur在前面找比基准小的,prev保持在cur找到比基准小的之后要跟prev交换的那个位置,prev是前置++
               swap(array, prev, cur);//代码走到这说明cur和prev拉开了距离且cur<left,prev>=left
           }
           cur++;//array[cur]>=array[left]时cur直接往前走,与prev拉开距离
       }
       swap(array, prev, left);
       return prev;
    }

6.2非递归实现快速排序

public static void quickSortNor(int[] array) {
        Stack<Integer> stack = new Stack<>();
        int left = 0;
        int right = array.length - 1;
        int piovt = partition2(array, left, right);
        if(piovt - 1 > left) {
            stack.push(left);//往栈上先放left后放right,则出栈时先出right后出left
            stack.push(piovt - 1);
        }
        if(piovt + 1 < right) {
            stack.push(piovt + 1);
            stack.push(right);
        }
        while(!stack.isEmpty()) {//因为这里出栈时先出right后出left,根据出出来的right和left找新的基准,所以相当于先递归二叉树右边,再递归二叉树左边
            right = stack.pop();
            left = stack.pop();
            piovt = partition2(array, left, right);
            if(piovt - 1 > left) {
                stack.push(left);
                stack.push(piovt - 1);
            }
            if(piovt + 1 < right) {
                stack.push(piovt + 1);
                stack.push(right);
            }
        }
    }

七、归并排序

时间复杂度:O(N*logN) 归并排序在合并过程中每层都要遍历N次,一共logN层
空间复杂度:O(N) 归并排序在最后合并的时候要额外申请与原来数组一模一样大小的数组
归并排序的缺点是空间复杂度过大
稳定性:稳定
原始分开的区间从0下标开始和原始分开的区间不从0下标开始的合并过程:
七大排序算法和计数排序,排序算法,算法,数据结构,开发语言,java,学习
七大排序算法和计数排序,排序算法,算法,数据结构,开发语言,java,学习

7.1递归实现归并排序

public static void mergeSort(int[] array) {
        mergeSort(array, 0, array.length - 1);//参数right传的是array.length-1
    }
    private static void mergeSort(int[] array, int left, int right) {
        if(left >= right) return;
        int mid = (left + right) / 2;
        //分裂左边
        mergeSort(array, left, mid);
        //分裂右边
        mergeSort(array, mid + 1, right);
        //合并,在合并的过程中进行了排序
        merge(array, left, right, mid);
    }
    private static void merge(int[] array, int left, int right, int mid) {
        int s1 = left;
        int s2 = mid + 1;
        int[] tmpArr = new int[right - left + 1];//申请一个新的数组,大小为right-left+1
        int k = 0;
        while(s1 <= mid && s2 <= right) {//满足这个循环条件说明s1~mid和s2~right这两个区间都同时有数据
            if(array[s2] < array[s1]) {//若条件改为array[s2]<=array[s1]则排序变为不稳定
                tmpArr[k++] = array[s2++];
            }else {
                tmpArr[k++] = array[s1++];
            }
        }
        while(s1 <= mid) {
            tmpArr[k++] = array[s1++];
        }
        while(s2 <= right) {
            tmpArr[k++] = array[s2++];
        }
        for (int i = 0; i < tmpArr.length; i++) {
            array[i + left] = tmpArr[i];//将tmpArr数组拷回原来数组时,赋给array[i+left],因为原来数组的left有可能不是0
        }
    }

7.2非递归实现归并排序

在合并之前分的组数不断减少,每组元素不断增多,一个一个为一组(gap为1),再到两个两个为一组(gap为2),如此类推
七大排序算法和计数排序,排序算法,算法,数据结构,开发语言,java,学习
合并之前mid和right有可能会越界,此时要做出调整
七大排序算法和计数排序,排序算法,算法,数据结构,开发语言,java,学习

public static void mergeSortNor(int[] array) {
        int gap = 1;
        while(gap < array.length) {
            for (int i = 0; i < array.length; i += 2*gap) {
                int left = i;
                int mid = left + gap - 1;
                int right = mid + gap;
                if(mid >= array.length) {//mid有可能会越界,越界时要做出调整
                    mid = array.length - 1;
                }
                if(right >= array.length) {//right有可能会越界,越界时要做出调整
                    right = array.length - 1;
                }
                merge(array, left, right, mid);
            }
            gap *= 2;
        }
    }

八、计数排序

时间复杂度:O(2N+数据范围) 即O(MAX(N,数据范围))
空间复杂度:O(数据范围)
稳定性:稳定,但以下代码实现的计数排序是不稳定的
计数排序适合排序某个区间内且集中的数据文章来源地址https://www.toymoban.com/news/detail-605056.html

   public static void countSort(int[] array) {
        int minVal = array[0];
        int maxVal = array[0];
        //求数组中的最大值和最小值
        for (int i = 0; i < array.length; i++) {
            if(array[i] < minVal) {
                minVal = array[i];
            }
            if(array[i] > maxVal) {
                maxVal = array[i];
            }
        }
        int[] count = new int[maxVal - minVal + 1];//依据最大值和最小值来确定计数数组的大小
        //遍历原来的数组进行计数
        for (int i = 0; i < array.length; i++) {
            count[array[i] - minVal]++;//计数数组的下标相当于要排序数组的元素内容
        }
        //遍历count,把当前元素写回array
        int index = 0;
        for (int i = 0; i < count.length; i++) {
            while(count[i] != 0) {
                array[index] = i + minVal;
                index++;
                count[i]--;
            }
        }
    }

到了这里,关于七大排序算法和计数排序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构与算法之排序: 计数排序 (Javascript版)

    排序 排序:把某个乱序的数组变成升序或降序的数组 (这里用数组来做举例) 计数排序 核心思想 :通过计数而非比较来进行排序,借助数组下标本身就是有序的原理实现 适用范围:较小的非负整数序列和最小值和最大值之间的数字范围比较合适 基数排序需要新增一个计数数

    2024年02月06日
    浏览(41)
  • Python篇——数据结构与算法(第四部分:希尔排序及其讨论、计数排序、桶排序、基数排序)

    希尔排序(shell sort)是一种分组插入排序算法 首先取一个整数d1=n/2,将元素分为d1个组,每组相邻两元素之间距离为d1,在各 组内 进行直接插入排序 取第二个整数d2=d1/2,重复上述分组排序过程,知道di=1,即所有元素在同一组内进行直接插入排序。 希尔排序每趟并不使某些

    2024年02月07日
    浏览(42)
  • 【Java数据结构与算法】Day2-高级排序(希尔、归并、快速、计数)

    ✅作者简介:热爱Java后端开发的一名学习者,大家可以跟我一起讨论各种问题喔。 🍎个人主页:Hhzzy99 🍊个人信条:坚持就是胜利! 💞当前专栏:【Java数据结构与算法】 🥭本文内容:Java数据结构与算法中的比较高级的排序,希尔排序、归并排序、快速排序、计数排序

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

    初窥直接插入排序我们先来看一张动图: 由动图我们可以分析出直接插入排序是从 第二个数据开始遍历 ,与 前面的数据进行比较 如果小于 则让前面的数据向前移动 自己接着向前面的数据比较 直到比较到 大于等于自己的 数据或者 没有数据能进行比较 时停止 插入当前的位

    2023年04月13日
    浏览(67)
  • 【数据结构】 七大排序详解(贰)——冒泡排序、快速排序、归并排序

    ==冒泡排序(Bubble Sort)==也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会

    2024年02月09日
    浏览(123)
  • 数据结构——七大排序[源码+动图+性能测试]

    本章代码gitee仓库:排序 我们日常打扑克牌,摸牌,让后将牌按顺序插入好,这其实就是插入排序的过程,打小插入排序的思想就植入我们的脑海 第一张牌不用管,直接拿在手里,之后的牌按照大小再一个一个插入即可 直接插入排序特性: 越接近有序,效率越高(不用那么多

    2024年02月09日
    浏览(49)
  • 七大排序算法和计数排序

    以下排序以从小到大排序为例 时间复杂度: 最好情况:完全有序的情况 1 2 3 4 5 O(N) 最坏情况:完全逆序的情况 5 4 3 2 1 O(N^2)(相当于等差数列求和) 空间复杂度:O(1) 稳定性:稳定 当所给的数据越有序,直接插入排序越快 有一组基本有序的数据时,用直接插入排序较好 希

    2024年02月16日
    浏览(47)
  • 【数据结构】 七大排序详解(壹)——直接插入排序、希尔排序、选择排序、堆排序

    排序 :所谓排序,就是使一串记录,按照其中的某个或某些的大小,递增或递减的排列起来的操作。 稳定性 :假定在待排序的记录序列中,存在多个具有相同的的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而

    2024年02月09日
    浏览(67)
  • 数据结构中的七大排序(Java实现)

    目录 一、直接插入排序 二、希尔排序 三、直接选择排序 四、堆排序 五、冒泡排序 六、快速排序 七、归并排序               定义i下标之前的元素全部已经有序 ,遍历一遍要排序的数组,把i下标前的元素全部进行排序,当遍历玩这个数组后,就已经排好序了。        

    2024年02月08日
    浏览(52)
  • 【数据结构常见七大排序(一)】—插入排序篇【直接插入排序】And【希尔排序】

    目录 1.排序的概念及其运用 1.1排序的概念 1.2排序运用 1.3常见的七大排序 2.直接插入排序 2.1基本思想 2.2直接插入排序 2.3动图助解 2.4直接插入排序源码 2.5直接插入排序的特性总结 3.希尔排序( 缩小增量排序 ) 3.1希尔排序概念及思想 3.2希尔排序图解 3.3希尔排序源码 3.4希尔排序

    2024年02月13日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包