[转]数据结构(C#版)概念整理

第一章

1、数据(Data)

数据是外部世界信息的载体,它能够被计算机识别、存储和加工处理,是计算机程序加工的原料。计算机程序处理各种各样的数据,可以是数值数据,如整数、实数或复数;也可以是非数值数据,如字符、文字、图形、图像、声音等。

2、数据元素(Data Element)和数据项(Data Item)

数据元素是数据的基本单位,在计算机程序中通常被作为一个整体进行考虑和处理。数据元素有时也被称为元素、结点、顶点、记录等。一个数据元素可由若干个数据项(Data Item)组成。数据项是不可分割的、含有独立意义的最小数据单位,数据项有时也称为字段(Field)或域(Domain)。数据项分为两种,一种叫做初等项,另一种叫做组合项。

3、数据对象(Data Object)

数据对象是性质相同的数据元素的集合,是数据的一个子集。

4、数据类型(Data Type)

数据类型是高级程序设计语言中的概念,是数据的取值范围和对数据进行操作的总和。数据类型可分为两类:一类是非结构的原子类型,如C#语言中的基本类型(整型、实型、字符型等);另一类是结构类型,它的成分可以由多个结构类型组成,并可以分解。结构类型的成分可以是非结构的,也可以是结构的。

5、数据结构(Data Structure)

数据结构是相互之间存在一种或多种特定关系的数据元素的集合。在任何问题中,数据元素之间都不是孤立的,而是存在着一定的关系,这种关系称为结构(Structure)。根据数据元素之间关系的不同特性,通常有4类基本数据结构: (1) 集合(Set);(2) 线性结构(Linear Structure);(3) 树形结构(Tree Structure);(4) 图状结构(Graphic Structure)。

数据结构的形式化定义为: 数据结构(Data Structure)简记为DS,是一个二元组, DS = (D,R) 其中:D是数据元素的有限集合, R是数据元素之间关系的有限集合。

数据结构包括数据的逻辑结构和物理结构。数据的物理结构又称为存储结构(Storage Structure),是数据在计算机中的表示(又叫映像)和存储,包括数据元素的表示和存储以及数据元素之间关系的表示和存储。

数据的存储结构包括顺序存储结构和链式存储结构两种。顺序存储结构(Sequence Storage Structure)是通过数据元素在计算机存储器中的相对位置来表示出数据元素的逻辑关系,一般把逻辑上相邻的数据元素存储在物理位置相邻的存储单元中。在C#语言中用数组来实现顺序存储结构。因为数组所分配的存储空间是连续的,所以数组天生就具有实现数据顺序存储结构的能力。链式存储结构(Linked Storage Structure)对逻辑上相邻的数据元素不要求其存储位置必须相邻。链式存储结构中的数据元素称为结点(Node),在结点中附设地址域(Address Domain)来存储与该结点相邻的结点的地址来实现结点间的逻辑关系。这个地址称为引用(Reference),这个地址域称为引用域(Reference Domain)。

6. 算法

从上节我们知道,算法与数据结构和程序的关系非常密切。进行程序设计时,先确定相应的数据结构,然后再根据数据结构和问题的需要设计相应的算法。由于篇幅所限,下面只从算法的特性、算法的评价标准和算法的时间复杂度等三个方面进行介绍。

1.2.1算法的特性

算法(Algorithm)是对某一特定类型的问题的求解步骤的一种描述,是指令的有限序列。其中的每条指令表示一个或多个操作。一个算法应该具备以下5个特性:

1)、有穷性(Finity):一个算法总是在执行有穷步之后结束,即算法的执行时间是有限的。

2)、确定性(Unambiguousness):算法的每一个步骤都必须有确切的含义,即无二义,并且对于相同的输入只能有相同的输出。

3)、输入(Input):一个算法具有零个或多个输入。它即是在算法开始之前给出的量。这些输入是某数据结构中的数据对象。

4)、 输出(Output):一个算法具有一个或多个输出,并且这些输出与输入之间存在着某种特定的关系。

5)、 能行性(realizability):算法中的每一步都可以通过已经实现的基本运算的有限次运行来实现。

