程序与计算机工作的本质是对外界的信息抽象成数据然后通过一系列指令对数据进行操作,以期达到想要的目的。这里有几个概念。
1.数据:数据是个笼统的抽象名词,数据是指所有能输入到计算机并被计算机程序处理的符号的介质的总称,是用于输入计算机进行处理,具有一定意义的数字、字母、符号和模拟量等的通称。
2.数据元素:数据元素是数据的基本单位,在计算机程序中通常作为一个整体考虑。一个数据元素可以由若干个数据项组成。数据项是数据的不可分割的最小单位。有两类数据元素:一类是不可分割的原子型数据元素。另一类是由多个款项构成的数据元素,其中每个款项被称为一个数据项
3.数据结构:这个东东至今没有明确的定义,一般书上都是这样简单定义的:数据结构是一种或多种特定关系的数据元素的集合。一般认为,一个数据结构是由数据元素依据某种逻辑联系组织起来的。对数据元素间逻辑关系的描述称为数据的逻辑结构;数据必须在计算机内存储,数据的存储结构是数据结构的实现形式,是其在计算机内的表示;此外讨论一个数据结构必须同时讨论在该类数据上执行的运算才有意义。数据结构的内容包括数据元素本身以及数据之间的关系。可以从两个方面来描述数据结构:
一是数据结构的物理/存储结构:
数据结构在计算机中的表示(映像)称为数据的物理(存储)结构。它包括数据元素的表示和关系的表示。数据元素之间的关系有两种不同的表示方法:顺序映象和 非顺序映象,并由此得到两种不同的存储结构:顺序存储结构和链式存储结构。顺序存储方法:它是把逻辑上相邻的结点存储在物理位置相邻的存储单元里,结点间 的逻辑关系由存储单元的邻接关系来体现,由此得到的存储表示称为顺序存储结构。顺序存储结构是一种最基本的存储表示方法,通常借助于程序设计语言中的数组 来实现。链接存储方法:它不要求逻辑上相邻的结点在物理位置上亦相邻,结点间的逻辑关系是由附加的指针字段表示的。由此得到的存储表示称为链式存储结构,链式存储结构通常借助于程序设计语言中的指针类型来实现。索引存储方法:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。散列存储方法:就是根据结点的关键字直接计算出该结点的存储地址。
一是逻辑结构:这是我们对数据结构进行分类的标准,依照数据元素之间的关系可分为四种基本结构:(1)集合:结构中的数据元素之间没有关系,只是同属于一个结合而已,这是一种最松散的数据结构,跟数学中的结构概念基本一致(2)线性结构:这应该是一种使用最多的数据结构,在线性结构中数据元素之间有着一对一的顺序关系(3)树形结构:结构中的数据元素有着一对多的关系(4)图状或网状结构:由上可知这肯定是数据元素之间有着多对多的关系。
4.算法:算法是对解决一个或一类问题的过程的描述。其本质是一个对过程的描述,至于描述方式可以有多种,这个描述可以是纯文本的,可以是图形的,可以是伪代码,也可以用一种程序语言进行描述等等。
算法有5个重要的特性:(1)有穷性:算法是要解决问题的,如果一个算法在时间上无穷的这个算法肯定无法解决这个问题。(2)确定性:算法描述的每一步都无二义性。(3)可行性:即算法所描述的每一步都是在现有基础上能够实现的。(4)输入:一个算法有零个或多个输入。(5)一个算法有一个或多个输出
一个好的算法应该具备以下几个特点:
正确性,可读性,健壮性,效率与低存储性。
算法与数据结构的关系:算法与数据结构不是单独存在的而是相互依存的,它们具有双向选择性,数据结构组织数据是为了使用算法来对数据结构中的数据元素进行操作,而算法的目的就是为了对数据元素进行运算。算法的设计取决于数据(逻辑)结构,而算法的实现依赖于采用的存储结构。数据的存储结构实质上是它的逻辑结构在计算机存储器中的实现,为了全面的反映一个 数据的逻辑结构,它在存储器中的映象包括两方面内容,即数据元素之间的信息和数据元素之间的关系。不同数据结构有其相应的若干运算。数据的运算是在数据的 逻辑结构上定义的操作算法,如检索、插入、删除、更新和排序等。数据的运算是数据结构的一个重要方面,讨论任一种数据结构时都离不开开对该结构上的数据运算及其实现算法的讨论。有时我们是对一个数据结构要进行一个运算从而选择一个算法,比如对于顺序存储的线性表的查找,我们可以采取顺序查找我们也可以采取折半查找等。而另一种情况是基于算法选择数据结构,我们知道在一类数据上我们已知需要进行某种操作,比如字典自然最多的是查找操作而不是插入更新等操作。这是我们是要针对某中查找算法而选择一种数据结构如针对哈希查找的哈希表。
计算机解决一个具体问题时,大致需要经过下列几个步骤:首先要从具体问题中抽象出一个适当的数学模型,然后设计一个解此数学模型的算法 (Algorithm),最后编出程序、进行测试、调整直至得到最终解答。寻求数学模型的实质是分析问题,从中提取操作的对象,并找出这些操作对象之间含 有的关系,然后用数学的语言加以描述。计算机算法与数据的结构密切相关,算法无不依附于具体的数据结构,数据结构直接关系到算法的选择和效率。运算是由计算机来完成,这就要设计相应的插入、删除和修改的算法 。也就是说,数据结构还需要给出每种结构类型所定义的各种运算的算法。
下面是一些常见的数据结构:
线性结构:线性结构中的数据元素都是不可再分的原子类型,可以分为以下集中类型
(1)线性表:线性表是数据元素同属于同种数据并且有先后的顺序关系,第一数据元素只有后继没有前驱,最后一个数据元素只有前驱而没有后继,其余中间的每个数据元素都有一个唯一的前驱和一个唯一的后继。线性表可以在任意位置进行读取删除等操作。线性表在计算机可由两种方式来实现,一种是顺序存储,一种自然是链式存储。一讲到顺序存储大家可能会想到数组,的确在具体的程序设计语言中顺序存储都是通过一维数组来实现的(至少我还没发现不是用数组实现的),但顺序存储决不等同于数组,数组本身也是种数据结构。
(2)栈(Stack):不多说大家都懂,就是后进先出。栈十个死胡同只有一个口进出,所以是后进先出的。栈其实是受限的线性表。线性表可以在任意位置进行操作,而栈只能在一头进行操作。同样栈可以通过顺序和链式两种存储方法来实现。
(3)队列(Queue):也无须多说,大家应该都排过队的,队列是有两头口子的胡同(不是死胡同了)不过一头只准进另一头只准出,所以就是先进来的就可以先出去了。队列也可以用两种存储方式实现,链式存储方式和顺序存储方式,只不过顺序存储方式与一般不同的是把一连续的地址的存储单元采用序号取模的方式循环使用,个这种顺序方式实现的队列又叫循环队列。队列里面又可以分为:优先队列,双端队列,受限双端队列等顾名思义就不解释了。
(4)串:即字符串。不知怎么的把字符串也搞成了一个数据结构。串应该就是一种数据元素为字符的线性表,可能是线性表中操作哦的多半是单个数据元素,而串中是把多个数据元素当成一个整体来操作。同样也可以采取顺序和链式两种存储方式。
(1)数组(Array):上述集中线性结构中的数 据元素都是不可再分的原子类型。数组看似和线性表很像但数组以及下面要讲的广义表的数据元素本身是一种数据结构如线性结果,栈等,数组可看成是拓展的线性结构,数组的每一个数据元素都是 一个等长的同一类型的线性表时就成了矩阵。数组自然是采用顺序存储方式,不知道有没有采用链式的。
(2)广义表:同样广义表可看成是拓展的线性结构。线性表被定义为一个有限的序列 (a1,a2,a3,…,an)其中ai被限定为是单个数据元素。广义表是n个数据元素d1,d2,d3,…,dn的有限序列,但不同的是,广义表中的 di 则既可以是单个元素,还可以是一个广义表,通常记作:GL=(d1,d2,d3,…,dn)。GL是广义表的名字,通常广义表的名字用大写字母表示。n是 广义表的长度。若其中di是一个广义表,则称di是广义表GL的子表。在广义表GL中,d1是广义表GL的表头,而广义表GL其余部分组成的表 (d2,d3,…,dn)称为广义表的表尾。由此可见广义表的定义是递归定义的。因为在定义广义表时,又使用了广义表的概念。广义表通常采用链式存储结 构。
非线性数据结构:
(3)树(Tree):
树(tree)是包含n(n>0)个结点的有穷集合K,且在K中定义了一个关系N,N满足 以下条件:
(1)有且仅有一个结点 k0,他对于关系N来说没有前驱,称K0为树的根结点。简称为根(root)。
(2)除K0外,k中的每个结点,对于关系N来说有且仅有一个前驱。
(3)K中各结点,对关系N来说可以有m个后继(m>=0)。
若n>1,除根结点之外的其余数据元素被分为m(m>0)个互不相交的结合T1,T2,……Tm,其中每一个集合Ti(1<=i<=m)本身也是一棵树。
树 T1,T2,……Tm称作根结点的子树(sub tree)。
树也就可以这样定义:树是有根结点和若干颗子树构成的。------(上一段来自高林,《数据结构(C++)》,北京 清华大学出版社)
树是由一个集合以及在该集合上定义的一种关系构成的。集合中的元素称为树的结点,所定义的关系称为父子关系。父子关系在树的结点之间建立了一个层次结构。在这种层次结构中有一个结点具有特殊的地位,这个结点称为该树的根结点,或简称为树根。我们可以形式地给出树的递归定义如下:
单个结点是一棵树,树根就是该结点本身。
设T1,T2,..,Tk是树,它们的根结点分别为n1,n2,..,nk。用一个新结点n作 为n1,n2,..,nk的父亲,则得到一棵新树,结点n就是新树的根。我们称n1,n2,..,nk为一组兄弟结点,它们都是结点n的儿子结点。我们还 称n1,n2,..,nk为结点n的子树。
空集合也是树,称为空树。空树中没有结点。
树是图的一种特例,无向不循环图即为树。
树可细分很多种:有二叉树,平衡二叉树,红黑树,B树等等
(4)堆 (Heap):
在计算机科学中,堆是一种特殊的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。
(5)图(Graph):相比较起来图是一种相当复杂的数据结构
图的定义如下:
图是由结点的有穷集合V和边的集合E组成。其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点,边是顶点的有序偶对,若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系。
在上面两个图结构中,一个是有向图,即每条边都有方向,另一个是无向图,即每条边都没有方向。
在有向图中,通常将边称作弧,含箭头的一端称为弧头,另一端称为弧尾,记作<vi,vj>,它表示从顶点vi到顶点vj有一条边。
若有向图中有n个顶点,则最多有n(n-1)条弧,我们又将具有n(n-1)条弧的有向图称作有向完全图。以顶点v为弧尾的弧的数目称作顶点v的出度, 以顶点v为弧头的弧的数目称作顶点v的入度。在无向图中,边记作(vi,vj),它蕴涵着存在< vi,vj>和<vj,vi>两条弧。若无向图中有n个顶点,则最多有n(n-1)/2条弧,我们又将具有n(n-1)/2条弧的无向 图称作无向完全图。与顶点v相关的边的条数称作顶点v的度。
路径长度是指路径上边或弧的数目。
若第一个顶点和最后一个顶点相同,则这条路径是一条回路。
若路径中顶点没有重复出现,则称这条路径为简单路径。
在无向图中,如果从顶点vi到顶点vj有路径,则称vi和vj连通。如果图中任意两个顶点之间都连通,则称该图为连通图,否则,将其中的极大连通子图称为连通分量。
在有向图中,如果对于每一对顶点vi和vj,从vi到vj和从vj到vi都有路径,则称该图为强连通图;否则,将其中的极大连通子图称为强连通分量。
图的基本操作
(1)创建一个图结构 CreateGraph(G)
(2)检索给定顶点 LocateVex(G,elem)
(3)获取图中某个顶点 GetVex(G,v)
(4)为图中顶点赋值 PutVex(G,v,value)
(5)返回第一个邻接点 FirstAdjVex(G,v)
(6)返回下一个邻接点 NextAdjVex(G,v,w)
(7)插入一个顶点 InsertVex(G,v)
(8)删除一个顶点 DeleteVex(G,v)
(9)插入一条边 InsertEdge(G,v,w)
(10)删除一条边 DeleteEdge(G,v,w)
(11)遍历图 Traverse(G,v)
图的存储结构:
1.数组(邻接矩阵)存储表示(有向或无向):用两个数组分别存储数据元素(定点)的信息和数据元素之间的关系(边或弧)的信息
2.邻接表:是图的一种链式存储结构。在图的每个顶点建立一个单链表(n个顶点建立n个单链表),第i个单链表中的结点包含顶点Vi的所有邻接顶点。又称链接表。
补充: 1.在有向图的邻接表中不易找到指向该顶点的弧 2.在有向图的邻接表中,对每个顶点,链接的是指向该顶点的弧。
3.有向图的十字链表:十字链表(Orthogonal List)是有向图的另一种链式存储结构。可以看成是将有向图的邻接表和逆邻接表结合起来得到的一种链表。在十字链表中,对应于有向图中每一条弧都有一个结点,对应于每个定顶点也有一个结点。十字链表之于有向图,类似于邻接表之于无向图。用链表模拟矩阵的行(或者列,这可以根据个人喜好来定),然后,再构造代表列的链表,将每一行中的元素节点插入到对应的列中去。十字链表的逻辑结构就像是 一个围棋盘(没见过,你就想一下苍蝇拍,这个总见过吧),而非零元就好像是在棋盘上放的棋子,总共占的空间就是,确定那些线的表头节点和那些棋子代表的非 零元节点。最后,我们用一个指针指向这个棋盘,这个指针就代表了这个稀疏矩阵。
4.无向图的邻接多重链表:邻接多重表是无向图的另一种链式存储结构。
图的遍历:
1.图的深度优先遍历:深度优先搜索法是树的先根遍历的推广,它的基本思想是:从图G的某个顶点v0出发,访问v0,然后选择一个与v0相邻且没被访问过的顶点vi访问,再从 vi出发选择一个与vi相邻且未被访问的顶点vj进行访问,依次继续。如果当前被访问过的顶点的所有邻接顶点都已被访问,则退回到已被访问的顶点序列中最 后一个拥有未被访问的相邻顶点的顶点w,从w出发按同样的方法向前遍历,直到图中所有顶点都被访问
2.图的宽度优先遍历:图的广度优先搜索是 树的按层次遍历的推广,它的基本思想是:首先访问初始点vi,并将其标记为已访问过,接着访问vi的所有未被访问过的邻接点vi1,vi2, …, vi t,并均标记已访问过,然后再按照vi1,vi2, …, vi t的次序,访问每一个顶点的所有未被访问过的邻接点,并均标记为已访问过,依次类推,直到图中所有和初始点vi有路径相通的顶点都被访问过为止。
(6)散列表 (Hash):
若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数(Hash function),按这个思想建立的表为散列表。
常用的构造散列函数的方法
散列函数能使对一个数据序列的访问过程更加迅速有效,通过散列函数,数据元素将被更快地定位:
1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a•key + b,其中a和b为常数(这种散列函数叫做自身函数)
2. 数字分析法
3. 平方取中法
4. 折叠法
5. 随机数法
6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。
处理冲突的方法
1. 开放寻址法:Hi=(H(key) + di) MOD m, i=1,2,…, k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列,可有下列三种取法:
(1). di=1,2,3,…, m-1,称线性探测再散列;
(2). di=1^2, (-1)^2, 2^2,(-2)^2, (3)^2, …, ±(k)^2,(k<=m/2)称二次探测再散列;
(3). di=伪随机数序列,称伪随机探测再散列。 == 2. 再散列法:Hi=RHi(key), i=1,2,…,k RHi均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。
3. 链地址法(拉链法)
4. 建立一个公共溢出区
以上部分概念来自百度百科