大话数据结构

基本概念(参考了小草莓|||||博主的《数据结构基础概念篇》)

目录

基本概念(参考了小草莓|||||博主的《数据结构基础概念篇》)

线性表——零个或多个数据元素的有限序列

1.顺序表

a.线性表的顺序存储结构:把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里。用这种方法存储的线性表简称顺序表。是一种随机存取结构。

2.线性链表

栈和队列

栈的应用

队列

树的表示法

二叉树

遍历二叉树和线索二叉树

线索二叉树

树和森林

哈夫曼树

图(课程里讨论的都是简单图)

图的遍历

最小生成树

最短路径

拓扑排序

关键路径

查找

有序表查找

线性索引查找

二叉排序树

平衡二叉树

多路查找树

散列表查找(哈希表)概述

排序

冒泡排序算法

简单选择排序

直接插入排序

希尔排序

堆排序

归并排序

快速排序


1.逻辑结构:数据之间的相互关系。

   a.集合 结构中的数据元素除了同属于一种类型外,别无其它关系。

   b.线性结构 数据元素之间一对一的关系。

   c.树形结构 数据元素之间一对多的关系。

   d.图状结构或网状结构 结构中的数据元素之间存在多对多的关系。

2.物理结构:是把数据的逻辑结构在计算机中的存储形式(如:顺序结构、链式结构、索引结构、哈希结构等)。

3.算法五个特性: 有穷性、确定性、可行性、输入、输出。

4.算法设计要求:正确性、可读性、健壮性、时间效率高和存储量低。

线性表——零个或多个数据元素的有限序列

线性表是一种典型的线性结构。头结点无前驱有一个后继,尾节点无后继有一个前驱。链表只能顺序查找,定位一个元素的时间为O(N),删除一个元素的时间为O(1)。

1.顺序表

a.线性表的顺序存储结构:把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里。用这种方法存储的线性表简称顺序表。是一种随机存取结构。

b.存取和存储是不一样的,存储是指按顺序的;而存取则是可以随机的,可以利用元素下标进行。

2.线性链表

用一组任意的存储单元来依次存放线性表的结点,这组存储单元即可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址。data域是数据域,用来存放结点的值。next是指针域(亦称链域),用来存放结点的直接后继的地址(或位置)。

A.单链表:其中每个结点的存储地址是存放在其前趋结点next域中,而开始结点无前趋,故应设头指针head指向开始结点。同时,由于最后一个结点无后继,故结点的指针域为空,即NULL。头插法建表(逆序)、尾插法建表(顺序)。增加头结点的目的是算法实现上的方便,但增大了内存开销。

a.查找:只能从链表的头指针出发,顺链域next逐个结点往下搜索,直到搜索到第i个结点为止。因此,链表不是随机存取结构

b.插入:先找到表的第i-1的存储位置,然后插入。新结点先连后继,再连前驱。

c.删除:首先找到ai-1的存储位置p。然后令p–>next指向ai的直接后继结点,即把ai从链上摘下。最后释放结点ai的空间.r=p->next;p->next=r->next;delete r。

d.判断一个单向链表中是否存在环的最佳方法是快慢指针。

 

B.静态链表:用一维数组来实现线性链表,这种用一维数组表示的线性链表,称为静态链表。静态:体现在表的容量是一定的。(数组的大小);链表:插入与删除同前面所述的动态链表方法相同。静态链表中游标表示的是下一元素在数组中的位置。

是为了给没有指针的高级语言设计的一种实现单链表能力的方法。

基本和单链表相同,不过因为没有像动态链表的结点申请和释放问题,因此要自己实现malloc()和free()函数,通过更新备用链表来实现。前进通过k=L[k].cur;来实现。

 

C.循环链表:是一种头尾相接的链表。其特点是无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活。

   其实质就是将单链表终端结点的指针域Null改为指向表头结点或开始结点。

 