算法的含义与程序非常相似,但二者有区别。一个程序不一定满足有穷性。例如操作系统,只要整个系统不遭破坏,它将永远不会停止。还有,一个程序只能用计算机语言来描述,也就是说,程序中的指令必须是机器可执行的,而算法不一定用计算机语言来描述,自然语言、框图、伪代码都可以描述算法。

7. 算法的时间复杂度

一个算法的时间复杂度(Time Complexity)是指该算法的运行时间与问题规模的对应关系。一个算法是由控制结构和原操作构成的,其执行的时间取决于二者的综合效果。

8. 集合的概念

集合(Set)是由一些确定的、彼此不同的成员(Member)或者元素(Element)构成的一个整体。成员取自一个更大的范围,称为基类型(Base Type)。集合中成员的个数称为集合的基数(Cardinality)。集合的每个成员或者是基类型的一个基本元素(Base Element),或者它本身也是一个集合。我们把是集合的成员叫做该集合的子集(Subset),子集中的每个成员都属于该集合。没有元素的集合称为空集(Empty Set,又称为Null Set),记作Φ。

集合的特性

1) 确定性:任何一个对象都能被确切地判断是集合中的元素或不是;

2) 互异性:集合中的元素不能重复;

3) 无序性:集合中元素与顺序无关。

9. 递归

一个算法调用自己来完成它的部分工作,在解决某些问题时,一个算法需要调用自身。如果一个算法直接调用自己或间接地调用自己,就称这个算法是递归的(Recursive)。根据调用方式的不同,它分为直接递归(Direct Recursion)和间接递归(Indirect Recursion)。

10. 接口

1)、 接口的定义

接口(Interface)定义为一个约定,实现接口的类或结构必须遵守该约定。简单的说,接口是类之间交互时遵守的一个协议。这种将一个对象看成多个类型的能力通常称作多继承(Multiple Inheritance)。通用语言运行时(CLR)支持单实现继承和多接口继承。单实现继承(Single Implementation Inheritance)是指一个类型只能有一个基类型。多接口继承(Multiple Interface Inheritance)是指一个类型可以继承多个接口,而接口是类之间相互交互的一个抽象(Abstract),把类之间需要交互的内容抽象出来定义成接口,可以更好地控制类之间的逻辑交互。

接口只包含成员定义,不包含成员的实现。接口不会继承自任何的System.Object派生类型。接口仅仅是一个包含着一组虚方法的抽象类型。成员的实现需要在继承的类或者结构中实现。接口的成员包括静态方法、索引器、常数、事件以及静态构造器等,不包含任何实例字段或实例构造器,所以,不能实例化一个接口。 实现接口的类必须严格按其定义来实现接口的每个成员。

11. 接口与抽象类

抽象类(Abstract Class)和接口在定义与功能上有很多相似的地方,在程序中选择使用抽象类还是接口需要比较抽象类和接口之间的具体差别。 抽象类是一种不能实例化而必须从中继承的类,抽象类可以提供实现,也可以不提供实现。子类只能从一个抽象类继承。抽象类应主要用于关系密切的对象。如果要设计大的功能单元或创建组件的多个版本,则使用抽象类。 接口是完全抽象的成员集合,不提供实现。类或者结构可以继承多个接口。接口最适合为不相关的类提供通用功能。如果要设计小而简练的功能块,则使用接口。接口一旦创建就不能更改,如果需要接口的新版本,必须创建一个全新的接口。

12. 泛型编程

泛型(Generic Type)是.NET Framework 2.0最强大的功能。泛型的主要思想就是将算法与数据结构完全分离开来,使得一次定义的算法能够作用于多种数据结构,从而实现高度可重用的开发。通过泛型可以定义类型安全的数据结构,而没有必要使用实际的数据类型。这将显著提高性能并得到更高质量的代码,因为可以重用数据处理算法,而没有必要复制类型特定的代码。

(1) 性能问题。(2) 类型安全。(3) 工作效率。

13. 泛型的好处

泛型使代码可以重用,类型和内部数据可以在不导致代码膨胀的情况下更改,而不管是值类型还是引用类型。可以一次性地开发、测试和部署代码,通过任何类型(包括将来的类型)来重用它,并且全部具有编译器支持和类型安全。因为泛型代码不会强行对值类型进行装箱和取消装箱,或者对引用类型进行向下强制类型转换,所以性能得到显著提高。对于值类型,性能通常会提高200%;对于引用类型,在访问该类型时,可以预期性能最多提高100%(当然,整个应用程序的性能可能会提高,也可能不会提高)。

