C#数据结构与算法揭秘19

简介:

这节,我们介绍基数排序和归并排序。

一、基数排序

基数排序(Radix Sort)的设计思想与前面介绍的各种排序方法完全不同。前面介绍的排序方法主要是通过关键码的比较和记录的移动这两种操作来实现排序的,而基数排序不需要进行关键码的比较和记录的移动。基数排序是一种借助于多关键码排序的思想,是将单关键码按基数分成多关键码进行排序的方法,是一种分配排序。

下面用一个具体的例子来说明多关键码排序的思想。
一副扑克牌有 52 张牌,可按花色和面值进行分类,其大小关系如下:
花色:梅花<方块<红心<黑心
面值:2<3<4<5<6<7<8<9<10<J<Q<K<A
在对这 52 张牌进行升序排序时,有两种排序方法:
方法一:可以先按花色进行排序,将牌分为 4 组,分别是梅花组、方块组、红心组和黑心组,然后,再对这 4 个组的牌分别进行排序。最后,把 4 个组连接起来即可。
方法二:可以先按面值进行排序,将牌分为 13 组,分别是 2 号组、3 号组、4 号组、…、A 号组,再将这 13 组的牌按花色分成 4 组,最后,把这四组的牌连接起来即可。
设序列中有n个记录,每个记录包含d个关键码{k1,k2,…,kd},序列有序指的对序列中的任意两个记录ri和rj(1≤i≤j≤n) ,(ki1, ki2,…, kid)<( kj1, kj2,…, kjd) 其中,k1称为最主位关键码,kd称为最次位关键码。

其中,k1称为最主位关键码,kd称为最次位关键码。

多关键码排序方法按照从最主位关键码到最次位关键码或从最次位关键码到最主位关键码的顺序进行排序,分为两种排序方法:
(1)最高位优先法(MSD法) 。先按k1排序,将序列分成若干子序列,每个子序列中的记录具有相同的k1值;再按k2排序,将每个子序列分成更小的子序列;然后,对后面的关键码继续同样的排序分成更小的子序列,直到按kd排序分组分成最小的子序列后,最后将各个子序列连接起来,便可得到一个有序的序列。前面介绍的扑克牌先按花色再按面值进行排序的方法就是MSD法。
(2)最次位优先法(LSD法) 。先按kd排序,将序列分成若干子序列,每个子序列中的记录具有相同的kd值; 再按kd-1排序, 将每个子序列分成更小的子序列;然后,对后面的关键码继续同样的排序分成更小的子序列,直到按k1排序分组分成最小的子序列后,最后将各个子序列连接起来,便可得到一个有序的序列。前面介绍的扑克牌先按面值再花色按进行排序的方法就是LSD法。

基数的源代码如下:


//基数排序的方法
public int[] RadixSort(int[] ArrayToSort, int digit,int len)
        {
         
           // 从低位到高位的方法
            for (int k = 1; k <= digit; k++)
            {
                // 临时的数组存储里面的十进制的排序的结果
                int[] tmpArray = new int[ArrayToSort.Length];

                
                //临时的数组存储计数的结果
                int[] tmpCountingSortArray = new int[len];

                //基数的数组1            
                    for (int i = 0; i < ArrayToSort.Length; i++)
                {
                    //截取实际值   570-570/10*10
                    int tmpSplitDigit = ArrayToSort[i] / (int)Math.Pow(10, k - 1) - (ArrayToSort[i] / (int)Math.Pow(10, k)) * 10;
                    tmpCountingSortArray[tmpSplitDigit] += 1;
                }

                for (int m = 1; m < 10; m++)
                {
                    tmpCountingSortArray[m] += tmpCountingSortArray[m - 1];
                }

                //   输出的结果
                for (int n = ArrayToSort.Length - 1; n >= 0; n--)
                {
                    int tmpSplitDigit = ArrayToSort[n] / (int)Math.Pow(10, k - 1) - (ArrayToSort[n] / (int)Math.Pow(10, k)) * 10;
                    tmpArray[tmpCountingSortArray[tmpSplitDigit] - 1] = ArrayToSort[n];
                    tmpCountingSortArray[tmpSplitDigit] -= 1;
                }

                //  拷贝排序的数组
                for (int p = 0; p < ArrayToSort.Length; p++)
                {
                    ArrayToSort[p] = tmpArray[p];
                }
            }

            return ArrayToSort;
        }