D.双向链表:在单链表的每个结点里再增加一个指向其直接前趋的指针域prior。这样就形成的链表中有两个方向不同的链。双链表一般由头指针唯一确定的,将头结点和尾结点链接起来构成循环链表,并称之为双向链表。设指针p指向某一结点,则双向链表结构的对称性可用下式描述:p—>prior—>next=p=p—>next—>prior。从两个方向搜索双链表,比从一个方向搜索双链表的方差要小。
       a.插入:先搞定插入节点的前驱和后继,再搞定后结点的前驱,最后搞定前结点的后继。
       b.在有序双向链表中定位删除一个元素的平均时间复杂度为O(n)。(查找O(n),删除O(1))
       c.可以直接删除当前指针所指向的节点。而不需要像单向链表中,删除一个元素必须找到其前驱。因此在插入数据时,单向链表和双向链表操作复杂度相同,而删除数据时,双向链表的性能优于单向链表。

 

栈和队列

是限定仅在表尾进行插入和删除操作的线性表。我们将允许插入和删除的一端称为栈顶(top),另一端称为栈底,不含任何数据元素的栈称为空栈。栈又称为先进后出的线性表,简称LIFO结构。空栈的判定条件定位top==-1。

栈的应用

A.四则运算表达式求值

    a.中缀表达式---->后缀表达式(栈用来进出运算的符号)

              规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断                           其与栈顶符号的优先级,,是右括号或优先级低于栈顶符号则栈顶元素依次出栈并输出,并将当前符号进栈,一                           直到最终输出后缀表达式为止。

    b.将后缀表达式进行运算得出结果。(栈用来进出运算的数字)

              规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到事符号,就将处于栈顶的两个数字出栈进行运                           算,运算结果进栈,一直到最终获得结果。

 

队列

队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。是一种先进先出的线性表,简称FIFO。允许插入的一端叫队尾,允许删除的一端称为队头。

        A.顺序队列:顺序存储结构。当头尾指针相等时队列为空。在队列里,front指针指向队头元素,而rear指针指向队尾元素的下一位置。出队时front指针移动。(存在假溢出现象)

        B.循环队列:把队列的头尾相接的顺序存储结构称为循环队列。判断循环队列队满的条件是(rear+1)%QueueSize==front。此时数组中还会有一个空闲单元。计算队列的长度为(rear-front+QueueSize)%QueueSize。

        C.链队列:链式存储结构。限制仅在表头删除和表尾插入的单链表。显然仅有单链表的头指针不便于在表尾做插入操作,为此再增加一个尾指针,指向链表的最后一个结点。

 

串是零个或多个字符组成的有限序列。

串的表示和实现:

  1. 定长顺序存储表示。静态存储分配的顺序表。
  2. 堆分配存储表示。存储空间是在程序执行过程中动态分配而得。所以也称为动态存储分配的顺序表
  3. 串的链式存储结构。

串匹配:将主串称为目标串,子串称之为模式串。朴素移位法匹配。KMP算法匹配。改进的KMP算法。

KMP匹配算法是在朴素移位算法基础上,去掉了i值回溯的部分。

改进的KMP匹配算法是在计算next基础上,如果a为字符与它next值指向的b位字符相等,那么该a位的nextval值就指向b位的nextval值,如果不等,则该a位的nextval值就是它自己a位的next值。

 

