• C#数据结构和算法[An Introduction to Collections, Generics, and the Timing Class]


    An Introduction to Collections, Generics,and the Timing Class
    集合,泛型,时间类的介绍
    This book discusses the development and implementation of data structures
    and algorithms using C#. The data structures we use in this book are found
    in the .NET Framework class library System.Collections. In this chapter, we
    develop the concept of a collection by first discussing the implementation of
    our own Collection class (using the array as the basis of our implementation)
    and then by covering the Collection classes in the .NET Framework.
    本书讨论地是用C#开发或实现数据结构及算法,书中提及的数据结构都可以在System.Collections里找到。
    本章,我们首先用自己的方式实现集合(用数组),然后再说.net Framework。

    An important addition to C# 2.0 is generics. Generics allow the C# programmer
    to write one version of a function, either independently or within a
    class, without having to overload the function many times to allow for different
    data types. C# 2.0 provides a special library, System.Collections.Generic,
    that implements generics for several of the System.Collections data structures.
    This chapter will introduce the reader to generic programming.
    Finally, this chapter introduces a custom-built class, theTiming class, which
    we will use in several chapters to measure the performance of a data structure
    and/or algorithm. This class will take the place of Big O analysis, not because
    Big O analysis isn’t important, but because this book takes a more practical
    approach to the study of data structures and algorithms.
    泛型是C#2.0的一个新特性,泛型允许程序员只需写一个函数(不管是独立地还是成员函数),不用重载很多次去适应各种数据类型。
    C#2.0提供了一个库,System.Collections.Generic,它实现了一些集合类,本章介绍如何泛型编程。最后,本章介绍一个自定义类,
    时间相关的类,用来代替O方法来测试算法的效率,不是说O方法不重要,是因为本书更注重实际的结果。

    COLLECTIONS DEFINED
    集合的定义
    A collection is a structured data type that stores data and provides operations
    for adding data to the collection, removing data from the collection, updating
    data in the collection, as well as operations for setting and returning the values
    of different attributes of the collection.
    Collections can be broken down into two types: linear and nonlinear. A
    linear collection is a list of elements where one element follows the previous
    element. Elements in a linear collection are normally ordered by position
    (first, second, third, etc.). In the real world, a grocery list is a good example
    of a linear collection; in the computer world (which is also real), an array is
    designed as a linear collection.
    Nonlinear collections hold elements that do not have positional order
    within the collection. An organizational chart is an example of a nonlinear
    collection, as is a rack of billiard balls. In the computer world, trees, heaps,
    graphs, and sets are nonlinear collections.
    集合是一种存储数据并提供一些诸如增,删,查,改操作的数据结构。集合分线性和非线性的。
    线性集合是一串连续的元素,元素间以位置排序。比如现实生活中的小卖部,比如计算机中的数组。
    非线性集合内的元素位置并不一定连续,比如现实生活中的组织结构图,比如计算机中的树,堆,图,set。
    Collections, be they linear or nonlinear, have a defined set of properties that
    describe them and operations that can be performed on them. An example
    of a collection property is the collections Count, which holds the number of
    items in the collection. Collection operations, called methods, include Add
    (for adding a new element to a collection), Insert (for adding a new element
    to a collection at a specified index), Remove (for removing a specified element
    from a collection), Clear (for removing all the elements from a collection),
    Contains (for determining if a specified element is a member of a collection),
    and IndexOf (for determining the index of a specified element in a
    collection).

    不管线性非线性集合,会定义一些属性和方法,比如count,保存集合内元素的个数,
    比如添加,插入(在特定位置添加),删除,清空,查找是否存在,查找元素位置。

    COLLECTIONS DESCRIBED
    Within the two major categories of collections are several subcategories.
    Linear collections can be either direct access collections or sequential access
    collections, whereas nonlinear collections can be either hierarchical or
    grouped. This section describes each of these collection types.
    集合的描述
    两个主要的集合分类下还有一些小分类。
    线性集合可以是直接存储集合或序列化存储集合,非线性集合可以是层级或分组。本节将逐一讨论。

    Direct Access Collections
    The most common example of a direct access collection is the array.We define
    an array as a collection of elements with the same data type that are directly
    accessed via an integer index, as illustrated in Figure 1.1.
    Item ø Item 1 Item 2 Item 3 . . . Item j Item n−1
    FIGURE 1.1. Array.

    直接存储集合
    最常见的直接存储集合就是数组,用同样的数据类型定义一个数组时可以直接通过整数的索引来存取。
    像 Item ø Item 1, Item 2, Item 3, . . . Item j, Item n−1

    Arrays can be static so that the number of elements specified when the array
    is declared is fixed for the length of the program, or they can be dynamic, where
    the number of elements can be increased via the ReDim or ReDim Preserve
    statements.
    In C#, arrays are not only a built-in data type, they are also a class. Later
    in this chapter, when we examine the use of arrays in more detail, we will
    discuss how arrays are used as class objects.
    数组可以是静态的,这样定义数组时需要指定数组的长度。也可以是动态的,可以通过redim来增加元素。
    C#里。数组不只是一个内建数据类型,还是一个类,后面的章节就讨论做为类时是如何使用的。

    We can use an array to store a linear collection. Adding new elements to an
    array is easy since we simply place the new element in the first free position
    at the rear of the array. Inserting an element into an array is not as easy (or
    efficient), since we will have to move elements of the array down in order
    to make room for the inserted element. Deleting an element from the end of
    an array is also efficient, since we can simply remove the value from the last
    element. Deleting an element in any other position is less efficient because,
    just as with inserting, we will probably have to adjust many array elements
    up one position to keep the elements in the array contiguous.We will discuss
    these issues later in the chapter. The .NET Framework provides a specialized
    array class, ArrayList, for making linear collection programming easier. We
    will examine this class in Chapter 3.
    数组可以存储线性集合,数组内添加元素很简单,就需要把新元素放到数组末尾的第一个空位置上。
    插入元素倒没这么方便,因为需要移动很多元素去给新元素腾出位置来。从末尾删除元素很方便,只需要移除最后一个即可,
    但是从不具体的位置删除就稍麻烦了,原理同插入元素一样,不多解释。
    NET Framework专门提供了一个数组类,ArrayList,能为我们操作线性集合更简单,具体留到第三章说

    Another type of direct access collection is the string. A string is a collection
    of characters that can be accessed based on their index, in the same manner we
    access the elements of an array. Strings are also implemented as class objects
    in C#. The class includes a large set of methods for performing standard
    operations on strings, such as concatenation, returning substrings, inserting
    characters, removing characters, and so forth.We examine the String class in
    Chapter 8.
    另一个直接存储集合的类型是string,一个string对象是被索引的字符串的集合,我们可以像数组一样操作string。
    C#也把string现成类了。类里有大量的方法来操作字符串,比如连接,返回字串,插入字符,删除字符,具体留到第八章说

    C# strings are immutable, meaning once a string is initialized it cannot be
    changed. When you modify a string, a copy of the string is created instead of
    changing the original string. This behavior can lead to performance degradation
    in some cases, so the .NET Framework provides a StringBuilder class that
    enables you to work with mutable strings.We’ll examine the StringBuilder in
    Chapter 8 as well.
    C#里的string是不可变的,意思是一旦这个string对象初使化了值就不能改变了。
    当你想要修改这个string对象时,其实是创建了这个string对象的副本,这样使得效率就降低了。
    所以.net提供了一个StringBuilder类来操作可变的字符串。同样,具体的第八章再细说。
    The final direct access collection type is the struct (also called structures
    and records in other languages). A struct is a composite data type that holds
    data that may consist of many different data types. For example, an employee
    record consists of employee’ name (a string), salary (an integer), identification
    number (a string, or an integer), as well as other attributes. Since storing each
    of these data values in separate variables could become confusing very easily,
    the language provides the struct for storing data of this type.
    A powerful addition to the C# struct is the ability to define methods for
    performing operations stored on the data in a struct. This makes a struct
    somewhat like a class, though you can’t inherit or derive a new type from
    a structure. The following code demonstrates a simple use of a structure
    in C#:

     最后一个直接存储集合是struct(在其他语言里貌似叫结构体和记录)。struct是一个包含了很多不同数据类型的复杂数据类型。
    比如,一条雇员记录包含 雇员姓名(string型),薪水(int型),工号(string或int型),等等。如果把这些变量分开存储的话很容易搞混。
    所以C#提供了struct。C#里的struct的新特性是可以在struct里面定义方法.这样一来struct就有点像class了,但是不能从struct继承或派生新struct.
    下面是一个struct的例子

    using System;
    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)));
      }
    }
      ("My initials are {0}.", inits);
    }
    }
      
    }
    }

     
    Sequential Access Collections
    A sequential access collection is a list that stores its elements in sequential
    order. We call this type of collection a linear list. Linear lists are not limited
    by size when they are created, meaning they are able to expand and contract
    dynamically. Items in a linear list are not accessed directly; they are referenced
    by their position, as shown in Figure 1.2. The first element of a linear list is
    at the front of the list and the last element is at the rear of the list.
    Because there is no direct access to the elements of a linear list, to access an
    element you have to traverse through the list until you arrive at the position
    of the element you are looking for. Linear list implementations usually allow
    two methods for traversing a list—in one direction from front to rear, and
    from both front to rear and rear to front.
    序列化存储集合
    序列化存储集合是一串排好序的元素的集合,称之为线性list。线性list不限制元素个数,意思是可以动态扩大。
    线性list里的元素不能直接存取的。
    1st, 2nd,3rd, Nth

    front             rear
    第一个元素在最前面,最后一个元素在末尾。因为不能直接存取,所以要取某一个元素的话,就得遍历这个list,直到找到为止。
    线性list的实现一直有两种方式来遍历:单向的只能从头到尾,或者双向都行。

    当商店被list里的元素
    A simple example of a linear list is a grocery list. The list is created by
    writing down one item after another until the list is complete. The items are
    removed from the list while shopping as each item is found.
    Linear lists can be either ordered or unordered. An ordered list has values
    in order in respect to each other, as in:
    Beata Bernica David Frank Jennifer Mike Raymond Terrill
    An unordered list consists of elements in any order. The order of a list makes
    a big difference when performing searches on the data on the list, as you’ll see
    in Chapter 2 when we explore the binary search algorithm versus a simple
    linear search.

    FIGURE 1.3. Stack Operations.
    一个简单地例子是杂货店。
    Some types of linear lists restrict access to their data elements. Examples
    of these types of lists are stacks and queues. A stack is a list where access is
    restricted to the beginning (or top) of the list. Items are placed on the list
    at the top and can only be removed from the top. For this reason, stacks are
    known as Last-in, First-out structures. When we add an item to a stack, we
    call the operation a push. When we remove an item from a stack, we call that
    operation a pop. These two stack operations are shown in Figure 1.3.
    The stack is a very common data structure, especially in computer systems
    programming. Stacks are used for arithmetic expression evaluation and for
    balancing symbols, among its many applications.
    一些线性list对存取元素会做一些限制,比如栈和队列,栈是只能在顶部存取的list.
    所以,栈被叫做后进先出的结构。当添加元素时,叫做push,删除时,叫pop。
    Bernica Push    Bernica Pop
    David           David
    Raymond    Raymond
    Mike      Mike
    栈是很常见的数据结构,尤其在系统编程时,栈被用来计算数学表达式和运算符号。
    A queue is a list where items are added at the rear of the list and removed
    from the front of the list. This type of list is known as a First-in, First-out structure.
    Adding an item to a queue is called an EnQueue, and removing an item
    from a queue is called a Dequeue. Queue operations are shown in Figure 1.4.
    Queues are used in both systems programming, for scheduling operating
    system tasks, and for simulation studies. Queues make excellent structures
    for simulating waiting lines in every conceivable retail situation. A special
    type of queue, called a priority queue, allows the item in a queue with the
    highest priority to be removed from the queue first. Priority queues can be
    used to study the operations of a hospital emergency room, where patients
    with heart trouble need to be attended to before a patient with a broken arm,for example.
    队列是一个只能在末尾删除在前面添加的list,被称为先进先出的结构,添加元素叫做 入列,删除元素叫做 出列。
    队列也常用在系统编程里,
    比如任务调度,模拟研究。队列可以很好地模拟一些可以想象的情况。
    有一种队列比较特别,叫优先队列,允许队列中优先级高的元素先出列,例如优先队列被用来研究医院急救室的操作。
    当有心脏病的人需要比外伤病人优先治疗。

    The last category of linear collections we’ll examine are called generalized
    indexed collections. The first of these, called a hash table, stores a set of data
    values associated with a key. In a hash table, a special function, called a hash
    function, takes one data value and transforms the value (called the key) into
    an integer index that is used to retrieve the data. The index is then used to
    access the data record associated with the key. For example, an employee
    record may consist of a person’s name, his or her salary, the number of years
    the employee has been with the company, and the department he or she works
    in. This structure is shown in Figure 1.5. The key to this data record is the
    employee’s name. C# has a class, called HashTable, for storing data in a hash
    table. We explore this structure in Chapter 10.
    最后一个线性集合是泛型索引集合。首先是hash表,有一个函数叫hash函数,把数值(叫做key)转换成整数的索引去关联数据。
    这个整数索引用来存取key关联的数据记录。举个例子,一条雇员记录包含姓名,工资,工龄,部门。这条记录的key就是雇员姓名。
    C#有一个类叫HashTable,在第10章再细说。

    Another generalized indexed collection is the dictionary. A dictionary is
    made up of a series of key–value pairs, called associations. This structure
    is analogous to a word dictionary, where a word is the key and the word’s
    definition is the value associated with the key. The key is an index into the
    value associated with the key. Dictionaries are often called associative arrays
    because of this indexing scheme, though the index does not have to be an
    integer. We will examine several Dictionary classes that are part of the .NET
    Framework in Chapter 11.
    另一个泛型的索引集合是dictionary。dictionary是一些键(key)值(value)对的集合,这个数据结构就像个字典,
    单词是key,解释是value,key就是索引。因为索引的方式,dictionary常常被叫做组合数组。但是索引不用是整数,具体的在11章细说。
    Hierarchical Collections
    Nonlinear collections are broken down into two major groups: hierarchical
    collections and group collections. A hierarchical collection is a group of items
    divided into levels. An item at one level can have successor items located at
    the next lower level.
    One common hierarchical collection is the tree. A tree collection looks like
    an upside-down tree, with one data element as the root and the other data
    values hanging below the root as leaves. The elements of a tree are called
    nodes, and the elements that are below a particular node are called the node’s
    children. A sample tree is shown in Figure 1.6.
    Trees have applications in several different areas. The file systems of most
    modern operating systems are designed as a tree collection, with one directory
    as the root and other subdirectories as children of the root.
    层级集合
    非线性集合被分成两组,层级集合和分组集合,层级集合是按级别分组的。某一层的元素在下一级有继承者。
    一个常见的层级集合是树。树看起来乱七八糟的,在一个根结点上挂了很多了子结点。
    树有不同的领域有不同的应用。在大多数现代操作系统里文件系统被设计成树结构。目录做为根结点,子目录做为子结点。

    A binary tree is a special type of tree collection where each node has no
    more than two children. A binary tree can become a binary search tree, making
    searches for large amounts of data much more efficient. This is accomplished
    by placing nodes in such a way that the path from the root to a node where
    the data is stored is along the shortest path possible.
    Yet another tree type, the heap, is organized so that the smallest data value
    is always placed in the root node. The root node is removed during a deletion,
    and insertions into and deletions from a heap always cause the heap to reorganize
    so that the smallest value is placed in the root. Heaps are often used
    for sorts, called a heap sort. Data elements stored in a heap can be kept sorted
    by repeatedly deleting the root node and reorganizing the heap.
    Several different varieties of trees are discussed in Chapter 12.
    二叉树是一种特别的树,每个结点至多有两个子结点,二叉树可以变成二叉搜索树,搜索大量数据时比较高效。
    这是这样子设计来存储数据的:从根结点到某一个结点的path尽可能短。
    还有些其他的树,比如 堆,最小的数据在根结点,删除操作会移除根结点,插入或删除操作会重新组织堆,让最小值放在根结点。
    堆常用来排序,叫做堆排序,通过不停地删除根结点和重新组织堆来排序。其他的树第12章再说。
    A nonlinear collection of items that are unordered is called a group. The three
    major categories of group collections are sets, graphs, and networks.
    A set is a collection of unordered data values where each value is unique.
    The list of students in a class is an example of a set, as is, of course, the integers.
    Operations that can be performed on sets include union and intersection.
    非线性集合的元素是没有排序的,叫做聚合(group),三种聚合是set,图(graph),网络(network).
    set是无序且无重复元素的聚合,全班同学是一个set的例子。当然,整数也可以的。set可以求并或交。

    An example of set operations is shown in Figure 1.7.
    A graph is a set of nodes and a set of edges that connect the nodes. Graphs
    are used to model situations where each of the nodes in a graph must be visited,
    sometimes in a particular order, and the goal is to find the most efficient way
    to “traverse” the graph. Graphs are used in logistics and job scheduling and
    are well studied by computer scientists and mathematicians. You may have
    heard of the “Traveling Salesman” problem. This is a particular type of graph
    problem that involves determining which cities on a salesman’s route should
    be traveled in order to most efficiently complete the route within the budget
    allowed for travel.
    图是点和这些通过点相连的边的聚合。图被用来模拟图中各个点都要被遍历的情形时,用特定的顺序,
    目的是用最高效的方法去遍历图。图被用在后勤和工作的调度上,在计算机科学和数学上研究得很多。
    你应该听过遗传算法(旅行推销员算法),这就是一个图的算法:以怎样的顺序以最少地预算走完所有的城市。

    A sample graph of this problem is shown in Figure 1.8.
    This problem is part of a family of problems known as NP-complete problems.
    This means that for large problems of this type, an exact solution is not
    known. For example, to find the solution to the problem in Figure 1.8, 10
    factorial tours, which equals 3,628,800 tours. If we expand the problem to
    100 cities, we have to examine 100 factorial tours, which we currently cannot
    do with current methods. An approximate solution must be found instead.
    这个问题是NP-complete问题集中的一部分,意思是解决问题的方法有很多种,并没有确定的解决方案。
    举个例子,10的阶乘=3628800.如果把这个问题扩展到100个城市,我们需要算100的阶乘,这样现在的方法就不行了,
    需要有个类似的方法来替代。

    A network is a special type of graph where each of the edges is assigned a
    weight. The weight is associated with a cost for using that edge to move from
    one node to another. Figure 1.9 depicts a network of cities where the weights
    are the miles between the cities (nodes).
    网络(network)是一种特别的图,这种图的每条边有权重。
    We’ve now finished our tour of the different types of collections we are going
    to discuss in this book. Now we’re ready to actually look at how collections
    are implemented in C#.We start by looking at how to build a Collection class
    using an abstract class from the .NET Framework, the CollectionBase class.
    现在介绍完将就要在书中讨论的结构了,接下来看C#里是怎么实现的,首先看一下怎么建立一个抽象类,这些集合类的基类。
    THE COLLECTIONBASE CLASS
    The .NET Framework library does not include a generic Collection class
    for storing data, but there is an abstract class you can use to build your
    own Collection class—CollectionBase. The CollectionBase class provides the
    programmer with the ability to implement a custom Collection class. The
    class implicitly implements two interfaces necessary for building a Collection
    class, ICollection and IEnumerable, leaving the programmer with having to
    implement just those methods that are typically part of a Collection class.
    集合类的基类
    .net库里没有用来存储数据的泛型集合类,但是有一个抽象类CollectionBase,可以通过它来实现自己的集合类。
    CollectionBase可以让程序员自己定义集合类,实现集合类需要隐式实现两个接口,ICollection和IEnumerable。其他的方法自己按需要实现。

    A Collection Class Implementation Using ArrayLists
    In this section, we’ll demonstrate how to use C# to implement our own Collection
    class. This will serve several purposes. First, if you’re not quite up
    to speed on object-oriented programming (OOP), this implementation will
    show you some simple OOP techniques in C#.We can also use this section to
    discuss some performance issues that are going to come up as we discuss the
    different C# data structures. Finally, we think you’ll enjoy this section, as well
    as the other implementation sections in this book, because it’s really a lot of
    fun to reimplement the existing data structures using just the native elements
    of the language. As Don Knuth (one of the pioneers of computer science)
    says, to paraphrase, you haven’t really learned something well until you’ve
    taught it to a computer. So, by teaching C# how to implement the different
    data structures, we’ll learn much more about those structures than if we just
    choose to use the classes from the library in our day-to-day programming.
    用ArrayLists实现集合类
    本节讨论如何实现自己的集合类,这样做是为了:首先如果你还不了解OOP,这个实现可以向你展示简单的OOP技术。
    其次还会讨论一些其他数据结构的性能问题。最后,你会发现重新实现本地语言既有的数据结构的乐趣。 Don Knuth说过:
    只会套用,说明你还没有真正学会它。所以,我们要用这些类的同时要知道他们是如何实现的。

    Defining a Collection Class
    The easiest way to define a Collection class in C# is to base the class on an
    abstract class already found in the System.Collections library—the Collection-
    Base class. This class provides a set of abstract methods you can implement
    to build your own collection. The CollectionBase class provides an underlying
    data structure, InnerList (an ArrayList), which you can use as a base for
    your class. In this section, we look at how to use CollectionBase to build a
    Collection class.
    定义一个集合类
    定义一个集合类最简单的方法是继承System.Collections里的CollectionBase类。
    这个类提供一些你可以实现的抽象方法。CollectionBase类提供了一个底层的数据结构:InnerList。
    我们来看一下如何用CollectionBase来建一个集合类。
    Implementing the Collection Class
    The methods that will make up the Collection class all involve some type of
    interaction with the underlying data structure of the class—InnerList. The
    methods we will implement in this first section are the Add, Remove, Count,
    and Clear methods. These methods are absolutely essential to the class, though
    other methods definitely make the class more useful.
    Let’s start with the Add method. This method has one parameter – an
    Object variable that holds the item to be added to the collection. Here is the
    code:
    实现集合类
    这些将要创建的方法涉及InnerList类的底层数据结构。这些要实现的方法有添加,删除,计数,清空。
    这些方法是绝对必要的,其他方法锦上添花。先说添加方法,只有一个入参:要添加进去的对象。

    代码如下

    public void Add(Object item)
    {
      InnerList.Add(item);
    }

    ArrayLists store data as objects (the Object data type), which is why we
    have declared item as Object. You will learn much more about ArrayLists
    in Chapter 2.
    The Remove method works similarly:
    ArrayLists用object存储数据,删除方法如下

    public void Remove(Object item) {
    InnerList.Remove(item);
    }

    The next method is Count. Count is most often implemented as a property,
    but we prefer to make it a method. Also, Count is implemented in the
    of Count found in CollectionBase:
    接下来是计数,计数通常以属性方式实现,但是这里做成方法。

    public new int Count() {
    return InnerList.Count;
    }


    The Clear method removes all the items from InnerList. We also have to use
    the new keyword in the definition of the method:
    清空方法在InnerList里删除所有元素。这里还用这个关键字。

    public new void Clear() {
    InnerList.Clear();
    }

    This is enough to get us started. Let’s look at a program that uses the
    Collection class, along with the complete class definition:
    可以开始了,来看看这个类。
    There are several other methods you can implement in order to create a
    more useful Collection class. You will get a chance to implement some of
    these methods in the exercises.

    Generic Programming
    One of the problems with OOP is a feature called “code bloat.” One type of
    code bloat occurs when you have to override a method, or a set of methods,
    to take into account all of the possible data types of the method’s parameters.
    One solution to code bloat is the ability of one value to take on multiple data
    types, while only providing one definition of that value. This technique is
    called generic programming.
    A generic program provides a data type “placeholder” that is filled in by a
    specific data type at compile-time. This placeholder is represented by a pair
    of angle brackets (< >), with an identifier placed between the brackets. Let’s
    look at an example.
    A canonical first example for generic programming is the Swap function.
    Here is the definition of a generic Swap function in C#:
    泛型编程
    OOP的一个问题叫做“代码膨胀”,一种情况发生在你要重载一个方法,或一些方法时,

    using System;
    using System.Collections;
    public class Collection : CollectionBase<T> {
    public void Add(Object item) {
    InnerList.Add(item);
    }
    public void Remove(Object item) {
    InnerList.Remove(item);
    }
    public new void Clear() {
    InnerList.Clear();
    }
    public new int Count() {
    return InnerList.Count;
    }
    }
    class chapter1 {
    static void Main() {
    Collection names
    = new Collection();
    names.Add(
    "David");
    names.Add(
    "Bernica");
    names.Add(
    "Raymond");
    names.Add(
    "Clayton");
    foreach (Object name in names)
    Console.WriteLine(name);
    Console.WriteLine(
    "Number of names: " + names.
    Count());
    names.Remove(
    "Raymond");
    Console.WriteLine(
    "Number of names: " + names.
    Count());
    names.Clear();
    Console.WriteLine(
    "Number of names: " + names.
    Count());
    }
    }

    要考虑该方法的参数的所有可能的数据类型,一个解决方法是用一个值来替代所有可能的数据类型。
    只需要提供一种定义,这种技术叫泛型编程。
    泛型程序提供一个在编译时数据类型“占位符”,这个占位符是一对尖括号(<>),在尖括里有一个标识符。
    来看一个例子。一个典型的例子是第一个泛型编程的交换功能。

    static void Swap<T>(ref T val1, ref T val2) {
    T temp;
    temp
    = val1;
    val1
    = val2;
    val2
    = temp;
    }

    The placeholder for the data type is placed immediately after the function
    name. The identifier placed inside the angle brackets is now used whenever a
    generic data type is needed. Each of the parameters is assigned a generic data
    type, as is the temp variable used to make the swap. Here’s a program that
    tests this code:
    点位符在函数名之后,标识符在尖括号内,每个参数指定一个泛型数据类型,临时变量用来交换。程序如下

    using System;
    class chapter1 {
    static void Main() {
    int num1 = 100;
    int num2 = 200;
    Console.WriteLine(
    "num1: " + num1);
    Console.WriteLine(
    "num2: " + num2);
    Swap
    <int>(ref num1, ref num2);
    Console.WriteLine(
    "num1: " + num1);
    Console.WriteLine(
    "num2: " + num2);
    string str1 = "Sam";
    string str2 = "Tom";
    Console.WriteLine(
    "String 1: " + str1);
    Console.WriteLine(
    "String 2: " + str2);
    Swap
    <string>(ref str1, ref str2);
    Console.WriteLine(
    "String 1: " + str1);
    Console.WriteLine(
    "String 2: " + str2);
    }
    static void Swap<T>(ref T val1, ref T val2) {
    T temp;
    temp
    = val1;
    val1
    = val2;
    val2
    = temp;
    }
    }

    The output from this program is:
    Generics are not limited to function definitions; you can also create generic
    classes. A generic class definition will contain a generic type placeholder after
    the class name. Anytime the class name is referenced in the definition, the type
    placeholder must be provided. The following class definition demonstrates
    how to create a generic class:
    泛型不仅可以用在函数定义,也可以创建泛型类,泛型类的定义在类名后包含一个占位符,
    引用时类名是必须的,类型占位符也必须要。

    public class Node<T> {
    T data;
    Node
    <T> link;
    public Node(T data, Node<T> link) {
    this.data = data;
    this.link = link;
    }
    }

    This class can be used as follows:
    这个类可以这样用

    Node<string> node1 = new Node<string>("Mike", null);
    Node
    <string> node2 = new Node<string>("Raymond", node1);

     We will be using the Node class in several of the data structures we examine
    in this book.
    While this use of generic programming can be quite useful, C# provides a
    library of generic data structures already ready to use. These data structures
    are found in the System.Collection.Generics namespace and when we discuss
    a data structure that is part of this namespace, we will examine its use. Generally,
    though, these classes have the same functionality as the nongeneric data
    structure classes, so we will usually limit the discussion of the generic class
    to how to instantiate an object of that class, since the other methods and their
    use are no different.
    泛型编程很有用,C#提供了一个泛型数据结构的库。可以在System.Collection.Generics里找到。
    虽然这些类和非泛型类有相同的功能,所以我们只讨论如何实例化一个对象,因为其他方法的用法不同。
    Timing Tests
    Because this book takes a practical approach to the analysis of the data structures
    and algorithms examined, we eschew the use of BigOanalysis, preferring
    instead to run simple benchmark tests that will tell us how long in seconds
    (or whatever time unit) it takes for a code segment to run.
    Our benchmarks will be timing tests that measure the amount of time it
    takes an algorithm to run to completion. Benchmarking is as much of an art
    as a science and you have to be careful how you time a code segment in order
    to get an accurate analysis. Let’s examine this in more detail.
    时间测试
    因为本书侧重于研究数据结构和算法的实用性,所以避开了大O分析法,宁愿用简单的运行时间来测试效率。

    An Oversimplified Timing Test
    First, we need some code to time. For simplicity’s sake, we will time a subroutine
    that writes the contents of an array to the console. Here’s the code:
    一个简单地时间测试

    首先,需要写一时间相关的代码,简单期间,我们测试把数组内容的子程序输出控制台的时间。代码如下:

    static void DisplayNums(int[] arr) {
    for(int i = 0; i <= arr.GetUpperBound(0); i++)
    Console.Write(arr[i]
    + " ");
    }


    The array is initialized in another part of the program, which we’ll examine
    later.
    To time this subroutine, we need to create a variable that is assigned the
    system time just as the subroutine is called, and we need a variable to store
    the time when the subroutine returns. Here’s how we wrote this code:
    这个数组的实例化在程序的另一部分里,后面将会测试。
    测试这个子程序的运行时间,需要建一个变量来存放子程序调用时的系统时间。
    然后还需要一个变量来存储子程序返回时的时间,下记是代码。

    DateTime startTime;
    TimeSpan endTime;
    startTime
    = DateTime.Now;
    endTime
    = DateTime.Now.Subtract(startTime);

    Running this code on my laptop (running at 1.4 mHz on Windows XP
    Professional), the subroutine ran in about 5 seconds (4.9917). Although this
    code segment seems reasonable for performing a timing test, it is completely
    inadequate for timing code running in the .NET environment. Why?
    First, the code measures the elapsed time from when the subroutine was
    called until the subroutine returns to the main program. The time used by
    other processes running at the same time as the C# program adds to the time
    being measured by the test.
    Second, the timing code doesn’t take into account garbage collection performed
    in the .NET environment. In a runtime environment such as .NET,
    the system can pause at any time to perform garbage collection. The sample
    timing code does nothing to acknowledge garbage collection and the resulting
    time can be affected quite easily by garbage collection. So what do we do
    about this?
    这串代码在我的电脑(1.4GHZ的XP下)运行了5秒。虽然这串代码的表现看起来还算合理,
    但是在.net环境下,完全不行。为啥?首先,代码测试的时间是从子程序被调用到返回给main程序,
    其他进程运行的时候也被加到此次测试的时间里了。其次,时间测试代码没有考虑到.net的垃圾回收。
    在.net运行环境里,系统随时有可能停下来进行垃圾回收,这个例子没有考虑到垃圾回收的影响,那怎么办呢?

    Timing Tests for the .NET Environment
    In the .NET environment, we need to take into account the thread our program
    is running in and the fact that garbage collection can occur at any time. We
    need to design our timing code to take these facts into consideration.
    Let’s start by looking at how to handle garbage collection. First, let’s discuss
    what garbage collection is used for. In C#, reference types (such as strings,
    arrays, and class instance objects) are allocated memory on something called
    the heap. The heap is an area of memory reserved for data items (the types
    mentioned previously). Value types, such as normal variables, are stored on
    the stack. References to reference data are also stored on the stack, but the
    actual data stored in a reference type is stored on the heap.
    .net环境下的运行时间测试
    .net环境里,我们需要考虑线程和垃圾回收存在的事实,这样就设计这个测试代码时去考虑这些因素。
    看下垃圾回收吧,首先,讨论一下垃圾回收在.net里的作用,C#里,引用类型(如string,数组,类的实例化对象)
    要在堆里申请内存,堆是一块留给数据元素的内存区域。值类型,比如简单变量,存储在栈里,引用数据的引用也存在栈里,
    但是实际的数据存在堆里。

    Variables that are stored on the stack are freed when the subprogram in
    which the variables are declared completes its execution. Variables stored on
    the heap, on the other hand, are held on the heap until the garbage collection
    process is called. Heap data is only removed via garbage collection when there
    is not an active reference to that data.
    Garbage collection can, and will, occur at arbitrary times during the execution
    of a program. However, we want to be as sure as we can that the
    garbage collector is not run while the code we are timing is executing.We can
    head off arbitrary garbage collection by calling the garbage collector explicitly.
    The .NET environment provides a special object for making garbage
    collection calls, GC. To tell the system to perform garbage collection, we
    simply write:
    GC.Collect();
    存储在栈里的变量声明这个变量所在的子程序执行完成时释放。存储在堆里的变量一直存在堆里,直到垃圾回收。
    堆数据只会当没有动态的引用那些数据时才通过垃圾回收来清除,垃圾回收可以发生在程序执行的任何时候,所以,
    我们需要确定垃圾回收是否有在我们测试时运行。我们可以通过显示调用来关闭垃圾回收。.net提供一个专门的对象
    来调用垃圾回收,告诉系统去垃圾回收。GC.Collect();

    That’s not all we have to do, though. Every object stored on the heap has
    a special method called a finalizer. The finalizer method is executed as the
    last step before deleting the object. The problem with finalizer methods is
    that they are not run in a systematic way. In fact, you can’t even be sure an
    object’s finalizer method will run at all, but we know that before we can be
    sure an object is deleted, it’s finalizer method must execute. To ensure this,
    we add a line of code that tells the program to wait until all the finalizer
    methods of the objects on the heap have run before continuing. The line of
    code is:
    GC.WaitForPendingFinalizers();
    这就是我们需要做的。每个存储在堆里的对象都有一个专门的方法叫做finalizer。finalizer方法的执行是删除对象前的最后一步。
    finalizer的问题是他们并不是系统化地运行的。实际上,,你甚至不能确定哪个对象finalizer会被运行。但是我们能确定删除的那个对象之前,
    它的finalizer方法一定要执行。确认了这个,我们加一行代码来告诉程序:所有堆里的对象的finalizer方法运行之前先等待。代码如下:
    GC.WaitForPendingFinalizers();


    We have one hurdle cleared and just one left to go – using the proper
    thread. In the .NET environment, a program is run inside a process, also
    called an application domain. This allows the operating system to separate
    each different program running on it at the same time. Within a process, a
    program or a part of a program is run inside a thread. Execution time for a
    program is allocated by the operating system via threads. When we are timing
    the code for a program, we want to make sure that we’re timing just the
    code inside the process allocated for our program and not other tasks being
    performed by the operating system.
    We can do this by using the Process class in the .NET Framework. The
    Process class has methods for allowing us to pick the current process (the
    process our program is running in), the thread the program is running in, and
    a timer to store the time the thread starts executing. Each of these methods
    can be combined into one call, which assigns its return value to a variable to
    store the starting time (a TimeSpan object). Here’s the line of code (okay, two
    lines of code):
    我们有一。使用合适的线程。.net环境里,程序是运行在一个进程里的,也叫做一个应用程序域。
    这允许操作系统把每个不同的程序分开让他们同时运行。在一个进程里,一个程序或程序的一部分是运行在一个线程里的。
    执行时间是由操作系统来分配的。当我们测试运行时间时,我们需要确定恰好进程不被其他进程干扰。
    也可以用进程类做这个工作。进程类里有让我们拾取当前进程的方法,程序里正在运行的线程。然后记录线程开始执行的时间。
    这些方法可以组合成一个指定它的返回值为开始时间的方法,代码如下:

    TimeSpan startingTime;
    startingTime
    = Process.GetCurrentProcess.Threads(0).
    UserProcessorTime;
    class chapter1 {
    static void Main() {
    int[] nums = new int[100000];
    BuildArray(nums);
    TimeSpan startTime;
    TimeSpan duration;
    startTime
    =
    Process.GetCurrentProcess().Threads[
    0].
    UserProcessorTime;
    DisplayNums(nums);
    duration
    =
    Process.GetCurrentProcess().Threads[
    0].
    UserProcessorTime.
    Subtract(startTime);
    Console.WriteLine(
    "Time: " + duration.TotalSeconds);
    }
    static void BuildArray(int[] arr) {
    for(int i = 0; i <= 99999; i++)
    arr[i]
    = i;
    }
    static void DisplayNums(int[] arr) {
    for(int i = 0; i <= arr.GetUpperBound(0); i++)
    Console.Write(arr[i]
    + " ");
    }
    }


    Using the new and improved timing code, the program returns 0.2526.
    This compares with the approximately 5 seconds returned using the first
    timing code. Clearly, there is a major discrepancy between these two timing
    techniques and you should use the .NET techniques when timing code in the
    .NET environment.

    使用这个改进的时间记录代码,时间变成了0.2526秒,比之前的5秒,显然差距很大。
    所以,当在.net下计算运行时间时要使用.net的技术。

    A Timing Test Class
    Although we don’t need a class to run our timing code, it makes sense to
    rewrite the code as a class, primarily because we’ll keep our code clear if we
    can reduce the number of lines in the code we test.
    A Timing class needs the following data members:
     startingTime—to store the starting time of the code we are testing
     duration—the ending time of the code we are testing
    The starting time and the duration members store times and we chose to use
    the TimeSpan data type for these data members.We’ll use just one constructor
    method, a default constructor that sets both the data members to 0.
    We’ll need methods for telling a Timing object when to start timing code
    and when to stop timing.We also need a method for returning the data stored
    in the duration data member.
    As you can see, the Timing class is quite small, needing just a few methods.
    Here’s the definition:
    一个时间测试类
    尽管我们不需要一个类去运行我们的时间测试代码,把它写成一个类,主要是为了使我们的代码变干净。
    一个时间测试类需要如下成员:开始时间,结束时间。
    开始时间和结束时间用TimeSpan来存储。只需要一个构造函数,一个默认的构造函数来把值设置成0.
    我们需要一个方法来告诉时间对象什么时候开始和结束,还需要一个方法来返回结束时间。
    如下就是这个类了。

    代码
    public class Timing {
    TimeSpan startingTime;
    TimeSpan duration;
    public Timing() {
    startingTime
    = new TimeSpan(0);
    duration
    = new TimeSpan(0);
    }
    public void StopTime() {
    duration
    =
    Process.GetCurrentProcess().Threads[
    0].
    UserProcessorTime.Subtract(startingTime);
    }
    public void startTime() {
    GC.Collect();
    GC.WaitForPendingFinalizers();
    startingTime
    =
    Process.GetCurrentProcess().Threads[
    0].
    UserProcessorTime;
    }
    public TimeSpan Result() {
    return duration;
    }
    }
    Here’s the program to test the DisplayNums subroutine, rewritten with the
    Timing
    class:
    using System;
    using System.Diagnostics;
    public class Timing {
    TimeSpan startingTime;
    TimeSpan duration;
    public Timing() {
    startingTime
    = new TimeSpan(0);
    duration
    = new TimeSpan(0);
    }
    public void StopTime() {
    duration
    =
    Process.GetCurrentProcess().Threads[
    0].
    UserProcessorTime.
    Subtract(startingTime);
    }
    public void startTime() {
    GC.Collect();
    GC.WaitForPendingFinalizers();
    startingTime
    =
    Process.GetCurrentProcess().Threads[
    0].
    UserProcessorTime;
    }
    public TimeSpan Result() {
    return duration;
    }
    }
    class chapter1 {
    static void Main() {
    int[] nums = new int[100000];
    BuildArray(nums);
    Timing tObj
    = new Timing();
    tObj.startTime();
    DisplayNums(nums);
    tObj.stopTime();
    Console.WriteLine(
    "time (.NET): " & tObj.Result.
    TotalSeconds);
    }
    static void BuildArray(int[] arr) {
    for(int i = 0; i < 100000; i++)
    arr[i]
    = I;
    }
    }

    By moving the timing code into a class, we’ve cut down the number of lines
    in the main program from 13 to 8. Admittedly, that’s not a lot of code to cut
    out of a program, but more important than the number of lines we cut is the
    clutter in the main program. Without the class, assigning the starting time to
    a variable looks like this:
    通过把时代代码放进一个类,我们把代码行数从13行砍成8行了。再没什么代码可以砍了,
    但是比代码行数更重要的是main程序里的杂乱。没有这个类的话,就这样用

    startTime = Process.GetCurrentProcess().Threads[0)].UserProcessorTime;

    With the Timing class, assigning the starting time to the class data member
    looks like this:
    tObj.startTime();
    使用时间类来记录开始时间:tObj.startTime();
    SUMMARY
    This chapter reviews three important techniques we will use often in this book.
    Many, though not all of the programs we will write, as well as the libraries we
    will discuss, are written in an object-oriented manner. The Collection class
    we developed illustrates many of the basic OOP concepts seen throughout
    these chapters. Generic programming allows the programmer to simplify the
    definition of several data structures by limiting the number of methods that
    have to be written or overloaded. The Timing class provides a simple, yet
    effective way to measure the performance of the data structures and algorithms
    we will study.
    总结
    这一章回顾了三种重要的技术。虽然不是所有程序都会写,以及我们提到的类库是用OO的方式写的。
    我们开发演示了基础的OOP概念。泛型编程允许程序员通过限制不得不重载的方法的数量来简化一些数据结构的定义。
    时间类提供了一个简单有效地方法来衡量数据结和算法的表现。


  • 相关阅读:
    慎用静态类static class
    20170617
    学习笔记之工厂模式-2017年1月11日23:00:53
    链表翻转
    面试被虐
    tips
    依赖注入那些事儿
    浅谈算法和数据结构(1):栈和队列
    猫都能学会的Unity3D Shader入门指南(一)
    SerializeField等Unity内的小用法
  • 原文地址:https://www.cnblogs.com/icuit/p/1759866.html
Copyright © 2020-2023  润新知