数据结构之八大排序算法

概述

排序就是把集合中的元素按照一定的次序排序在一起。一般来说有升序排列和降序排列2种排序,在算法中有8中基本排序:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、基数排序、堆排序

性能对比

各排序算法时间复杂度:
在这里插入图片描述
复杂度函数时间大小比较 :
在这里插入图片描述

插入排序

直接插入排序

直接插入排序是一种简单插入排序,基本思想是:把n个待排序的元素看成为一个有序表和一个无序表。开始时有序表中只包含1个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程 。
在这里插入图片描述
类似我们摸牌,一开始有一堆牌(待排序的)。由于第一次摸牌时手中没牌,所以不需要排序。第二次摸牌时和手中第一张拍比较,如果它大,就放在它的后面。 每次摸牌都会把牌放在一个前面比自己小(或等于),后面比自己大(或等于)的位置。

	public static void insertionSort(int[] arrays){
        int i,j,temp;
        //从第二个开始插入
        for(i = 1; i < arrays.length; i++){
            //temp为要插入的元素,arrays[i]之前的元素有序
            temp = arrays[i];
            //比较arrays[i]前面的元素,如果temp<arrays[j],则a[j]往后移一位
            for(j = i - 1; j >= 0 && temp < arrays[j]; j--){
                arrays[j + 1] = arrays[j];
            }
            //此时j+1为插入的位置
            arrays[j + 1] = temp;
        }
    }

希尔排序

该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的。
在这里插入图片描述

	public static void shellSort(int[] a){
        int n = a.length;   //n为数组长度
        int d, i, j, temp; //d为增量
        //增量递减到1使完成排序
        for(d = n/2;d >= 1;d = d/2)
        {
            //插入排序的每一轮
            for(i = d; i < n;i++)
            {
                //temp为要插入的元素,a[i]之前的元素有序
                temp = a[i];
                //隔d的增量取a[i]前的元素与temp相比,如果a[j]大于temp,则a[j]后移d个位置
                for(j = i - d;(j >= 0) && (a[j] > temp);j = j-d)
                {
                    a[j + d] = a[j];
                }
                //此时j+d为插入位置
                a[j + d] = temp;
            }
        }
    }

选择排序

直接选择排序

从待排序序列中,找到关键字最小的元素,如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换。从余下的 N - 1 个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。
在这里插入图片描述

	public static void selectionSort(int[] a){
        int n = a.length;
        int i,j,pos,tmp;
        for (i=0; i<n-1; i++) {
            pos = i;
            //找出最小值,如果比a[pos]大,则与a[pos]交换
            for (j=i+1; j<n; j++){
                if (a[pos]>a[j]){
                    pos=j;
                }
            }
            //a[i]与a[pos]交换
            if (pos != i) {
                tmp=a[i];
                a[i]=a[pos];
                a[pos]=tmp;
            }
        }
    }

堆排序

什么是堆?
  堆是一棵顺序存储的完全二叉树。
小根堆:每个结点的关键字都不大于其孩子结点的关键字。
大根堆:每个结点的关键字都不小于其孩子结点的关键字。

关于堆的特性及操作,可参考之前的博客数据结构之堆和优先队列

排序过程

  1. 堆排序是把数组看作堆,第i个结点的孩子结点为第2i+1和2i+2个结点(不超出数组长度前提下)。
  2. 堆排序的第一步是建堆,然后是取堆顶元素(堆顶元素为最大值(大根堆)或最小值(小根堆)),然后调整堆(取堆顶元素时,将堆顶元素与最后一个元素交换后并移出堆,此时需要重新调整新堆)。
  3. 调整堆即堆的向下调整,将堆顶元素调整至合适位置

堆排序图解
在这里插入图片描述

	/**
     * 堆向下调整
     */
    private static void heapShiftDown(int[] a,int i,int n){
        int temp = a[i];
        while((2 * i + 1) < n){ //a[i]存在子节点
            //左子节点
            int child = 2 * i + 1;
            //如果存在右子节点,且右子节点大于左子节点,则child为右子节点(右子节点 = 左子节点 + 1)
            if(child + 1 < n && a[child + 1] > a[child])
                ++child;
            //如果父节点小于子节点,则与子节点交换
            if(temp < a[child]){
                a[i] = a[child];
                a[child] = temp;
            }
            else
                break;
            //迭代执行
            i = child;
        }
    }

    /**
     * 堆排序
     * @param a
     */
    public static void heapSort(int[] a){
        int n = a.length;
        //将数组转化为堆,(n / 2 -1)为堆的最后一个节点的父节点
        for(int i = n / 2 - 1; i >= 0; i--){
            heapShiftDown(a,i,n);
        }
        for(int i = n - 1; i > 0; i--){
            //将堆顶元素a[0]与最后一个元素a[i]交换
            int temp = a[i];
            a[i] = a[0];
            a[0] = temp;
            //调整堆(堆不断缩小,直至只有一个元素)
            heapShiftDown(a,0,i);
        }
    }

