C#描述数据结构算法 Data Structures and Algorithms Using C#-Michael McMillan 第一章

边翻译边学习吧~加油andy (红字标明的是暂时不知道如何翻译的 持续更新)欢迎指正


第一章 介绍集合,泛型以及Timing类

    这本书讨论的是用C#语言对数据结构和算法进行开发和实现。我们在本书中使用的数据结构可以在.NET Framework里的类库System.Collections中找到。在本章中,我们先通过对我们自己创造的集合类进行实现(使用数组作为我们实现的基础),之后再研究.NET framework里原有的类库,以此建立集合的概念。

    对于C#2.0来说,泛型是一个很重要的新增内容。泛型允许C#开发人员只需要写函数的一个版本,不论是单独地写函数或是在类中写一个函数,而不用重载多次以适应各种数据类型。C#2.0提供了一个特殊的库,System.Collections.Generic,它实现了System.Collections里很多数据结构的泛型。这一章节将会给读者介绍泛型编程。

    最后,这章介绍了一个定制的类,Timing类,我们将会在很多章节中使用它用以衡量一个数据结构或是算法的表现。这个类会用来替代Big O的分析方式,并不是因为Big O分析方式不重要,而是因为在这本书中会以一种更加贴合实际的方法来学习数据结构和算法。

集合的定义

    集合是一个结构化的数据类型用来存储数据并且提供很多表达式(添加数据到集合中、从集合中删除数据、更新集合中的数据、设置或返回集合中不同属性的值)。

    集合可以分为两种类型:线性和非线性。线性集合是很多元素的一个列表,这个列表中一个元素紧跟着前一个元素。在线性表中的元素通常是按位置顺序排列(第一个,第二个,第三个,等等)。在现实生活中,购物清单是线性集合的很好的例子;在计算机世界(其实也是真实的),数组是以线性集合设计的。

    非线性集合并不以位置顺序来存放元素。组织结构图是非线性集合的一个例子,就像一架子的台球一样。计算机世界中,树、堆、图还有集(交集并集的集)是非线性集合。

    集合,不管是线性还是非线性,都有一系列定义的属性用来描述,还有一系列表达式来运行。集合属性的一个例子是Count集合,它用来获得集合有几个元素。集合的表达式叫做方法,包括Add(添加一个新元素到集合中)、Insert(在指定的索引处添加一个新元素)、Remove(从集合中移除一个指定元素)、Clear(清空集合中所有的元素)、Contains(判断一个指定的元素是否在集合中)以及IndexOf(判断集合中一个指定元素的索引)。

集合的描述

    在线性和非线性两大类下面还有很多分类。线性集合可以是直接存取集合或是顺序存取集合,而非线性集合可以是分层的或是分组的。这部分描述了集合的各个类型。

直接存取集合

    直接存取集合最普通的例子就是数组。我们定义一个由相同类型元素组成的数组,可以通过整型索引直接获取数据,如1.1图。