树(Tree)是n(n\geq0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:

(1)有且仅有一个根(Root)结点;

(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集,其中每一个集合本身又是一棵树,且称为根的子树(子树互不相交)。

1.结点拥有的子树数称为结点的度(Degree)。度为0的结点称为叶结点或终端结点;不为0的称为分支结点或非终端结点,也叫内部结点。树的度是树内各结点的度的最大值。

2.孩子结点,双亲结点,兄弟结点,堂兄弟结点。

3.树的高度或深度——树中结点的最大层次。

4.如果将树中结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。

5.森林是m(m>0)棵互不相交的树的集合。

树的表示法

A.双亲表示法

与链表中的结点相比,数据域依然存数据,指针域存储该结点的双亲位置(为了解决其他问题,还可以增加右兄弟域或长子域)

B.孩子表示法

每个结点有多个指针域,其中每个指针指向一棵子树的根结点,我们把这种方法叫做多重链表表示法。

三种方案:a.固定指针域个数为树的度;b.按需分配,专门取一个位置来存放结点的度。

                  c.把每个结点的孩子结点排列起来,以单链表作存储结构,如果是叶结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放于一个一维数组中。

C.孩子兄弟表示法(感觉是为了引出二叉树的,其内容和上述几个差不多

二叉树

二叉树(Binary Tree)是n(n\geq0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

特点:a.每个结点最多有两棵子树。

           b.左子树和右子树是有顺序的,次序不能颠倒。

           c.即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。

五种基本形态:空二叉树、只有一个根结点、根结点只有左子树、根结点只有右子树、根结点既有左子树又有右子树。

1.满树并不单是每个结点都存在左右子树,还必须所有的叶子都在同一层上。

2.对一棵具有n个结点的二叉树按层序编号,如果编号为i(1\leq i\leq n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。(所以注意,完全二叉树不是满二叉树,最底一层不需要子节点是满的。)

     完全二叉树的特点:a.叶子结点只能出现在最下两层

                                     b.最下层的叶子一定集中在左部连续位置

                                     c.倒数二层,若有叶子结点,一定都在右部连续位置

                                     d.如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况

                                     e.同样结点的二叉树,完全二叉树的深度最小。

二叉树性质:

a.在二叉树的第i 层上至多有2^{i-1}个结点(i\geq 1),深度为k的二叉树至多有2^{k}-1个结点(k\geqslant 1)。

b.对任何一棵二叉树T,如果其终端结点数为n_{0},度为2的结点数为n_{2},则n_0=n_2+1

c.具有n个结点的完全二叉树的深度为\left \lfloor \log_2n \right \rfloor+1\left \lfloor x \right \rfloor表示不大于x的最大整数)。

d.如果对一棵有n个结点的完全二叉树的结点按层序编号,则对任一结点i(1<=i<=n),有:

                 (1)如果i=1,则结点i无双亲,是二叉树的根;如果i>1,则其双亲的编号是 \left \lfloor i/2\right \rfloor
                 (2) 如果2i>n,则结点i无左孩子;否则,其左孩子是结点2i。
                 (3)如果2i+1>n,则结点i无右孩子;否则,其右孩子是结点2i+1。

二叉树存储结构:

1.顺序存储结构:一般只用于完全二叉树,否则会造成空间的浪费,比如深度为k的右斜树。

2.二叉链表法:二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域,这样的链表称为二叉链表。

遍历二叉树和线索二叉树

二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树的所有结点,使得每个结点被访问一次且仅被访问一次。

二叉树遍历方法(三种遍历方法只是代码的顺序有所区别,将输出那一行代码放在前中后的位置)

1.前序遍历DLR:根节点->左子树->右子树

2.中序遍历LDR:中序遍历左子树,然后是访问根结点,最后中序遍历右子树。

3.后序遍历LRD:从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。

4.层序遍历:从树的根结点开始访问,从上而下逐层遍历,在同一层中从左到右对结点逐个访问。

推导遍历结果:已知中序遍历序列和前或后便利序列,可以唯一确定一棵二叉树。

线索二叉树

       对于一个有n个结点的二叉链表,每个结点有指向左右孩子的两个指针域,所以一共是2n个指针域。而n个结点的二叉树一共有n-1个分支线数,所以有2n-(n-1)=n+1个空指针域。为了有效利用这些空着的内存,我们将指向前驱和后继的指针存入其中。

我们把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树。

1.线索化——对二叉树以某种次序遍历使其变为线索二叉树的过程。

2.为了区分指针域存的是孩子还是前驱或后继,增加只存放布尔型变量的标志域ltag和rtag。

3.线索化的过程就是在遍历的过程中修改空指针的过程。

基本步骤:

         1.p=T->lchild; p指向线索链表的根结点;
         2.若线索链表非空,循环:
                   a.循环,顺着p左孩子指针找到最左下结点;访问之;
                   b.若p所指结点的右孩子域为线索,p的右孩子结点即为后继结点循环: p=p->rchild; 并访问p所指结点。
                   c.一旦线索“中断”,p所指结点的右孩子域为右孩子指针,p=p->rchild,使 p指向右孩子结点;

树和森林

将树转换为二叉树的步骤如下:

1.加线。在所有兄弟结点之间加一条连线。

2.去线。对树中的每个结点,只保留它与第一个孩子的连线,删除它与其它孩子结点的连线。

3.层次调整。旋转一定角度,使结构层次分明。第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是结点的右孩子。

森林转换为二叉树:

森林是由若干棵树组成的,所以可以理解为,森林中的每棵树都是兄弟,可以按兄弟的处理来操作。

1.把每一棵树转换为二叉树。

2.第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵的二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连起来。

判断一棵二叉树能否转换成一棵树还是森林,看这棵二叉树的根结点有没有右孩子,有就是森林,没有就是树。

森林/树的前序遍历和二叉树的前序遍历结果相同,森林的后序遍历和二叉树的中序遍历结果相同。

哈夫曼树

带权路径长度WPL最小的二叉树称为赫夫曼树。

赫夫曼算法:

1.根据给定的n个权值\left \{ w_1,w_2,...,w_n \right \}构成n棵二叉树的集合F=\left \{ T_1,T_2,...,T_n \right \},其中每棵二叉树T_1中只有一个带权为w_i根结点,其左右子树均为空。

2.在F中选取两棵根结点的权值最小的树作为左右子树构成一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。

3.在F中删除这两棵树,同时将新得到的二叉树加入F中。

4.重复2和3,直到F只含一棵树为止。

赫夫曼编码:即在赫夫曼树基础上,规定赫夫曼树左分支代表0,右分支代表1,从根结点到叶结点所经过的路径分支组成的序列便为该结点对应字符的编码。

图(课程里讨论的都是简单图)

图(Graph)是由顶点的有穷非空集合(所以不允许没有顶点)和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

有向边:若从顶点vi到vj的边有方向,则称这条边为有向边,也称为弧,用有序偶对来表示,vi称为弧尾,vj称为弧头。

图的一些基本概念

1.对于具有n个顶点和e条边数的图,无向图0\leqslant e\leqslant \frac{n(n-1)}{2},有向图0\leqslant e\leqslant n(n-1)

2.如果任意两个顶点之间都存在边叫完全图。

3.顶点v的度是和v相关联的边的数目,记为TD(v)。

4.如果图中任意两个顶点vi、vj∈E,vi和vj都是连通的,则称G是连通图。

5.如果一个图有n个顶点和小于n-1条边,则是非连通图。

6.无向图中连通且n个顶点n-1条边叫生成树。有向图中顶点入度为0其余顶点入度为1的叫有向树。

图的存储形式

1.邻接矩阵  用两个数组来表示图,一个一维数组存储图中顶点信息,一个二维数组存储图中的边或弧的信息。

2.邻接表      a.图中顶点用一个一维数组存储,另外对于顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针。

                    b.图中每个顶点vi的所有邻接点构成一个线性表,用单链表存储,无向图称为顶点vi的边表,有向图称为顶点vi作为弧尾的出边表。

3.十字链表    对于有向图,将其邻接表和逆邻接表结合起来。邻接表体现在存放弧尾弧头的下标和出的指针域,逆邻接表体现在入的指针域和存放弧头下标。这样相当于一个纵向的逆邻接表,所以被称为十字链表。

4.邻接多重表

5.边集数组

 

图的遍历

深度优先遍历(DFS) 类似于先序遍历

广度优先遍历(BFS) 类似于层序遍历

深度优先遍历更适合目标比较明确,以找到目标为主要目的的情况,而广度优先更适合在不断扩大遍历范围时找到相对最优解的情况。

 

最小生成树

Prim算法:从U={u0},TE={}开始。重复执行下述操作:在所有u∈U,v∈V-U的边(u,v)∈E中找一条代价最小的边(u0,v0)并入集合TE,同时v0并入U,直至U=V为止。此时TE中必有n-1条边,则T=(V,{TE})为N的最小生成树。

其实就是以某顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树。适用于稠密图,即边多的。

Kruskal算法:假设N=(V,{E})是连通网,则令最小生成树的初始状态为只有n个顶点而无边的非连通图T={V,{}},图中每个顶点自成一个连通分量。在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入到T中,否则舍去此边而选择下一代价最小的边。依次类推,直至T中所有顶点都在同一连通分量上为止。

其实就是从权重最小的边开始选,逐步找全顶点且不形成环路。适用于边少的情况。

最短路径

Dijkstra算法:基于已经求出的最短路径的基础上,求得更远顶点的最短路径,最终得到你要的结果。

Floyd算法:求所有顶点到所有顶点的最短路径问题,两个二维矩阵,一个存权值,一个存路径,每一步以经过第i个点为中转,计算最短路径是否变化。

拓扑排序

有一个有向图,V中的顶点序列满足若从顶点vi到vj有一条路径,则在顶点序列中vi必在顶点vj之前。我们称这样的顶点序列为一个拓扑序列。所谓拓扑排序,其实就是对一个有向图构造拓扑序列的过程。一个AOV(顶点表示活动)网的拓扑序列不唯一。

算法描述:1.将入度为0的顶点依次入栈

                  2.若栈不空,则栈顶元素出栈且输出;然后在其邻接表中查找该元素的后继,将其后继入度减1,若入度为0则进栈。

                  3.若栈空时输出的顶点个数不是n,则有向图有环;否则,拓扑排序完毕。

关键路径

路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动。AOE网:带权的有向无环图,其中顶点表示事件,弧表示活动,权表示活动持续时间。

分两步:第一步,用改进的拓扑排序算法求出拓扑序列和stack2,;第二步求事件最迟发生时间ltv,并求出lte和ete,比对二者是否相等。

ete表示最早开工时间,lte表示最晚开工时间,最晚开工时间并不是等vj事件发生才开始,而必须要在vj事件之前发生,所以lte=ltv[j]-len

查找

查找就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素。

查找表分为静态查找表(查询是否存在和检索某个特定值的相关信息)和动态查找表(查找时插入或者删除数据元素)。

顺序表查找   就逐个比较嘛,改进就是添一个哨兵,类似于迭代器里的beg==end

有序表查找

折半查找: 前提是线性表中的记录必须是关键码有序,线性表必须采用顺序存储。

插值查找: 折半查找的改进,即改进mid的位置,按比例的去变化

mid=low+(high-low)*(key-a[low])/(a[high]-a[low]);//插值

斐波那契查找:mid=low+F[k-1]-1;         如果key>a[mid],low=mid+1;k=k-2;         如果key

之所以是F[k-2]是因为左边是F[k-1]-1,全长时F[k]-1,那么右边这一段的长就是F[k]-1-(F[k-1]-1)-1(这个是mid)=F[k-2]-1

线性索引查找

索引就是把一个关键字与它对应的记录相关联的过程。索引按照结构可以分为线性索引、树形索引和多级索引。重点介绍三种线性索引:稠密索引,分块索引和倒排索引。

稠密索引:在线性索引中,将数据集中的每个记录对应一个索引项(索引项一定是按照关键码有序的排列)。

分块索引:将数据分为若干块,且块内无序,块间有序。比如索引项结构分为三个数据项,最大关键码,记录个数,指向块首元 

                  素的指针。找到所在块后在块内顺序查找。

倒排索引:索引项通用结构是次关键码记录号表,其中记录号表存储具有相同次关键字的所有记录的记录号。

二叉排序树

若它的左子树不为空,则左子树上的所有结点的值均小于它的根结构的值;若它的右子树不空,则右子树上的所有结点的值均大于它的根结点的值;它的左右子树也分别为二叉排序树。

二叉排序树的查找与其深度有关,我们希望对一个集合按二叉排序树查找,最好是把它构建成一棵平衡的二叉排序树。

平衡二叉树

平衡二叉树是一种二叉排序树,其中每个节点的左子树和右子树的高度差至多等于1。

距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称之为最小不平衡子树。

AVL解决步骤:在二叉排序树创建过程中保证它的平衡性,当最小不平衡子树根结点的平衡因子BF是大于1时,就右旋,小于-1就左旋。而插入结点后,最小不平衡字数的BF与它的子树的BF符号相反时,就需要对结点先进行一次旋转以使得符号相同后,再反向旋转一次才能够完成平衡操作。

多路查找树

a.2-3树:其中的每一个结点都具有两个孩子或三个孩子或没有孩子,并且叶子都在同一层次上。(插入删除情况巨多,有规律)

               2结点包含一个元素和两个孩子或没有孩子。

               3结点包含一小一大元素和三个孩子或没有孩子。

b.2-3-4树  多了一个4结点,包含小中大三个元素和四个孩子或没有孩子。

c.B树:是一种平衡的多路查找树,2-3树和2-3-4树是B树的特例。结点最大的孩子数目称为B树的阶。

d.B+树:B树的变形,将分支结点上的元素当作它们在该分支结点位置的中序后继者(叶子结点)中再次列出,每一个叶子结点都会保存一个指向后一叶子结点的指针。

为什么说B+tree比B树更适合实际应用中操作系统的文件索引和数据库索引?

   1.B+tree的磁盘读写代价更低
        B+tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
        举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B+树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B树就比B+树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。
   2.B+tree的查询效率更加稳定
        由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

散列表查找(哈希表)概述

散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。这里我们把f称为散列函数,又称哈希函数。采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表。

散列技术既是一种存储方法,又是一种查找方法。即先计算出存储地址,然后把记录存储到该地址。

散列函数的构造方法

1.直接定址法:f\left ( key \right )=a\times key+b(a,b为常数)

2.数字分析法:抽取关键字中的若干位或其组合作哈希地址。

3.平方取中法:以关键字的平方值中间几位作为存储地址。

4.折叠法

5.除留余数法:f(key)=key mod p  (p\leqslantm) 通常散列表表长为m,p为小于或等于表长的最大质数或不包含小于20质因子的合数。

6.随机数法

处理散列冲突的方法

1,开放定址法:f_i(key)=(f(key)+d_i)  mod m。即冲突了我再换个空位置存。

              a.线性探测法:d_i=1,2,3,...,m-1。

              b.二次探测法:d_i=1^2,-1^2,2^2,-2^2,...,\pm k^2\left ( k\leqslant \frac{m}{2} \right )

              c.伪随机探测法:d_i为一随机序列。

2.再散列函数:就是冲突了就换个函数算地址。

3.链地址法:冲突了就挤一下,将这些记录存在单链表中,并用一维数组存放头指针。

4.公共溢出区法

散列表查找性能分析:散列函数是否均匀;处理冲突的方法;散列表的装填因子。

排序

排序的稳定性:就是如果两个值相等,它们排序前领先排序后还是领先,就稳定,否则不稳定。

内部排序:全部数据可同时放入内存进行的排序。整个排序需要多次在内外存之间交换数据才能进行。

外部排序:文件中数据太多,无法全部调入内存进行的排序。

冒泡排序算法

初级版:就是先找第一大/小的,然后再找第二大小的,依次排序。(与简单选择排序的差别在于交换次数)

void Bubblesort0(Sqlist *L)
{
    for(int i=1;ilength;i++)
    {
        for(int j=i+1;jlength;j++)
        {
            if(L->r[i]>L->r[j]);
            swap(L,i,j);
        }
    }
}

正经版:核心思路一致,就逐个找最小的,不同的是第二个循环里,从后往前循环,相邻两位进行比较,让较小的数字逐渐冒上来,而不会像初级版那样有被替换到最底下的可能。

for(int j=L->length-1;j>=i;j--)
{
    if(L->r[j]>L->r[j+1])
    swap(L,i,j);
}

进阶版:增加一个标记变量flag来标记是否有数据交换,来避免因已经有序的情况下无意义循环判断。有数据交换就是要排序,就为true,否则就为false。

简单选择排序

基本思想:每一趟在n-i+1个记录种选择关键字最小的记录作为有序序列的第i个记录。

void selectsort(SqList *L)
{
    int i,j,min;
    for(i=1;ilength;i++)
    {
        min=i;
        for(j=i+1;j<=L->length;j++)
        {
            if(L->r[min]>L->r[j])
            min=j;
        }
        if(i!=min)
        swap(L,i,min);
    }
}

直接插入排序

基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。

void InsertSort(SqList *L)
{
    int i,j;
    for(i=2;i<=L->length;i++)
    {//这里第一次自己写成小于了
        if(L->r[i]r[i-1])
        {//这个括号第一次自己写时忘加了!!!
            L->r[0]=L->r[i];
            for(j=i-1;L->r[j]>L->r[0];j--)
            {
                L->r[j+1]=L->r[j];
            }
            L->r[j+1]=L->r[0];
        }
    }
}

希尔排序

基本有序——小的关键字基本在前面,不大不小的基本在中间,大的基本在后面,比如{2,1,3,6,4,7,5,8,9}

跳跃分割:将相距某个增量的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。

希尔排序算法:

不稳定,采用跳跃分割,在子序列中进行直接插入排序,然后减小增量,不断减小,直至增量为1,由于已经基本有序,所以需要移动的情况大大减少,再完成最后一次循环即结束。

void ShellSort(SqList *L)
{
    int i,j;
    int increment=L->length;
    do
    {
        increment=increment/3+1;
        for(i=increment+1;i<=L->length;i++)
        {
            if(L->r[i]r[0]=L->r[i];//暂存在L->r[0]
                for(j=i-increment;L->r[0]r[j];j-=increment)
                {
                    L->r[j+increment]=L->r[j]//记录后移,查找插入位置
                }
                L->r[j+increment]=L->r[0];//插入
            }
        }
    }while(increment>1);
}

堆排序

堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者都小于或等于称为小顶堆。

先由一个无序序列构建成一个大顶堆或小顶堆,再输出堆顶元素后调整为一个大顶堆/小顶堆。

适合待排序序列个数较多的情况。

堆排序:

堆调整:

归并排序

归并排序需要与原始记录序列同样数量的存储空间存放归并结果以及递归时深度为Log2N的栈空间。

 

非递归实现归并排序 从最小的序列开始归并直至完成。

 

快速排序

快速排序的基本思想:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录进行排序,以达到整个序列有序的目的。

找枢轴:大致是将比它小的换到它的左边,比它大的换到它的右边,这样不断向中间逼近,直至二者相等,即找到中位数枢轴。

算法如下:

void QuickSort(SqList *L)//排序主函数
{
    QSort(L,1,L->length);
}

void QSort(SqList *L,int low,int high)
{//封装递归调用的排序函数
    int pivot;
    if(lowr[low];
    while(lowr[high]>=pivotkey)
            high--;
        swap(L,low,high);//将比枢轴小的放入低端
        while(lowr[low]<=pivotkey)
            low++;
        swap(L,low,high);//将比枢轴大的放入高端
    }
    return low;
}

那么我们会发现枢轴的选取对于算法复杂度影响很大,因此提出三种改进,即选取枢轴的方法:

1.n数取中法(优先选择枢轴)

2.优化不必要的交换   就把多留一个位置,把pivot存起来,然后每次会有一个空位空出来,给数交换,就不需要用swap了

3.优化小数组时的排序方案

4.优化递归操作:对QSort实施尾递归优化

因为low第一次递归后就没用了,因此可以low=pivot+1,if替换为while,这样增加了迭代的次数,减小了递归。

 

你可能感兴趣的:(大话数据结构)