//双层的循环    时间的复杂度是O(n2)

      如图所示:

基数排序是稳定的排序,时间的复杂度是O(n2).

二、归并排序

归并排序(merge Sort)主要是二路归并排序。二路归并排序的基本思想是:将两个有序表合并为一个有序表。
假设顺序表 sqList 中的 n 个记录为 n 个长度为1的有序表,从第1个有序表开始,把相邻的两个有序表两两进行合并成一个有序表,得到 n/2 个长度为2的有序表。如此重复,最后得到一个长度为 n 的有序表。
一趟二路归并排序算法的实现如下所示, 算法中记录的比较代表记录关键码的比较,顺序表中只存放了记录的关键码:

对于n个记录的顺序表,将这n个记录看作叶子结点,若将两两归并生成的子表看作它们的父结点, 则归并过程对应于由叶子结点向根结点生成一棵二叉树的过程。所以,归并趟数约等于二叉树的高度减1,即log2n,每趟归并排序记录关键码比较的次数都约为n/2,记录移动的次数为2n(临时顺序表的记录复制到原顺序表中记录的移动次数为n) 。 因此, 二路归并排序的时间复杂度为O (nlog2n) 。
而二路归并排序使用了n个临时内存单元存放记录,所以,二路归并排序算法的空间复杂度为O(n) 。归并排序,如图所示:

一趟二路归并排序算法的实现如下所示, 算法中记录的比较代表记录关键码的比较,顺序表中只存放了记录的关键码,相应的源代码如下:


public void Merge(SeqList<int> sqList, int len) 
   { 
        int m = 0;                 //临时顺序表的起始位置 
int l1 = 0;                 //第1个有序表的起始位置 
        int h1;                    //第1个有序表的结束位置 
int l2;                    //第2个有序表的起始位置 
int h2;                     //第2个有序表的结束位置 
        int i = 0; 
int j = 0;  
 
        //临时表,用于临时将两个有序表合并为一个有序表 
        SeqList<int> tmp = new SeqList<int>(sqList.GetLength()); 
 
       //归并处理 
        while (l1 + len < sqList.GetLength()) 
       {    
           l2 = l1 + len;           //第2个有序表的起始位置 
           h1 = l2 - 1;             //第1个有序表的结束位置 
 
          //第2个有序表的结束位置 
           h2 = (l2 + len - 1 < sqList.GetLength())  
? l2 + len - 1 : sqList.Last; 
 
           j = l2; 
           i = l1; 
 
           //两个有序表中的记录没有排序完 
           while ((i<=h1) && (j<=h2)) 
           { 
                //第1个有序表记录的关键码小于第2个有序表记录的关键码 
                if (sqList[i] <= sqList[j])     
                { 
                    tmp[m++] = sqList[i++]; 
                } 
                //第2个有序表记录的关键码小于第1个有序表记录的关键码
else 
                { 
                    tmp[m++] = sqList[j++]; 
                } 
            } 
 
            //第1个有序表中还有记录没有排序完 
            while (i <= h1) 
            { 
                tmp[m++] = sqList[i++]; 
            } 
 
            //第2个有序表中还有记录没有排序完 
            while (j <= h2) 
            { 
                tmp[m++] = sqList[j++]; 
            } 
 
            l1 = h2 + 1; 
        } 
 
        i = l1; 
        //原顺序表中还有记录没有排序完 
        while (i < sqList.GetLength()) 
        { 
            tmp[m++] = sqList[i++]; 
        } 
 
        //临时顺序表中的记录复制到原顺序表,使原顺序表中的记录有序 
       for (i = 0; i < sqList.GetLength(); ++i) 
        { 
            sqList[i] = tmp[i]; 
} 
} 
二路归并排序算法的实现如下: 
public void MergeSort(SeqList<int> sqList) 
     { 
          int k = 1;     //归并增量 
 
          while (k < sqList.GetLength()) 
          { 
               Merge(sqList, k); 
               k *= 2; 
           }
  }