图1.1 数组

    数组可以是静态的,这样的话当数组声明时,对于程序来说里面的元素数量就有了一个固定长度。或者他们也可以是动态的,这样元素数量可以通过ReDim、ReDim Preserve语句增长。

    C#中,数组不仅是一个固有的数据类型,他们也可以是一个类。本章末尾当我们更具体的检验数组用法的时候,将会讨论数组是如何以类的对象来使用的。

    我们可以使用数组来存储一个线性集合。向数组中添加新的元素是很简单的事情,我们只需要把新的元素放在数组后方第一个空位就行。向数组中插入一个元素并不容易(或高效的),因为我们不得不为了给这个新插入的元素腾出一个空位,而把后面的元素依次向后移动。从数组末尾删除一个元素是很高效的,我们仅需要把最后一个元素的值移除就好。若是要删除其他位置的元素则效率很低,因为就像插入一个元素一样,我们将不得不把很多数组元素向前移动一位来保证数组紧密相连。本章后面的内容将会讨论这个问题。.NET Framework提供了一个专门的数组类,ArrayList,可以在编程中使创造线性集合更容易。我们将会在第三章检验这个类。

    另一个直接存取的集合类型是字符串。字符串是一个可以通过字符索引来访问的字符集合,以同样的方式我们可以访问数组中的元素。字符串在C#语言之中也可以作为类的对象来实现。这个类包含了非常多的方法用以在字符串上进行标准表达式编程,例如字符串连接、返回子字符串、插入字符、删除字符等等。我们将在第八章检验字符串类。

    C#字符串是不可改变的,这意味着一单一个字符串被初始化了,他就不能被改变。当你调整一个字符串,一个字符串的复制将会产生而不是改变了原有的字符串。这种行为可能会在一些情况下使得程序表现不佳,所以.Net Framework提供了一个StringBuilder类可以使你对可变的字符串进行操作。我们也将在第八章检验StringBuilder类。

    最后一个直接存取集合类型是结构体,结构体是一个混合数据类型可以存储许多数据类型组成的数据。例如一个职员记录是由职员姓名(字符串)、薪水(整型)、身份证号(字符串或整型)以及其他属性组成。因为如果分别使用变量存储这些数据可能会非常容易使人感到困惑,C#语言为存储这类型数据提供了结构体。

    C#结构体一个强大的特性是可以为存储在结构体中的数据定义一些方法来操作它们。这使得结构体在一些方面看上去像一个类,虽然你不能从一个结构体里继承或派生。下面的代码展示了C#中一个结构体的用法。


 public struct Name
    {
        private string fname, mname, lname;
        public Name(string first, string middle, string last)
        {
            fname = first;
            mname = middle;
            lname = last;
        }
        public string firstName
        {
            get
            {
                return fname;
            }
            set
            {
                fname = firstName;
            }
        }
        public string middleName
        {
            get
            {
                return mname;
            }
            set
            {
                mname = middleName;
            }
        }
        public string lastName
        {
            get
            {
                return lname;
            }
            set
            {
                lname = lastName;
            }
        }

        public override string ToString()
        {
            return (String.Format("{0} {1} {2}", fname, mname, lname));
        }
        public string Initials()
        {
            return (String.Format("{0}{1}{2}", fname.Substring(0, 1), mname.Substring(0, 1), lname.Substring(0, 1)));
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Name myName = new Name("Michael", "Mason", "McMillan");
            string fullName, inits;
            fullName = myName.ToString();
            inits = myName.Initials();
            Console.WriteLine("My name is {0}.", fullName);
            Console.WriteLine("My initials are {0}.", inits);
            Console.ReadKey();
        }
    }


   

    虽然.NET环境中许多元素是作为类来实现的(例如数组和字符串),但许多基本元素还是以结构来实现的,例如数值类型。

比如整型就是以Int32结构来实现的。对于Int32你可以使用的一个方法是Parse方法,用以将一个字符串类型的数字转换成一个整型。下面举例:

    class Program
    {
        static void Main(string[] args)
        {
            int num;
            string snum;
            Console.Write("Enter a number: ");
            snum = Console.ReadLine();
            num = Int32.Parse(snum);
            Console.WriteLine(num);
            Console.ReadKey();
        }
    }

顺序存取集合

    顺序存取集合是一个按次序存储元素的列表。我们把这类集合叫做线性表。线性表在创建时不会受到数量的限制,这意味着他们能够动态地增长和缩小。线性表中的数据不是直接访问的;他们由他们的位置所引用,就像在图1.2中显示的那样。线性表中第一个元素在列表最前方,最后一个元素在列表最后方。


图1.2 线性表

    由于在线性表中没有直接访问元素的方法,如果要访问一个元素,你不得不贯穿整个列表直到你到达你正在寻找的那个元素。线性表的实现通常会允许两种贯穿列表的方式——一种是从前往后一个方向,另一种是从前往后、从后往前同时进行。

    线性表的一个简单例子是购物清单。通过写下一个接一个的商品直到写完,这样便创建好了一个购物清单表。在购物过程中,每找到一个所需的商品就会在购物清单中把它去掉。

    线性表可以是有序的或是无序的。一个有序表有一系列关于自身有序的值,例如下面的


    无序表是由任意顺序的元素组成的。列表的顺序不同,在列表中搜索数据时的表现也非常不同,你将会在第二章我们探索二分查找与简单线性查找的对比中了解这些。

    一些类型的线性表在对他们的数据元素的访问时有限制。例如栈和队列。栈是一个只能访问列表开头(顶部)数据的列表。数据被放在列表的顶部而且只能从顶部移除。由于这个原因,栈被称为Last-in,First-out(后入先出)结构。当我们往栈中添加一个数据,我们把这个操作叫做push(入栈)。当我们从栈中移除一个数据,我们把这个操作叫做pop(出栈)。在图1.3中展示了这两个栈操作。