第2章

1. 线性表

线性表是最简单、最基本、最常用的数据结构。线性表是线性结构的抽象(Abstract),线性结构的特点是结构中的数据元素之间存在一对一的线性关系。这种一对一的关系指的是数据元素之间的位置关系,即:(1)除第一个位置的数据元素外,其它数据元素位置的前面都只有一个数据元素;(2)除最后一个位置的数据元素外,其它数据元素位置的后面都只有一个元素。也就是说,数据元素是一个接一个的排列。因此,可以把线性表想象为一种数据元素序列的数据结构。

2. 线性表的定义

线性表(List)是由n(n≥0)个相同类型的数据元素构成的有限序列。对于这个定义应该注意两个概念:一是“有限”,指的是线性表中的数据元素的个数是有限的,线性表中的每一个数据元素都有自己的位置(Position)。二是“相同类型”,指的是线性表中的数据元素都属于同一种类型。

3. 顺序表的定义

在计算机内,保存线性表最简单、最自然的方式,就是把表中的元素一个接一个地放进顺序的存储单元,这就是线性表的顺序存储(Sequence Storage)。线性表的顺序存储是指在内存中用一块地址连续的空间依次存放线性表的数据元素,用这种方式存储的线性表叫顺序表(Sequence List),顺序表的特点是表中相邻的数据元素在内存中存储位置也相邻。

4. 链式存储 (Linked Storage)

这样的线性表叫链表(Linked List)。链表不要求逻辑上相邻的数据元素在物理存储位置上也相邻,因此,在对链表进行插入和删除时不需要移动数据元素,但同时也失去了顺序表可随机存储的优点。

5. 链表

是用一组任意的存储单元来存储线性表中的数据元素(这组存储单元可以是连续的,也可以是不连续的)。

两部分信息组成该数据元素的存储映像(Image),称为结点(Node)。把存储据元素本身信息的域叫结点的数据域(Data Domain),把存储与它相邻的数据元素的存储地址信息的域叫结点的引用域(Reference Domain)。因此,线性表通过每个结点的引用域形成了一根“链条”,这就是“链表”名称的由来。

如果结点的引用域只存储该结点直接后继结点的存储地址,则该链表叫单链表(Singly Linked List)。

第三章 栈和队列

栈和队列是非常重要的两种数据结构,在软件设计中应用很多。栈和队列也是线性结构,线性表、栈和队列这三种数据结构的数据元素以及数据元素间的逻辑关系完全相同,差别是线性表的操作不受限制,而栈和队列的操作受到限制。栈的操作只能在表的一端进行,队列的插入操作在表的一端进行而其它操作在表的另一端进行,所以,把栈和队列称为操作受限的线性表。

栈分为顺序栈和链栈。顺序栈用数组表示,链栈使用单链来表示,是单链的一种简化。

2. 队列

队列(Queue)是插入操作限定在表的尾部而其它操作限定在表的头部进行的线性表。把进行插入操作的表尾称为队尾(Rear),把进行其它操作的头部称为队头(Front)。当对列中没有数据元素时称为空对列(Empty Queue)。

队列通常记为:Q= (a1,a2,…,an),Q是英文单词queue的第1个字母。a1为队头元素,an为队尾元素。这n个元素是按照a1,a2,…,an的次序依次入队的,出对的次序与入队相同,a1第一个出队,an最后一个出队。所以,对列的操作是按照先进先出(First In First Out)或后进后出( Last In Last Out)的原则进行的,因此,队列又称为FIFO表或LILO表.

判断队空的条件是:rear==front,判断队满的条件是:(rear + 1) % maxsize==front。求循环队列中数据元素的个数可由(rear-front+maxsize)%maxsize公式求得。

队尾指示器的加1操作修改为: rear = (rear + 1) % maxsize

队头指示器的加1操作修改为: front = (front + 1) % maxsize

2.1链队列

队列的另外一种存储方式是链式存储,这样的队列称为链队列(Linked Queue)。同链栈一样,链队列通常用单链表来表示,它的实现是单链表的简化。所以,链队列的结点的结构与单链表一样.