对于n个记录的顺序表,将这n个记录看作叶子结点,若将两两归并生成的子表看作它们的父结点, 则归并过程对应于由叶子结点向根结点生成一棵二叉树的过程。所以,归并趟数约等于二叉树的高度减1,即log2n,每趟归并排序记录关键码比较的次数都约为n/2,记录移动的次数为2n(临时顺序表的记录复制到原顺序表中记录的移动次数为n) 。 因此, 二路归并排序的时间复杂度为O (nlog2n)。而二路归并排序使用了n个临时内存单元存放记录,所以,二路归并排序算法的空间复杂度为O(n) 。

C#数据结构,就此结束了,这是我的一点总结。


目录
相关文章
|
2月前
|
开发框架 算法 搜索推荐
C# .NET面试系列九:常见的算法
#### 1. 求质数 ```c# // 判断一个数是否为质数的方法 public static bool IsPrime(int number) { if (number < 2) { return false; } for (int i = 2; i <= Math.Sqrt(number); i++) { if (number % i == 0) { return false; } } return true; } class Progr
83 1
|
2月前
|
搜索推荐 算法 C#
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
【Unity 3D】C#中冒泡排序、选择排序、插入排序等算法的详解(附源码 超详细)
72 1
|
23天前
|
存储 编解码 算法
C#.NET逃逸时间算法生成分形图像的毕业设计完成!晒晒功能
该文介绍了一个使用C#.NET Visual Studio 2008开发的程序,包含错误修复的Julia、Mandelbrot和优化过的Newton三种算法,生成色彩丰富的分形图像。作者改进了原始算法的效率,将内层循环的画点操作移至外部,提升性能。程序提供五种图形模式,支持放大缩小及颜色更新,并允许用户自定义画布大小以调整精度。还具备保存为高质JPG的功能。附有四张示例图片展示生成的分形效果。
|
2月前
|
存储 算法 C#
C#编程与数据结构的结合
【4月更文挑战第21天】本文探讨了C#如何结合数据结构以构建高效软件,强调数据结构在C#中的重要性。C#作为面向对象的编程语言,提供内置数据结构如List、Array和Dictionary,同时也支持自定义数据结构。文章列举了C#实现数组、链表、栈、队列等基础数据结构的示例,并讨论了它们在排序、图算法和数据库访问等场景的应用。掌握C#数据结构有助于编写高性能、可维护的代码。
|
2月前
|
搜索推荐 C#
C#实现选择排序算法
C#实现选择排序算法
23 2
|
2月前
|
搜索推荐 C#
C#实现插入排序算法
C#实现插入排序算法
21 1
|
2月前
|
搜索推荐 C#
C#实现冒泡排序算法
C#实现冒泡排序算法
31 0
|
2月前
|
存储 程序员 编译器
C#基本数据结构
C#基本数据结构
15 0
|
2月前
|
算法 C#
C# .Net Core bytes转换为GB/MB/KB 算法
C# .Net Core bytes转换为GB/MB/KB 算法
66 0
|
2月前
|
存储 算法 数据处理
C# | 上位机开发新手指南(十一)压缩算法
流式压缩 流式压缩是一种能够实时处理数据流的压缩方式,例如音频、视频等实时传输的数据。 通过流式压缩算法,我们可以边读取边压缩数据,并能够随时输出已压缩的数据,以确保数据的实时性和减少存储和传输所需的带宽。 块压缩 块压缩则是将数据划分为固定大小的块,在每个块内进行独立的压缩处理。块压缩通常适用于文件、存储、传输等离线数据处理场景。 字典压缩 字典压缩是一种基于字典的压缩算法,通过建立一个字典来存储一组重复出现的字符串,并将这些字符串替换成字典中相应的索引,从而减少数据的存储和传输。字典压缩算法可以更好地处理数据中的重复模式,因为它们可以通过建立字典来存储和恢复重复出现的字符串。
73 0
C# | 上位机开发新手指南(十一)压缩算法