图1.3 栈操作

    栈是非常常见的数据结构,尤其是在计算机系统编程中更是如此。栈被用于算术表达式evaluation(评估?求值?)以及用来在许多应用中衡量symbol

    队列是一个数据从列表最后进入、从列表前方移除的列表。这类型列表称作First-in,First-out(先入先出)结构。向队列中添加一个数据叫做EnQueue(入队),从队列中移除一个数据叫做Dequeue(出队)。在图1.4中展示了队列操作。

    

图1.4 队列操作

    队列也被用于系统编程中,例如安排操作系统任务、模拟研究。队列在模拟零售行业的等待线方面是极好的结构。队列的一个特殊类型,优先队列,允许队列中的一个数据拥有最高优先级,可以从队列第一个被移除。优先队列可以用来学习医院急诊室的操作,有心脏病的病人比起一个骨折病人更需要被优先治疗。

    我们将要说的线性集合的最后一个类别是generalized indexed集合(广义索引集合?)。第一个是hash表(哈希表),它存储一系列数据并用一个key(键)关联着。哈希表中有一个特殊的函数叫做哈希函数,它拿到一个数据的键并把这个键(也叫做key)转换成一个整型索引,之后可以用这个索引取回这个条数据。这个索引之后可以用来获取这条与键相关联的数据记录。例如,一个职员记录可能是由职员姓名,薪水,在职年限,就职部门组成。图1.5中展示了这个结构。这份数据记录的键(key)是职员姓名。C#中有一个类叫做HashTable哈希表,用来在哈希表中存储数据。我们将在第十章研究这个结构。


图1.5 一条将被哈希化的数据

    另一个广义索引集合是字典。字典是由一系列(key-values)键值对组成,叫做associations(连结?)。这个结构与单词字典相似,一个单词就是key(键),单词的定义就是与key关联的value(值)。key是进入与之关联的value的索引。字典通常被叫做关联数组,因为字典的索引体系,但它里面的索引并不必须是整型。我们将会在第十一章检验.NET Framework中的各种字典类。

分层集合

    非线性集合分为两大类:分层集合和分组集合。分层集合是一组被分为几个等级的数据。一个等级的数据在下面低等级中有successor(后继?)。

    树是一个普通的分层集合。树形集合看起来像是一个倒立着的树,一个数据元素作为根,其他数据作为树叶在根下悬挂着。书的元素叫做nodes(结点),一个结点下的数据叫做这个结点的children(子孙)。图1.6展示了一个建单的树。


图1.6 树形集合

    树在许多不同领域都有应用。大多数现在操作系统的文件管理系统都是按照树来设计的,有一个目录作为根,其他的子目录作为根的子孙。

    二叉树(binary tree)是树形集合的一个特殊类别,它的每一个结点都有不超过两个的子孙。二叉树可以作为一个二叉查找树,使得对于大量数据的查找更加高效。他的实现方式是,通过把结点按一种根到结点的路径方式放置,使得数据沿着尽可能短的路径存储。

    然而三叉树,堆,总是将最小的数据放在根结点。当删除时根结点将会被移除,插入或删除操作总是会使堆重新组织以便最小的数据被放置在根部。堆通常用来排序,叫做堆排序。存放在堆中的数据元素总是可以保持有序状态,通过重复删除根结点和重新组织这个堆来实现。

    各种不同种类的树将会在第十二章讨论。

分组集合

    无序的非线性集合数据叫做组。分组集合的三大类是set,graphs,和networks(,图,网络)。

    集中的无序数据每一个都是独一无二的。集的一个例子是一个班里面的学生列表,当然了是整型。可以在集上操作的表达式包括union(并集)还有intersection(交集)。图1.7展示了集的表达式操作。


图1.7 集的表达式操作























  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值