交换排序

冒泡排序

冒泡排序的名字很形象,实际实现是相邻两节点进行比较,大的向后移一个,经过第一轮两两比较和移动,最大的元素移动到了最后,第二轮次大的位于倒数第二个,依次进行。这是最基本的冒泡排序,还可以进行一些优化。
优化
某一轮结束位置为j,但是这一轮的最后一次交换发生在lastSwap的位置,则lastSwap之后是排好序的,下一轮的结束点直接到lastSwap即可。这样可以减少比较次数。

	/**
     * 冒泡排序
     * @param a
     */
    public static void bubbleSort(int[] a){
        int n = a.length;
        //比较n-1轮
        for(int i = 0; i < n - 1; i++){
            //每一轮从0到n-i-1
            for(int j = 0; j < n - i - 1; j++){
                //如果前面的值大于后面的则交换
                if(a[j] > a[j + 1]){
                    int temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                }
            }
        }
    }

    /**
     * 冒泡排序优化
     * @param a
     */
    public static void bubbleSort2(int[] a){
        int n = a.length;
        int lastSwap;
        //比较轮数
        for(int i = n - 1; i > 0; i = lastSwap){
            //每一轮lastSwap初始化为0,防止某一轮未发生交换
            lastSwap = 0;
            //每一轮从0到lastSwap
            for(int j = 0; j < i; j++){
                //如果前面的值大于后面的则交换
                if(a[j] > a[j + 1]){
                    int temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                    //lastSwap为最后交换的位置
                    lastSwap = j;
                }
            }
        }
    }

快速排序

快速排序首先找到一个基准,下面程序以第一个元素作为基准(pivot),然后先从右向左搜索,如果发现比pivot小,则和pivot交换,然后从左向右搜索,如果发现比pivot大,则和pivot交换,一直到左边大于右边,此时pivot左边的都比它小,而右边的都比它大,此时pivot的位置就是排好序后应该在的位置,此时pivot将数组划分为左右两部分,可以递归采用该方法进行。
在这里插入图片描述

	 /**
     * 快速排序
     * @param a
     * @param low
     * @param high
     */
    public static void quickSort(int[] a,int low,int high){
        if(low > high)
            return;
        int i = low;
        int j = high;
        //取第一个元素作为基准
        int temp = a[low];
        while(i != j){
            //如果a[j]>=temp,则j向前移
            while(i < j && a[j] >= temp){
                j--;
            }
            if(i < j){
                //此时a[j]<temp,令a[i] = a[j],i向后移
                a[i] = a[j];
                i++;
            }

            //如果a[i]<=temp,则i向后移
            while(i < j && a[i] <= temp){
                i++;
            }
            if(i < j){
                //此时a[i]>temp,令a[j] = a[i],j向前移
                a[j] = a[i];
                j--;
            }

        }
        //此时i=j,令a[i]=temp,则a[i]之前的元素都比temp小,a[i]之后的元素都比temp大
        a[i] = temp;
        //递归执行
        quickSort(a,low,i-1);   //左半边
        quickSort(a,j+1,high);  //右半边
    }

归并排序

归(递归)并(合并)排序采用了分治策略(divide-and-conquer),就是将原问题分解为一些规模较小的相似子问题,然后递归解决这些子问题,最后合并其结果作为原问题的解。
归并排序将待排序数组A[1…n]分成两个各含n/2个元素的子序列,然后对这个两个子序列进行递归排序,最后将这两个已排序的子序列进行合并,即得到最终排好序的序列。
在这里插入图片描述

 //临时数组空间
    private static int[] tmpArray;

    /**
     * 合并排序
     * @param a
     * @param left
     * @param mid
     * @param right
     */
    private static void sort(int[] a, int left, int mid, int right) {
        int i = left; //左数组下一个要进行比较的元素的索引
        int j = mid + 1; //右数组下一个要进行比较的元素的索引

        for (int k = left; k <= right; k++) {
            if (i > mid) {  //左数组元素已全比较完
                tmpArray[k] = a[j++];
            } else if (j > right) { //右数组元素已全比较完
                tmpArray[k] = a[i++];
            } else if (a[j] < a[i]) { //右数组元素小于左数组
                tmpArray[k] = a[j++];
            } else {  //右数组元素大于等于左数组
                tmpArray[k] = a[i++];
            }
        }
        //合并完成后,再复制回原数组
        for (int k = left; k < right + 1; k++) {
            a[k] = tmpArray[k];
        }
    }

    /**
     * 递归
     * @param a
     * @param left
     * @param right
     */
    private static void merge(int[] a, int left, int right) {
        //左索引大于等于右索引直接返回
        if (left >= right) {
            return;
        }
        //一分为二
        int mid = (left + right) / 2;
        //递归一分为二左边的队列
        merge(a, left, mid);
        //递归一分为二右边的队列
        merge(a, mid+1, right);
        //排序
        sort(a, left, mid, right);
    }
	/**
     * 归并排序
     * @param a
     */
    public static void mergeSort(int[] a) {
        int n = a.length;
        tmpArray = new int[n]; //用于暂时存放比较后的元素
        merge(a, 0, n - 1);
    }