3. 树

二叉树(Binary Tree)是n(n≥0)个相同类型的结点的有限集合。n=0的二叉树称为空二叉树(Empty Binary Tree);对于n>0的任意非空二叉树有:

(1)有且仅有一个特殊的结点称为二叉树的根(Root)结点,根没有前驱结点;

(2)若n>1,则除根结点外,其余结点被分成了2个互不相交的集合TL,TR,而TL、TR本身又是一棵二叉树,分别称为这棵二叉树的左子树(Left Subtree)和右子树(Right Subtree)。

(1)满二叉树(Full Binary Tree):如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树,对于深度为k的满二叉树的结点个数为2k-1。

(2)完全二叉树(Complete Binary Tree):深度为k,有n个结点的二叉树当且仅当其每一个结点都与深度为k,有n个结点的满二叉树中编号从1到n的结点一一对应时,称为完全二叉树,完全二叉树的特点是叶子结点只可能出现在层次最大的两层上,并且某个结点的左分支下子孙的最大层次与右分支下子孙的最大层次相等或大1。

3.1二叉树的性质

性质1 一棵非空二叉树的第i层上最多有2i-1个结点(i≥1)。

性质2 若规定空树的深度为0,则深度为k的二叉树最多有2k-1个结点(k≥0)。

性质3 具有n个结点的完全二叉树的深度k为log2n+1。

性质4 对于一棵非空二叉树,如果度为0的结点数目为n0,度为2的结点数目为n2,则有n0= n2+1。

性质5 对于具有n个结点的完全二叉树,如果按照从上到下和从左到右的顺序对所有结点从1开始编号,则对于序号为i的结点,有:

(1)如果i>1,则序号为i的结点的双亲结点的序号为i/2(“/”表示整除);如果i=1,则该结点是根结点,无双亲结点。

(2)如果2i≤n,则该结点的左孩子结点的序号为2i;若2i>n,则该结点无左孩子。

(3)如果2i+1≤n,则该结点的右孩子结点的序号为2i+1;若2i+1>n,则该结点无右孩子。

3.2 二叉树遍历

1、先序遍历(DLR)

先序遍历的基本思想是:首先访问根结点,然后先序遍历其左子树,最后先序遍历其右子树。

2、中序遍历(LDR)

中序遍历的基本思想是:首先中序遍历根结点的左子树,然后访问根结点,最后中序遍历其右子树。

3、后序遍历(LRD)

后序遍历的基本思想是:首先后序遍历根结点的左子树,然后后序遍历根结点的右子树,最后访问根结点。

4、层序遍历(Level Order)

层序遍历的基本思想是:由于层序遍历结点的顺序是先遇到的结点先访问,与队列操作的顺序相同。所以,在进行层序遍历时,设置一个队列,将根结点引用入队,当队列非空时,循环执行以下三步:

(1) 从队列中取出一个结点引用,并访问该结点;

(2) 若该结点的左子树非空,将该结点的左子树引用入队;

(3) 若该结点的右子树非空,将该结点的右子树引用入队;

5.4哈夫曼树

5.4.1哈夫曼树的基本概念

首先给出定义哈夫曼树所要用到的几个基本概念。

(1)路径(Path):从树中的一个结点到另一个结点之间的分支构成这两个结点间的路径。

(2)路径长度(Path Length):路径上的分支数。

(3)树的路径长度(Path Length of Tree):从树的根结点到每个结点的路径长度之和。在结点数目相同的二叉树中,完全二叉树的路径长度最短。

(4)结点的权(Weight of Node):在一些应用中,赋予树中结点的一个有实际意义的数。

(5)结点的带权路径长度(Weight Path Length of Node):从该结点到树的根结点的路径长度与该结点的权的乘积。

(6)树的带权路径长度(WPL):树中所有叶子结点的带权路径长度之和,记为

Σ==n1kk.kWPLLW

