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#数据结构,就此结束了,这是我的一点总结。


目录
相关文章
|
8月前
|
开发框架 算法 搜索推荐
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
135 1
|
7天前
|
存储 监控 算法
企业内网监控系统中基于哈希表的 C# 算法解析
在企业内网监控系统中,哈希表作为一种高效的数据结构,能够快速处理大量网络连接和用户操作记录,确保网络安全与效率。通过C#代码示例展示了如何使用哈希表存储和管理用户的登录时间、访问IP及操作行为等信息,实现快速的查找、插入和删除操作。哈希表的应用显著提升了系统的实时性和准确性,尽管存在哈希冲突等问题,但通过合理设计哈希函数和冲突解决策略,可以确保系统稳定运行,为企业提供有力的安全保障。
|
2月前
|
算法 C#
C#常见的四种经典查找算法
C#常见的四种经典查找算法
|
2月前
|
算法 C# 索引
C#线性查找算法
C#线性查找算法!
|
3月前
|
存储 算法 C#
C#哈希查找算法
C#哈希查找算法
|
3月前
|
算法 C# 索引
C#二分查找算法
C#二分查找算法
|
3月前
|
机器学习/深度学习 算法 数据挖掘
使用C# 实现期望最大化算法
使用C# 实现期望最大化算法
58 0
|
4月前
|
存储 算法 C#
C#二叉搜索树算法
C#二叉搜索树算法
|
5月前
|
存储 C#
揭秘C#.Net编程秘宝:结构体类型Struct,让你的数据结构秒变高效战斗机,编程界的新星就是你!
【8月更文挑战第4天】在C#编程中,结构体(`struct`)是一种整合多种数据类型的复合数据类型。与类不同,结构体是值类型,意味着数据被直接复制而非引用。这使其适合表示小型、固定的数据结构如点坐标。结构体默认私有成员且不可变,除非明确指定。通过`struct`关键字定义,可以包含字段、构造函数及方法。例如,定义一个表示二维点的结构体,并实现计算距离原点的方法。使用时如同普通类型,可通过实例化并调用其成员。设计时推荐保持结构体不可变以避免副作用,并注意装箱拆箱可能导致的性能影响。掌握结构体有助于构建高效的应用程序。
144 7
|
7月前
|
存储 编解码 算法
C#.NET逃逸时间算法生成分形图像的毕业设计完成!晒晒功能
该文介绍了一个使用C#.NET Visual Studio 2008开发的程序,包含错误修复的Julia、Mandelbrot和优化过的Newton三种算法,生成色彩丰富的分形图像。作者改进了原始算法的效率,将内层循环的画点操作移至外部,提升性能。程序提供五种图形模式,支持放大缩小及颜色更新,并允许用户自定义画布大小以调整精度。还具备保存为高质JPG的功能。附有四张示例图片展示生成的分形效果。