基数排序

基数排序(radix sort)又称桶排序(bucket sort),相对于常见的比较排序,基数排序是一种分配式排序,即通过将所有数字分配到应在的位置最后再覆盖到原数组完成排序的过程。它是一种稳定的排序算法,但有一定的局限性:
  1、关键字可分解;
  2、记录的关键字位数较少,如果密集更好;
  3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。
初始化:构造一个10*n的二维数组,一个长度为n的数组用于存储每次位排序时每个桶子里有多少个元素。
循环操作:从低位开始(我们采用LSD的方式),将所有元素对应该位的数字存到相应的桶子里去(对应二维数组的那一列)。然后将所有桶子里的元素按照桶子标号从小到大取出,对于同一个桶子里的元素,先放进去的先取出,后放进去的后取出(保证排序稳定性)。这样原数组就按该位排序完毕了,继续下一位操作,直到最高位排序完成。
如下
待排序的数组:70, 34, 65, 24, 48, 32, 88, 16, 38, 81
按个位排序:

个位0123456789
70813234651648
2488
38

按个位排完之后的顺序:70,81,32,34,24,65,16,48,88,38
按十位排序:

十位0123456789
16243248657081
3488
38

按十位排完之后的顺序:16,24,32,34,38,48,65,70,81,88 。此时排序完成。

   /**
     * 基数排序
     * @param a 待排序数组
     * @param max  数组元素最大位数对应的数,如千位则为1000
     */
    public static void radixSort(int[] a,int max) {
        int n=1;//代表位数对应的数:个位、十位、百位、千位..直到等于max的最大位数
        int k=0;//保存每一位排序后的结果,将排序结果覆盖原数组
        int length=a.length;
        int[][] bucket=new int[10][length];//排序桶用于保存每次排序后的结果,这一位上排序结果相同的数字放在同一个桶里
        int[] order=new int[10];//用于保存每个桶里有多少个数字
        while(n <= max)
        {
            for(int num:a) //将数组array里的每个数字放在相应的桶里
            {
                //根据当前排序位数的值,放入对应的桶中
                int digit=(num/n)%10;
                bucket[digit][order[digit]]=num;
                order[digit]++;
            }
            //将前一个循环生成的桶里的数据覆盖到原数组中用于保存这一位的排序结果
            for(int i=0;i<10;i++)
            {
                //这个桶里有数据,从上到下遍历这个桶并将数据保存到原数组中
                if(order[i]!=0)
                {
                    for(int j=0;j<order[i];j++)
                    {
                        a[k]=bucket[i][j];
                        k++;
                    }
                }
                order[i]=0;//将桶里计数器置0,用于下一次位排序
            }
            n*=10;//扩大位数,如从个位扩大到十位
            k=0;//将k置0,用于下一轮保存位排序结果
        }
    }

参考链接:
https://blog.csdn.net/lyhkmm/article/details/78920769
https://blog.csdn.net/ccblogger/article/details/82384480
https://www.cnblogs.com/dcy521/p/10989064.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
数据结构中的八大排序算法,是指常见的八种用于对数据进行排序算法。这八种算法分别是冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序和基数排序。 冒泡排序是一种简单的排序算法,通过不断比较和交换相邻元素的位置,使得最大(或最小)的元素逐渐往后(或往前)移动。 选择排序是一种简单直观的排序算法,每次选择未排序序列中最小(或最大)的元素,放到已排序序列的末尾。 插入排序是一种简单直观的排序算法,将一个待排序的元素插入到已部分排序的数列中的合适位置。 希尔排序是一种改进的插入排序算法,通过将待排序数列分组,并对每个分组进行插入排序,然后逐渐减小分组规模,最后进行一次插入排序。 归并排序是一种分治思想的排序算法,将待排序数列不断分割成较小的数列,然后再将这些较小的数列按照顺序进行合并。 快速排序是一种分治思想的排序算法,通过选择一个中间的基准元素,将数列分割成两部分,然后分别对这两部分进行排序。 堆排序是一种利用堆这种数据结构排序算法,通过将待排序数列构建成一个大(或小)顶堆,然后逐步将堆顶元素与最后一个元素交换,并调整堆结构。 计数排序是一种非比较型的排序算法,通过统计待排序数列中每个元素出现的次数,然后依次输出即可。 基数排序是一种非比较型的排序算法,通过对待排序数列的每个位进行排序,依次从低位到高位进行。 这里简单介绍了八大排序算法的基本思想和实现方法。在实际应用中,不同的排序算法适用于不同的场景和要求,我们需要根据具体情况选择合适的算法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农先锋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值