其中,Wk为第k个叶子结点的权值,Lk为第k个叶子结点的路径长度。在图5.17(a)所示的二叉树中,结点B的路径长度为1,结点C和D的路径长度为2,结点E、F和G的路径长度为3,结点H的路径长度为4,结点I的路径长度为5。该树的路径长度为:1+2*2+3*3+4+5=23。如果结点B、C、D、E、F、G、H、I的权分别是1、2、3、4、5、6、7、8,则这些结点的带权路径长度分别是1*1、2*2、2*3、3*4、3*5、3*6、4*7、5*8,该树的带权路径长度为3*5+3*6+5*8=73。

那么,什么是哈夫曼树呢?

哈夫曼树(Huffman Tree),又叫最优二叉树,指的是对于一组具有确定权值的叶子结点的具有最小带权路径长度的二叉树。

哈夫曼算法。现叙述如下:

(1)根据给定的n个权值{w1,w2,…,wn},构造n棵只有根结点的二叉树集合F={T1,T2,…,Tn};

(2)从集合F中选取两棵根结点的权最小的二叉树作为左右子树,构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树根结点权值之和。

(3)在集合F中删除这两棵树,并把新得到的二叉树加入到集合F中;

(4)重复上述步骤,直到集合中只有一棵二叉树为止,这棵二叉树就是哈夫曼树。

树形结构是一种非常重要的非线性结构,树形结构中的数据元素称为结点,它们之间是一对多的关系,既有层次关系,又有分支关系。树形结构有树和二叉树两种。

树是递归定义的,树由一个根结点和若干棵互不相交的子树构成,每棵子树的结构与树相同,通常树指无序树。树的逻辑表示通常有四种方法,即直观表示法、凹入表示法、广义表表示法和嵌套表示法。树的存储方式有3种,即双亲表示法、孩子链表表示法和孩子兄弟表示法。

二叉树的定义也是递归的,二叉树由一个根结点和两棵互不相交的子树构成,每棵子树的结构与二叉树相同,通常二叉树指有序树。重要的二叉树有满二叉树和完全二叉树。二叉树的性质主要有5条。二叉树的的存储结构主要有三种:顺序存储结构、二叉链表存储结构和三叉链表存储结构,本书给出了二叉链表存储结构的C#实现。二叉树的遍历方式通常有四种:先序遍历(DLR)、中序遍历(LDR)、后序遍历(LRD)和层序遍历(Level Order)。

森林是m(m≥0)棵树的集合。树、森林与二叉树的之间可以进行相互转换。树的遍历方式有先序遍历和后序遍历两种,森林的遍历方式有先序遍历和中序遍历两种。

哈夫曼树是一组具有确定权值的叶子结点的具有最小带权路径长度的二叉树。哈夫曼树可用于解决最优化问题,在数据通信等领域应用很广。

1、直观表示法

它象日常生活中的树木一样。整个图就象一棵倒立的树,从根结点出发不断扩展,根结点在最上层,叶子结点在最下面。

2、凹入表示法

每个结点对应一个矩形,所有结点的矩形都右对齐,根结点用最长的矩形表示,同一层的结点的矩形长度相同,层次越高,矩形长度越短。

3、广义表表示法

用广义表的形式表示根结点排在最前面,用一对圆括号把它的子树结点括起,来,子树结点用逗号隔开。树的广义表表示如下:

(A(B(E,F,G),C(H),D(I,J)))

4、嵌套表示法

类似数学中所说的文氏图表示法。

6 图

图状结构简称图,是另一种非线性结构,它比树形结构更复杂。树形结构中的结点是一对多的关系,结点间具有明显的层次和分支关系。每一层的结点可以和下一层的多个结点相关,但只能和上一层的一个结点相关。而图中的顶点(把图中的数据元素称为顶点)是多对多的关系,即顶点间的关系是任意的,图中任意两个顶点之间都可能相关。也就是说,图的顶点之间无明显的层次关系,这种关系在现实世界中大量存在。

由最小生成树的定义可知,构造有n个顶点的无向连通网的最小生成树必须满足以下三个条件:

(1)构造的最小生成树必须包括n个顶点;

(2)构造的最小生成树有且仅有n-1条边;

(3)构造的最小生成树中不存在回路。

构造最小生成树的方法有许多种,典型的方法有两种,一种是普里姆(Prim)算法,一种是克鲁斯卡尔(Kruskal)算法。

2.普里姆(Prim)算法

