常见排序算法

1 篇文章 0 订阅
本文详细介绍了比较型排序算法(冒泡、插入、选择、希尔排序),以及非比较型整数排序(计数排序)和分治思想的代表快速排序。通过示例代码展示了这些排序方法的工作原理和实现过程。
摘要由CSDN通过智能技术生成

目录

比较型排序:

冒泡排序:

快速排序:采用“分治法”思想。把大的拆分为小的,小的再拆分为更小的。

插入排序:将一个数据插入到另一个有序容器

选择排序:

希尔排序:

归并排序:

非比较型整数排序

计数排序:空间换时间;适用于排序一定范围内的整数



比较型排序:

冒泡排序:

冒泡排序
    /// 冒泡排序的优点:每进行一趟排序,就会少比较一次,因为每进行一趟排序都会找出一个较大值。
    /// 如例:第一趟比较之后,排在最后的一个数一定是最大的一个数,第二趟排序的时候,只需要比较除了最后一个数以外的其他的数,
    /// 同样也能找出一个最大的数排在参与第二趟比较的数后面,第三趟比较的时候,只需要比较除了最后两个数以外的其他的数,
    /// 以此类推……也就是说,没进行一趟比较,每一趟少比较一次,一定程度上减少了算法的量。


   int[] arr = new int[] { 11, 33, 22, 55, 66, 88, 99, 199, 999, 77, 66, 6, 1 };

   public void Maopaosorted()
    {
        //原理: 比较两个相邻的元素,将值大的元素交换到右边,相等值不交换
        // N个数字要排序完成,总共进行N-1轮排序,每i轮的排序次数为(N-轮数)次  --从0轮开始,因为一                
        //开始全部都需要比较
        //核心:双循环。外部循环为 比较 轮数,内部循环为 比较次数

        for (int i = 0; i < arr.Length - 1 ; i++)
        {
            //arr.Length-1 是因为前面一个与后面一个比较。不减就会数组越界
            for (int j = 0; j < arr.Length-1 - i; j++)
            {
                if (arr[j] > arr[j + 1])
                {
                    int temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }

快速排序:采用“分治法”思想。把大的拆分为小的,小的再拆分为更小的。

参考链接:图解快排——快速排序算法(quick sort) - 知乎 (zhihu.com)

思想: 快速排序在每一轮挑选一个基准元素,并设置左右两个指针,左指针指向元素的开始,右指针指向元素的结尾。一开始左指针提出来为基准元素,该指针指向设为空。指向为空的指针不操作,操作有指向元素的指针,与基准元素比较。比基准元素大的放右边,小的放左边,与基准元素相等继续移动。当左右指针指向同一个位置时,放入此轮基准数,此轮排序完成。当完成一轮排序后,基准数左边全部小于基准数,右边全部大于基准数。

核心:分治、递归

 /// <summary>
    /// 快速排序
    /// 核心:分治、递归
    /// </summary>
    public void Kuaisupaixu(int[] suzu,int left,int right)
    {
        if (left >= right) return;
        int pivot = suzu[left];
        int l =left; int r =right;


        while (l < r)
        {
            while (l < r && suzu[r] >= pivot)
            {
                r--;
            }
            if (l < r)
            {
                suzu[l] = suzu[r];
                l++;
            }
            while(l < r && suzu[l] <= pivot)
            {
                l++;
            }
            if (l < r)
            {
                suzu[r] = suzu[l];
                r--;
            }
        }
        suzu[l] = pivot;
        //第一轮排序完后,分了左右,右边全部比右边小
        //中间的基准数不用在比较
        Kuaisupaixu(suzu, left, l-1);//左边分治
        Kuaisupaixu(suzu, l+1, right);//右边分治
    }

使用:

 Kuaisupaixu(arr, 0, arr.Length - 1);

插入排序:将一个数据插入到另一个有序容器

参考:插入排序——C#实现_c# 插入排序-CSDN博客

C#实现——十大排序算法之插入排序 - 欧气柠檬 - 博客园 (cnblogs.com)

思想:从无序数组的第二个元素开始取出 比较值。使该比较值与之前的元素进行比较:该比较值小于比较元素时,该比较元素插入到前面,比较元素和后面的元素往后移一位。当无序数组遍历完后排序结束;

    /// <summary>
    /// 核心思想;双循环
    /// 外层循环遍历无序元素,内层循环遍历有序元素 -- 因为是用无序元素与有序元素一一比较
    /// </summary>
    /// <param name="suzu"></param>
    public void Insertpaixu(int[] suzu)
    {
        //无序
        for (int i = 1; i < suzu.Length; i++)//默认下表为0的元素是有序的
        {
            int temp = suzu[i];//取出本轮比较值

            int insetIndex = i;//优化后新增的
            
            //有序
            for (int j = i - 1; j >=0; j--)//无序元素为有序元素的后一位
            {
                if (temp < suzu[j])//有序元素值大于本轮的无序比较值;有序元素往后移一位
                {
                    suzu[j + 1] = suzu[j];//有序元素往后移一位
                    //suzu[j] = temp;//空出位置放入比较值--可以优化:【当前为每比较一次都需要插入】--优化为:移动完位置,最后插入
                                  
                    insetIndex = j;//优化后新增的
                }
            }

            suzu[insetIndex] = temp;//优化后新增的
        }
    }

再次优化:思路:使用for循环有序的数组,使用if比较。可得出有一个不满足条件之后其他的都不满足条件了,就可以退出循环了;--可以使用whie循环,既能循环又能判断。

    /// <summary>
    /// 核心思想;双循环
    /// 外层循环遍历无序元素,内层循环遍历有序元素 -- 因为是用无序元素与有序元素一一比较
    /// </summary>
    /// <param name="suzu"></param>
    public void Insertpaixu(int[] suzu)
    {
        //无序
        for (int i = 1; i < suzu.Length; i++)//默认下表为0的元素是有序的
        {
            int temp = suzu[i];//取出本轮比较值

            //遍历 有序部分
            int j = i;//本轮循环开始时,J为有序数组的个数【无序的开始下标】;有序元素从后往前遍历
            while (j > 0 && temp < suzu[j-1])//suzu[j-1]通过下标获取有序元素
            {
                suzu[j] = suzu[j-1];//该有序的元素往后移动一位
                j--;//记录空出来的下标索引
            }
            suzu[j] = temp;//如果j没有发生--,插入的位置还是temp无序元素本身所在的位置
        }
    }

选择排序:

        它的基本思想是将待排序序列分成已排序和未排序两部分,每次从未排序中找到最小值,然后将其插入到已排序序列的末尾,直到全部元素都排序完成。【最小值与未排序元素第一个交换位置】

 /// <summary>
    /// 选择排序
    /// 核心:双循环
    /// 外层循环为:有序部分。内层循环为无序部分
    /// 本轮的最小值 与 轮次 位置的元素交换位置【】
    /// </summary>
    /// <param name="suzu"></param>
    public void Xuanzepaixu(int[] suzu)
    {
        //有序
        for (int i = 0; i < suzu.Length; i++)
        {
            //无序
            int minIndex = i;//从下标为0的开始
            for (int j = i+1; j < suzu.Length; j++)
            {
                if (suzu[j] < suzu[minIndex])
                {
                    minIndex = j;//记录最小元素下标
                } 
            }

            //交换位置
            int temp = suzu[i];
            suzu[i] = suzu[minIndex];
            suzu[minIndex] = temp;

        }
    }

希尔排序:

        思想:希尔排序是将数据分组,将每一组数据进行插入排序。直到间隔小于 1 结束排序【间隔为1是最后一轮排序】。

        步骤:将数据按照一定的间隔分组【通常为总长度的一半,奇偶数均可以】,将每组数据在组内进行插入排序;完成后在进行下一轮 间隔 分组【间隔为:上轮间隔的一半】。

大量数据时:希尔排序优于插入排序

        参考链接:希尔排序详解(Shell Sort)-CSDN博客

   /// <summary>
    /// 希尔排序
    /// 核心:三层循环
    /// 希尔排序是将数据分组,将每一组进行插入排序。
    /// 最外层确定 间隔增量【】,内层是插入排序对分组的元素排序
    /// </summary>
    /// <param name="data"></param>
    public void ShellSort(int[] data)
    {
        for (int gap = data.Length / 2; gap > 0; gap = gap / 2)//获取到间隔和分组数量
        {
            for (var i = gap; i < data.Length; i++)//无序元素
            {
                var j = i;//每次比较得到插入的索引
                var current = data[i];//获取到以间隔量开始的无序元素,此元素为无序的后一位

                while (j - gap >= 0 && current < data[j - gap])//j - gap >= 0确保插入位不小于0;current < data[j - gap]无序元素与前面的有序元素比较
                {
                    data[j] = data[j - gap];
                    j = j - gap;
                }
                data[j] = current;
            }
        }
    }

归并排序:

        核心:分治、递归

        思想:将数据分成子数据直到成为单个数据,两两比较,合并答案【合并的数组是已经排好序的,直接比较两个数组的头部得到最小值,放入新容器】。需要额外空间。

参考链接:排序算法 - 归并排序详解-CSDN博客

【排序算法精华2】归并排序_哔哩哔哩_bilibili

抄录补充自己理解

 /// <summary>
 /// 归并排序--使用的下标
 /// </summary>
 /// <param name="suzu">待排序数组</param>
 /// <param name="left">最左索引</param>
 /// <param name="right">最右索引</param>
 /// <param name="tempArr">临时数组</param>
void guibinpaixu(int[] suzu,int left,int right,int[] tempArr)
 {
     if(left>=right) { return; }
     int mid = (left+right)/2;//中间索引--mid为左边子序列的结束索引。 mid+1为右边子序列的开始索引
     //分治
     guibinpaixu(suzu,left,mid,tempArr);//左子序列拆分,直到成为单个数据
     guibinpaixu(suzu,mid+1,right,tempArr);//右子序列拆分,直到成为单个数据

     //当上边的分治拆分完后,return出来,就是单个数据,在进行合并,依次往回合并
     hebin(suzu, left, right, mid, tempArr);//每组拆分到最后的合并
 }

 /// <summary>
 /// 合并操作
 /// 把左边序列、右边序列合并,放入临时容器
 /// </summary>
 /// <param name="suzu">待排序数据</param>
 /// <param name="left">最左索引</param>
 /// <param name="right">最右索引</param>
 /// <param name="mid">中间索引</param>
 /// <param name="tempArr">临时数组</param>
 void hebin(int[] suzu,int left,int right,int mid, int[] tempArr)
 {
     int l = left;//左边序列的头部索引
     int r = mid + 1;//右边序列的头部索引,【如果不加1,那么就是左边的最后索引】
     int t = 0;//临时数组的当前索引

     // 1. 先把左右(有序)序列的数据按照规则填充到中转数组 temp
     //    直到左右俩边的有序序列有一边处理完毕为止
     while (l <= mid && r <= right)
     {
         //左边有序序列头部元素 <= 右边有序序列头部元素
         if (suzu[l] <= suzu[r])
         {
             //将左边 小的 数据 ,填充到临时数组,并将左边头部和临时数组索引 ++;
             tempArr[t] = suzu[l];
             l++;
             t++;
         }
         else
         {
             //反之,右边头部索引元素 放入到 临时数组,并将右边头部和临时数组索引 ++;
             tempArr[t] = suzu[r];
             r++;
             t++;
         }
     }

     //2.把有剩余数据的子序列 的剩余数据依次全部填充到 temp数组
     //2.1 如果左边有序序列还有剩余元素, 将剩余元素全部填充到 temp
     while (l <= mid)
     {
         tempArr[t] = suzu[l];
         l++;
         t++;
     }

     // 2.2 如果右边有序序列还有剩余元素, 将剩余元素全部填充到 temp
     while (r <= right)
     {
         tempArr[t] = suzu[r];
         r++;
         t++;
     }

     // 3. 把临时数组 temp 元素 拷贝到 原始数组 suzu
     //将此轮【最右索引以左】的无序元素 替换为 排好序的元素
     t = 0;//临时数组元素的索引 每轮都是从0开始
     int tempLeft = left;//left为此轮的最左元素,不一定是从0开始
     while (tempLeft <= right)
     {
         suzu[tempLeft] = tempArr[t];
         t++; 
         tempLeft++;
     }
 }

使用

 int[] ints = new int[] {1,7,6,5,8 };
 int[] temp = new int[ints.Length];
 guibinpaixu(ints, 0, ints.Length - 1, temp);
 foreach (var item in ints)
 {
     Debug.Log(item);
 }

非比较型整数排序

计数排序:空间换时间;适用于排序一定范围内的整数

参考链接:C#计数排序算法 - 追逐时光者 - 博客园 (cnblogs.com)

1.8 计数排序 | 菜鸟教程 (runoob.com)

 /// <summary>
    /// 计数排序
    /// </summary>
    /// <param name="suzu"></param>
    private void jishupaixu(int[] suzu)
    {
        if (suzu.Length < 1) return;
        int min = suzu[0];
        int max = min;

        //1.找出待排序元素的最大值、最小值
        for (int i = 0; i < suzu.Length; i++)
        {
            if (suzu[i]<min)min = suzu[i];
            if (suzu[i]>max)max = suzu[i];
        }

        //申请临时计数数组
        int[] jishu = new int[max-min +1];

        //2.统计元素出现的个数;计数数组【下标+最小值】为待排序元素,值为元素个数
        //【suzu[i] - min】为待排序元素在计数数组中的位置
        for (int i = 0; i < suzu.Length; i++)
        {
            jishu[suzu[i] - min] += 1;
        }

        存储排序结果
        //int[] temp = new int[suzu.Length];

        //3.填充排序容器
        int tempIndex = 0;
        for (int jishuindex = 0; jishuindex < jishu.Length; jishuindex++)//外层循环是遍历计数容器
        {
            while (jishu[jishuindex] > 0)//内层是计数数组个数大于0,依次插入排序容器
            {
                jishu[jishuindex] -= 1;
                suzu[tempIndex] = jishuindex + min;
                tempIndex++;
            }
        }

    }

桶排序:

思路:为将待排序元素确定一定数量的(范围)桶,把待排序元素放入符合范围的桶中,对桶内元素进行排序,最后把桶内元素返回到原数组;

参考链接:1.9 桶排序 | 菜鸟教程 (runoob.com)

C#桶排序算法-CSDN博客

关键代码:

//确定桶的个数
 int tongCount = (max - min)/arrLength + 1;

 //放入哪个桶
int tongIndex = (suzu[i] - min)/arrLength;
 /// <summary>
    /// 桶排序
    /// 
    /// </summary>
    /// <param name="suzu"></param>
    public void tongpaixu(int[] suzu)
    {
        int arrLength = suzu.Length;
        if(arrLength <= 1) return;

        //确定桶的个数
        int min = suzu[0];
        int max = min;
        for (int i = 0; i < suzu.Length; i++)
        {
            if (suzu[i]<min) min = suzu[i];
            if (suzu[i]>max) max = suzu[i];
        }
        int tongCount = (max - min)/arrLength + 1;


        List<List<int>> tongs = new List<List<int>>();
        //实例化桶
        for (int i = 0; i < tongCount; i++)
        {
            tongs.Add(new List<int>());
        }

        //桶中放入范围内的数据
        for (int i = 0; i < arrLength; i++)
        {
            //放入哪个桶
            int tongIndex = (suzu[i] - min)/arrLength;

            //数据放入桶
            tongs[tongIndex].Add(suzu[i]);
        }

        //对桶内元素排序;--少量元素使用 插入排序
        for (int i = 0; i < tongs.Count; i++)
        {
            charupaixu(tongs[i]);
        }

        //桶中元素放回到数组中
        int index = 0;
        for (int i = 0; i < tongCount; i++)
        {
            for (int j = 0; j < tongs[i].Count; j++)
            {
                suzu[index] = tongs[i][j];
                index++;
            }
        }

    }

    void charupaixu(List<int> suzu)
    {
        if (suzu.Count <= 1) return;
        for (int i = 1; i < suzu.Count; i++)
        {
            int temp = suzu[i];
            int j = i;
            while (j > 0 && temp < suzu[j-1])
            {
                suzu[j] = suzu[j-1];
                j--;
            }
            suzu[j]=temp;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值