假设G=(V,E)为一无向连通网,其中,V为网中顶点的集合,E为网中边的集合。设置两个新的集合U和T,其中,U为G的最小生成树的顶点的集合,T为G的最小生成树的边的集合。普里姆算法的思想是:令集合U的初值为U={u1}(假设构造最小生成树时从顶点u1开始),集合T的初值为T={}。从所有的顶点u∈U和顶点v∈V-U的带权边中选出具有最小权值的边(u,v),将顶点v加入集合U中,将边(u,v)加入集合T中。如此不断地重复直到U=V时,最小生成树构造完毕。此时,集合U中存放着最小生成树的所有顶点,集合T中存放着最小生成树的所有边。

3.克鲁斯卡尔(Kruskal)算法

克鲁斯卡尔算法的基本思想是:对一个有n个顶点的无向连通网,将图中的边按权值大小依次选取,若选取的边使生成树不形成回路,则把它加入到树中;若形成回路,则将它舍弃。如此进行下去,直到树中包含有n-1条边为止。

下面是拓扑排序算法的描述:

(1)在有向图中选择一个入度为0的顶点(即没有前驱的顶点),由于该顶点没有任何先决条件,输出该顶点;

(2)从图中删除所有以它为尾的弧;

(3)重复执行(1)和(2),直到找不到入度为0的顶点,拓扑排序完成。

如果图中仍有顶点存在,却没有入度为0的顶点,说明AOV网中有环路,否则没有环路。

由堆的定义可知,堆有如下两个性质:

(1)最大堆的根结点是堆中关键码最大的结点,最小堆的根结点是堆中关键码最小的结点,我们称堆的根结点记录为堆顶记录。

(2)对于最大堆,从根结点到每个叶子结点的路径上,结点组成的序列都是递减有序的;对于最小堆,从根结点到每个叶子结点的路径上,结点组成的序列都是递增有序的。

堆排序的过程是:设有n个记录,首先将这n个记录按关键码建成堆,将堆顶记录输出,得到n个记录中关键码最大(或最小)的记录。然后,再把剩下的n-1个记录,输出堆顶记录,得到n个记录中关键码次大(或次小)的记录。如此反复,便可得到一个按关键码有序的序列。

因此,实现堆排序需解决两个问题:

(1)如何将n个记录的序列按关键码建成堆;

(2)输出堆顶记录后,怎样调整剩下的n-1个记录,使其按关键码成为一个新堆。

排序是计算机程序设计中的一种重要操作,

成按记录的某个关键码有序的序列的过程。排序方法按涉及的存储器不同分为内部排序和外部排序两类。内部排序指记录存放在内存中并且在内存中调整记录之间的相对位置,没有内、外存的数据交换。外部

存中,借助于内存调整记录之间的相对位置,需要在内、外存之间交换数据。排序方

稳定排序方法在排序前后相同关键码值的记录之间的位置关系不变,不稳定排序方法在排序前后相同关键码值的记录之间的位置关系改变。本章主要介绍了常用的内部排序方法,包括三种简单排序方法,即直接插入排序、冒泡排序和简单选择排序,这三种排序方法在最好情况下的时间复杂度为O(n),在平均情况下和最坏情况下的时间复杂度都为O(n2),并且都是稳定的排序方法。

快速排序方法的平均性能最好,时间复杂度为O(nlog2n),所以,当待排序序列已经按关键码随机分布时,快速排序是最适合的。但快速排序在最坏情况下的时间复杂度是O(n2)。快速排序方法是不稳定的排序方法

堆排序方法在最好情况下、平均情况下和最坏情况下的时间复杂度不会发生变化,为O(nlog2n),并且所需的辅助空间少于快速排序方法。堆排序方法也是不稳定的排序方法。 归并排序方法在最好情况下、平均情况下和最坏情况下的时间复杂度不会发生变化,为O(nlog2n),但需要的辅助空间大于堆排序方法,但归并排序方法是稳定的排序方法。 以上排序方法都是通过记录关键码的比较和记录的移动来进行排序.

一般情况下,排序都采用顺序存储结构(基数排序方法除外),而当记录非常多时可以采用链式存储结构,但快速排序和堆排序却很难在链表上实现。

posted on 2012-05-17 12:40  SamWang  阅读(1165)  评论(0编辑  收藏  举报