数据结构笔记

目录

//*****03 算法效率***************//

//***************04 数据结构********//

//*************05 算法***********//

//*********11 线性结构****************//

//**********16 广义表与多重链表********************//

//**********17 堆栈*****************//

//***************21 队列********************//

//**************27 树**************************//

//*********** 32 二叉树********************//

//****************38 二叉树表示**************************//

//*********40 二叉搜索树**********//

//********42 平衡二叉树*******************//

//********51 堆*********************//

//********55 哈夫曼树与哈夫曼编码**********//

//*******65 图*******************//

//********68 图的遍历**********//

//********80 最短路径***********************//

//***************97 Prim算法**************//

//**********101 排序*********//

//*********123 散列(哈希)*********************//

//************125 散列函数的构造方法************//


//*****03 算法效率***************//

(1)解决问题方法的效率,跟算法的巧妙程度有关
(2)数据存放的方式有关

//***************04 数据结构********//


1、数据结构
(1)数据对象在计算机中的组织方式
    逻辑结构(线性,图论)+物理存储结构(数组、链表)
(2)数据对象必定与一系列加在其上的操作相关联
(3)完成这些操作所用的方法就是算法
2、抽象数据类型
(1)数据类型
    a、数据对象集
    b、数据集合相关联的操作集
(2)抽象
    a、与存放数据的机器无关
    b、与数据存放的物理结构无关
    c、与实现操作的算法和编程语言均无关
只描述数据对象集和相关操作集“是什么”,并不涉及“如何做到”的问题
(3)例子---“矩阵”的抽象数据类型定义

//*************05 算法***********//


1、算法(Alogorithm)
    (1)一个有限指令集;
    (2)接受一些输入(有些情况不需要输入);
    (3)产生输出;
    (4)一定在有限步骤之后终止;
    (5)每一条指令必须:有充分的明确的目标,不可以有歧义;计算机
能处理范围之内;描述应不依赖与任何一种计算机语言以及具体的实现手段;
2、好的算法---指标
(1)空间复杂度S(n)---根据算法写成的程序在执行时占用存储单位的长度
    这个长度往往与输入数据的规模有关,空间复杂过高的算法可能
导致使用的内存超限,造成程序非正常中断
(2)时间复杂度T(n)---根据算法写成的程序在执行时耗费时间的长度
    这个长度往往也与输入数据的规模有关。
3、复杂度的渐进表示法
    2^N>N^3>N^2>NlogN>N>logN

//*********11 线性结构****************//


1、线性表
    线性表(Linear List)由同类型数据元素构成有序序列的线性结构
(1)表中元素个数称为线性表的长度
(2)线性表没有元素时,称为空表
(3)表起始位置称表头,表结束位置称表尾
2、线性表的抽象数据类型描述
(1)类型名称:线性表(List)
(2)数据对象集:线性表是n(>=0)个元素构成的有序序列(a1,a2,a3,……,an)
(3)操作集:线性表L《List,整数i表示位置,元素X《ElementTyoe,线性表基本操作主要有:
    初始化、查找元素、插入元素、删除、返回线性表L的长度n
3、线性表的顺序存储实现
    利用数组的连续存储空间顺序存放线性表的各元素
typedef struct LNode *List;
struct LNode{
    ElementType Data[MAXSIZE];
    int Last;
};
struct LNode L;
List PtrL;
访问下标为i的元素:L.Data[i]或PtrL->Data[i]
线性表的长度:L.Last+1或PtrL->Last+1

4、线性表的链式存储实现
    不要求逻辑上相邻的两个元素物理上也相邻,通过“链”建立起数据元素之间的逻辑关系
插入、删除不需要移动数据元素,只需要修改“链”
typedef struct LNode *List;
struct LNode{
    ElementType Data;
    List Next;
};
struct LNode L;
List PtrL;

5、链表删除结点
(1)先找到链表的第i-1个结点,用P指向;
(2)再用指针s指向要被删除的结点(p的下一个结点);
(3)然后修改指针,删除s所指结点;
(4)最后释放s所指向结点空间(free);

//**********16 广义表与多重链表********************//


1、广义表
(1)广义表是线性表的推广
(2)对于线性表而言,n个元素都是基本的单元素
(3)广义表中,这些元素不仅可以使单元素,也可以是另一个广义表
typedef struct GNode *GList;
struct GNode
{
    int Tag;
    //标志域:0表示结点,通过Tag来区分后面union是Data还是子表指针
    union{    //子表指针域Sublist与单元数据域Data复用,即共用存储空间
        ElementType Data;
        GList SubList;
    }URegion;
    GList Next;    //指向后继结点
};
2、多重链表
(1)多重链表---链表中的结点可能同时隶属于多个链
    多重链表中结点的指针域会有多个,如前面例子包含了Next和SubList两个指针域
    担保函两个指针域的链表并不一定是多重链表,比如在双向链表不是多重链表
(2)多重链表有广泛用途:
    基本如树、图这样相对复杂的数据结构都可以采用多重链表方式实现存储
(3)例如:对于稀疏矩阵
    采用一种典型的多重链表---十字链表来存储稀疏矩阵
    a、只存储矩阵非0元素项,
    结点的数据域:行坐标Row、列坐标Col、数值Value
    b、每个结点通过两个指针域,把同行、同列串起来
    行指针(或称为向右指针)Right
    列指针(或称为向下指针)Down    

//**********17 堆栈*****************//


1、堆栈的抽象数据类型描述
    堆栈(Stack):具有一定操作约束的线性表
    只在一端(栈顶,Top)做插入、删除
(1)插入数据:入栈(Push)
(2)删除数据:出栈(Pop)
(3)后入先出:Last In First Out(LIFO)
2、堆栈的顺序存储实现
    栈的顺序存储结构通常由一个一维数组和一个记录栈顶元素位置的变量组成
//整个堆栈是由下面的一个结构体构成的
#define MaxSize<储存数据元素的最大个数>
typedef struct SNode *Stack;
struct SNode{
    ElementType Data[MaxSize];
    int Top;//栈顶的元素的数组下标//top默认为-1为空
};
3、堆栈的链式存储实现
    栈的链式存储结构实际上就是一个单链表,叫做链栈。插入和删除操作只能在链栈的栈顶进行
栈顶指针Top应该在链表的表头
typedef struct SNode *Stack;
struct SNode{
    ElementType Data;
    struct SNode *Next;
};
4、堆栈应用---表达式求值
    应用堆栈实现后缀表达式求值的基本过程:
从左到右读入后缀表达式的各项(运算符或运算数)
(1)运算数:入栈
(2)运算符:从堆栈中弹出适当数量的运算数,计算并将结果入栈
(3)最后,堆栈顶上的元素就是表达式的结果值
    中缀表达式求值----需要先转换为后缀表达式,然后求值
5、中缀表达式--->后缀表达式
a、从头到尾读取中缀表达式的每个对象,对不同对象按不同情况处理
(1)运算数:直接输出
(2)运算符:压入堆栈
(3)右括号:将栈顶的运算符弹出并输出,知道遇到左括号(出栈,不输出)
(4)运算符:
    若优先级大于栈顶运算符时,则把它压栈
    若优先级小于等于栈顶运算符时,将栈顶运算符弹出并输出;在比较新的栈顶运算符,
直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈
(5)若个对象处理完毕,则把堆栈中存留的运算符一并输出
6、堆栈的其他应用
    函数调用及递归实现---深度优先搜索---回溯算法

//***************21 队列********************//


1、队列(Queue):具有一定操作约束的线性表
    插入和删除操作:只能在一端插入,而在另一端删除
(1)数据插入:入队列(AddQ)
(2)数据删除:出队列(DeleteQ)
    先来先服务,先进先出FIFO
2、队列的顺序存储实现
    队列的顺序存储结构通常由一个一维数组和一个记录队列头元素位置的
变量front以及一个记录列尾元素位置的变量rear组成。
#define MaxSize //<存储数据元素的最大个数>
struct QNode{
    ElementType Data[MaxSize];
    int  rear;//刚开始时,rear\front均指向-1
    int front;//右侧增加,左侧删除
};
typedef struct QNode *Queue;
//队列中,front做删除出队列,rear做插入入队列
3、队列的链式存储
    队列的链式存储结构也可以用一个单链表实现,插入和删除操作分别在链表的两头进行,
队列指针front和rear分别在链表头和尾

//**************27 树**************************//


1、查找(Searching)
    根据某个给定关键字K,从集合R中找到关键字与K相同的记录
(1)静态查找:集合中记录是固定的
    没有插入和删除操作,只有查找
(2)动态查找:集合中记录是动态变化的
    除查找,还可能发生插入和删除
2、树的定义
    树:n(N>=0)个结点构成的有限集合
当n=0时,称为空树;对于任一棵非空树(n>0),具备以下性质:
(1)树有一个称为“根(Root)”的特殊结点,用r表示
(2)其余结点可分为m(m>0)个互不相交的有限集T1,T2,……Tm,其中每个集合本身又是一棵树,称为原来数的“子树(SubTree)”
重点:(1)子树是不相交的;
    (2)除了根结点外,每个结点有且仅有一个父结点;
    (3)一棵N个结点的树有N-1条边
3、树的基本术语
(1)结点的度(Degree):结点的子树个数
(2)树的度:树的所有结点中最大的度数
(3)叶结点(Leaf):度为0的结点
(4)父结点(Parent):有子树的结点是其子树的根节点的父结点
(5)子结点(Child):若A结点是B结点的父结点,则称B结点是A结点的子结点;子结点也称孩子结点
(6)兄弟结点(Sibling):具有同一父结点的各结点彼此是兄弟结点
(7)路径和路径长度:从节点n1到nk的路径为一个结点序列n1,n2,……nk,ni是ni+1的父结点,路径所包含边的个数为路径的长度
(8)祖先结点(Ancestor):沿树根到某个节点路径上的所有结点都是这个结点的祖先结点
(9)子孙节点(Descendant):某一个结点的子树中的所有结点是这个结点的子孙。
(10)结点的层次(Level):规定根结点在第一层,其他任一结点的层数是其父结点的层数加1
(11)树的深度(Depth):树中所有结点中的最大层次是这棵树的深度


//*********** 32 二叉树********************//


1、二叉树---度为2,
    每个结点统一两个指针域:左边指向第一个子结点,右边指向兄弟结点
2、二叉树T的定义:
(1)一个有穷的结点集合。
    这个集合可以为空,若不为空,则它是由根结点和称为其左子树TL和右子树TR的两个不相交的二叉树组成
(2)二叉树具体五种基本形态
    空;根结点;左之;右子;双子
(3)二叉树的子树有左右顺序之分
3、特殊二叉树
(1)斜二叉树
    仅仅指向以便
(2)完美二叉树(满二叉树)
(3)完全二叉树,
    有n个结点的二叉树,对树中结点按从上至下、从左到右顺序进行编号,编号为i
(1=<i<=n)结点与满二叉树中编号为i的结点在二叉树的位置相同,(后面可以,但有的一定是满的)
4、二叉树的几个重要性质
(1)一个二叉树第i层的最大结点数为:2的i-1次方,i>=1
(2)深度为K的二叉树有最大结点总数为:2的k次方-1,k>=1
(3)对任何非空二叉树T,若n0表示叶结点的个数、n2是度为2的非叶结点个数,
那么两者满足关系n0=n2+1;
5、二叉树的顺序存储结构
(1)完全二叉树:按从上到下、从左到右顺序存储n个结点的完全二叉树的结点父子关系
    a、非根结点(序号i>1)的父结点的序号是[i/2]
    b、结点(序号为i)的左孩子结点的序号是2i,(2i<=n,否则没有左孩子)
    c、结点(序号为i)的右孩子结点的序号是2i+1,(2i+1<=n,否则没有右孩子)
(2)一般二叉树也可以采用这种结构,(通过补全为完全二叉树)但会造成空间浪费……
6、二叉树的链表存储---两个指针域分别指向左儿子、右儿子,NULL
    typedef struct TreeNode *BinTree;
    typedef BinTree Position;
    struct TreeNode{
        ElementType Data;
        BinTree Left;
        BinTree Right;
    };

7、中序遍历非递归遍历算法---堆栈
(1)遇到一个结点,就把它压栈,并去遍历它的左子树;
(2)当左子树遍历结束后,从栈顶弹出这个结点并访问它;
(3)然后按其右指针再去中序遍历该结点的右子树。
8、使用队列按层序遍历
    先根结点入队,然后:
(1)从队列中取出一个元素;
(2)访问该元素所指结点;
(3)若该元素所指结点的左、右孩子结点非空,则将其左、右孩子的指针循序入队

!!必须有中序遍历才可以两种遍历序列确定二叉树

//****************38 二叉树表示**************************//


1、二叉树表示
    结构数组表示二叉树:静态链表
#define MaxTree 10
#define ElementType char
#define Tree int
#define Null -1

struct TreeNode
{
    ElementType Element;
    Tree Left;
    Tree Right;
}T1[MaxTree],T2[MaxTree];

//*********40 二叉搜索树**********//


1、查找问题
(1)静态查找---二分查找----复杂度降为logn(实现排序好才可以)---二分本身就是二叉树的搜索结构
(2)动态查找
2、二叉搜索树(BST,Binary Seaarch Tree)----二叉排序树、二叉查找树
二叉搜索树:一棵二叉树,可以为空;如果不为空,满足一下性质:
(1)非空左子树的所有键值小于其根结点的键值。
(2)非空右子树的所有键值大于其根结点的键值。
(3)左、右子树都是二叉搜索树。
3、二叉搜索树的插入
!!关键:找到元素应该插入的位置!!所以要采用递归
4、二叉搜索树的删除
(1)删除的是叶结点:直接删除,并再修改其父结点指针----置为NULL
(2)删除的结点只有一个孩子结点:将其父结点的指针指向要删除结点的孩子结点
(3)删除的结点有左、右两棵子树:
    用另一节点替代被删除的结点:有子树的最小元素或者左子树的最大元素

//********42 平衡二叉树*******************//


1、平衡因子(Balance Factor,简称BF):BT(T)=hL-hR,其中hL和hR分别为T的左右子树的高度
2、平衡二叉树(Balance Binary Tree)(AVL树)
    空树,或者!!任一结点!!左、右子树高度差的绝对值不超过1,即|BF(T)|<=1

//********51 堆*********************//


1、优先队列(Priority Queue):特殊的“队列”,取出元素的顺序是按照元素的优先权(关键字)大小,而不是元素进入队列的先后顺序
2、数组实现优先队列
(1)插入---元素总是插入尾部---O(1)
(2)删除---查找最大(最小)关键字---O(n);从数组中删去需要移动元素---O(n)
3、链表实现优先队列
(1)插入---元素总是插入链表的头部---O(1)
(2)删除---查找最大(最小)关键字---O(n);删去结点---O(1)
4、有序数组
(1)插入---找到合适的位置---O(n)或O(log2n);移动元素并插入---O(n)
(2)删除---删去最后一个元素---O(1)
5、有序链表
(1)插入---找到合适位置---O(n);插入元素---O(n)
(2)删除---删除首元素或最后元素---O(1)
6、二叉搜索树,一直删最大最小值,就会造成数不平衡,复杂度上升,平衡数复杂度为log2n
7、优先队列的完全二叉树表示---堆的两个特性
(1)结构性:用数组表示的完全二叉树
(2)有序性:任一结点的关键字是其子树所有结点的最大值(最小值)
    a、“最大堆(MaxHeap)”,也称“大顶堆”,最大值
    b、“最小堆(MinHeap)”,也称“小顶堆”,最小值
注意:从根结点到任一结点路径上结点序列的有序性
8、最大堆的建立
(1)建立最大堆:将已经存在的N个元素按最大堆的要求存放在一个一维数组中
(2)方法一:通过插入操作,将N个元素一个个相继插入到一个初始为空的堆中去,其时间代价最大为O(NlogN)
(3)方法二:在线性时间复杂度下建立最大堆
    a、将N个元素按输入顺序存入,先满足完全二叉树的结构特性
    b、调整各结点位置,以满足最大堆的有序特性

//********55 哈夫曼树与哈夫曼编码**********//


1、哈夫曼树
(1)带权路径长度(WPL):设二叉树有n个叶子结点,每个叶子结点带有权值Wk,从根结点到每个叶子结点的长度为Lk,
则每个叶子结点的带权路径长度之和就是WPL
(2)最优二叉树或哈夫曼树:WPL最小的二叉树
2、哈夫曼树的特点
(1)没有度为1的结点
(2)n个叶子结点的哈夫曼树共有2n-1个结点
(3)哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树
(4)对同一组权值{W1,W2,……,Wn},可能存在不同构的哈夫曼树
3、哈夫曼编码
(1)前缀码prefix code:任何字符的编码都不是另一字符编码的前缀
    可以无二义地解码


//*******65 图*******************//


1、图(Ggrah)
(1)表示“多对多”的关系
(2)包含:
    a、一组顶点:通常用V(Vertx)表示顶点集合
    b、一组边:通常用E(Edge)表示边的集合
    c、边是顶点对:(v,w)《E,其中v,w《V
    d、有向边<v,w>表示从v指向w的边(单行线)
2、图的抽象数据类型定义
(1)类型名称:图(Graph)
(2)数据对象集:G(V,E)由一个非空的有限顶点集合V和一个有限边集合E组成
(3)操作集:
3、图的表示---邻接矩阵的好处
(1)直观、简单、好理解
(2)方便检查任意一对顶点间是否存在边
(3)方便找任一顶点的所有“邻接点”(有边直接相连的顶点)
(4)方便计算任一顶点的“度”,(从该点发出的边数为“出度”,指向该点的边数为“入度”)
    a、无向图:对应行(或列)非0元素的个数
    b、有向图:对应行非0元素的个数是“出度”,对应列非0元素的个数是“入度”
4、图的表示---邻接的坏处
(1)浪费空间
(2)浪费时间
5、图的表示---邻接表
(1)邻接表:G(N)为指针数组,对应矩阵每行一个链表,只存非0元素
    一条边同样需要存两次,而且需要存指针域,因此一定要很稀疏才划算
(2)邻接表---好处
    a、方便找任一顶点的所有“邻接点”
    b、节约稀疏图的空间---需要N个头指针+2E个结点(每个结点至少2个域)
    c、方便计算任一顶点的度?对无向图是的;对有向图,只能计算“出度”,
需要构造“逆邻接表”(存指向自己的边)来方便计算“入度”


//********68 图的遍历**********//


1、堆栈---DFS---深度优先搜索(Depth First Search)----类似树的先序遍历---堆栈
void DFS(Vertex V)
{visited[V]=true;
for(V的每个邻接点W)
     if(!visited[W])
         DFS(W);    
}
2、队列---BFS---广度优先搜索(Breadth First Search,BFS)----类似数的层序遍历---队列
void BFS(Vertx V)
{
    visted[V]=true;
    Enqueue(V,Q);
    while(!IsEmpty(Q))
    {
    V=Dequeue(Q);
    for(V的每个邻接点W)
    if(!visted[W])
    {
        visited[W]=true;
        Enqueue(W,Q);    
    }
    }
}
3、若有N个顶点,E条边,时间复杂度是
(1)用邻接表存储图,有O(N+E)
(2)用邻接矩阵存储图,有O(N2)
4、图不连通怎么办?
(1)如果从v到w存在一条(无向)路径,则称v和w是连通的
(2)路径:v到w的路径是一系列顶点{V,v1,v2,……,vn,W}的集合,其中任一对相邻的顶点间都有图中的边。
路径的长度是路径中的边数(如果带权,则是所有边的权重和)。如果v到w之间的所有顶点都不同,则称简单路径
(3)回路:起点等于终点的路径
(4)连通图:图中任意两顶点均连通
(5)连通分量:无向图的极大连通子图
    a、极大顶点数:再加1个顶点就不连通了
    b、极大边数:包含子图中所有顶点相连的所有边
(6)强连通:有向图中顶点v和w之间存在双向路径,则称v和w是强连通的
(7)强连通图:有向图中任意两顶点均强连通
(8)强连通分量:有向图的极大强连通子图


//********80 最短路径***********************//


1、最短路径问题的抽象
在网络中,求两个不同顶点之间的所有路径中,边的权值之和最小的那一条路径
(1)这条路径就是两点之间的最短路径(Shortest Path)
(2)第一个顶点为源点(Source)
(3)最后一个顶点为终点(Destination)
2、问题分类
(1)单源最短路径问题:从某固定源点出发,求其到所有其他顶点的最短路径
(2)多源最短路径问题:求任意两顶点间的最短路径
3、无权图的单源最短路算法
    dist[w]=s到w的最短距离
    dist[S]=0
    path[W]=s到w的路上经过的某顶点
注意:采用BFS广度优先搜索算法类似,采用队列
void Unweighted(Vertex S)
{
    Enqueue(S,Q);
    while(!IsEmpty(Q))
    {
    V=Dequeue(Q);
    for(V的每个邻接点W)
    if(dist[W]==1)
    {
    dist[W]=dist[V]+1;
    path[W]=V;
    Enqueue(W,Q)
    }
    }
}
整体算法的复杂度为V+E
4、有权图的单源最短路算法


//***************97 Prim算法**************//


1、最小生成树(Minimum Spanning Tree)
(1)树---无回路---|V|个顶点一定有|V|-1条边
(2)生成树---包含全部顶点---|V|-1条边都在图里
(3)边的权重和最小
    向生成树中任加一条边都一定构成回路
2、贪心算法
(1)“贪”:每一步都要最好的
(2)“好”:权重最小的边
(3)需要约束:只能用图里有的边;只能正好用掉|V|-1条边;不能有回路
3、Prim算法---让一棵小树长大
4、Kruskal算法---将森林合并成树

//**********99 拓扑排序***********//
1、拓扑序
(1)如果图中从v到w有一条有向路径,则v一定排在w之前,满足此条件的顶点序列称为一个拓扑序
(2)获得一个拓扑序的过程就是拓扑排序
(3)AOV(Activity On Vertex ),活动表示点在顶点;如果有合理的拓扑序,则必定是有向无环图(Directed Acyclic Graph,DAG)
void TopSort()
{for(cnt=0;cnt<|V|;cnt++)
{
    V=未输入的入度为0的顶点;
    if(这样的V不存在)
    {
    Error(“图中有回路”);
    break;
    }
    输出V,或者记录V的输出序号;
    for(V的每个邻接点W)
    Indegree[W]--;
}
}
2、关键路径问题---由绝对不允许延误的活动组成的路径
(1)AOE(Activity On Edge)网络---活动表示在边上,顶点表示活动结束

//**********101 排序*********//


1、前提
(1)void X_Sort(ElementType A[],int N)
    a、大多数情况下,为简单起见,讨论从小到的整数排序
    b、N是正整数---只基于比较的排序(<>=有定义)---内部排序---稳定性(任意两个相等的数据,排序前后的相对位置不发生改变)
    c、没有一种排序是任何情况下都表现最好的
2、冒泡排序
3、插入排序
4、时间复杂度下界
    a、逆序对
    (1)对于下标i<j,如果A[i]<A[j],则称(i,j)是一对逆序对(inversion)
    (2)交换2个相邻元素正好消去1个逆序对
    (3)插入排序:T(N,I)=O(N+I)
    如果序列基本有序,则插入排序简单且高效
    b、时间复杂度下界
    (1)定理:
        任意N个不同元素组成的序列平均具有N(N-1)/4个逆序对
    (2)定理:
        任何仅以交换相邻两元素来排序的算法,其平均时间复杂度为O(下限)(N2)
    (3)要提高算法效率
        每次消去不止1个逆序对---每次交换相隔较远的2个元素
5、希尔排序
(1)定义增量序列Dm>Dm-1>……>D1=1
(2)对每个Dk进行“Dk-间隔”排序(K=M,M-1,...,1)
注意:“Dk-间隔”有序的序列,在执行“Dk-1间隔”排序后,仍然是“Dk-间隔”有序的
(3)更多的增量排序序列
6、选择排序
    在于快速找到最小元
7、堆排序
8、归并排序---有序子列的归并
    (1)递归(分而治之)---(2)非递归
9、快速排序---分而治之
(1)选主元---取头、中、尾的中位数,将中位数放在倒数第二位
(2)子集划分
当遇到主元相等,宁愿进行交换,二分中间位置,复杂度为NlogN,且稳定;如果不交换,将趋近于N2的复杂度,且不稳定
10、小规模数据的处理
(1)快速排序的问题
    用递归;对小规模的数据(例如N不到100)可能还不如插入排序快
(2)解决方案
a、当递归的数据规模充分小,则停止递归,直接调用简单排序(例如插入排序)
b、在程序中定义一个cutoff的阈值,
11、表排序---间接排序
    定义一个指针数组作为“表”(table)
12、物理排序
    N个数字的排列由若干个独立的环组成
(1)判断一个环的结束
    if(table[i]==i)
(2)复杂度分析
最好情况---初始即有序
最坏情况---有[N/2]个环,每个环包含2个元素;需要[3N/2]次元素移动,
T=O(mN),m是每个A元素的复制时间
13、桶排序
    建立一个指针数组count[],每个指针指向一个空链表的头指针
void Bucket_Sort(ElementType A[],int N)
{
    count[]初始化;
    while(读入1个学生成绩grade)
    将该生插入count[grade]链表;
    for(i=0;i<M;i++)
    输出整个count[i]链表;
}
14、基数排序
假设N=10个整数,每个整数的值在0到999之间(于是有M=1000个不同的值)
输入序列:64,8,216,512,27,729,0,1,343,125
用LSD“次位优先”(Least Significant Digit)
复杂度T=O(P(N+B))
15、多关键字排序---扑克牌中花色,面值---
用MSD“主位优先”(Most Significant Digit)排序
    (1)为花色建4个桶;(2)在每个桶内分别排序(这里还是需要额外的排序算法),最后合并结果
用LSD“次位优先”(Least Significant Digit)排序:
    (1)为面值建13个桶(2)将结果合并,然后再为花色建4个桶

//*********123 散列(哈希)*********************//


1、查找的本质---已知对象找位置
(1)有序安排对象:全序、半序
(2)直接“算出”对象位置:散列
2、散列查找法的两项基本工作:
(1)计算位置:构造散列函数确定关键词存储位置
(2)解决冲突:应用某种策略解决多个关键词位置相同的问题
3、时间复杂度几乎是常量:O(1),即超找时间与问题规模无关!
4、“散列”Hashing基本思想
(1)以关键字Key为自变量,通过一个确定的函数h(散列函数),计算出对应的函数值h(Key),作为数据对象的存储地址
(2)可能不同的关键字会映射到同一个散列地址上,即h(keyi)=h(keyj)(当keyi!=keyj),称为“冲突(Collision)”,需要某种冲突解决策略

//************125 散列函数的构造方法************//


1、“好”的散列函数一般考虑下列因素
(1)计算简单,以便提高转换速度
(2)关键词对应的地址空间分布均匀,以尽量减少冲突
2、数字关键词的散列函数构造
(1)直接定址法
    取关键词的某个线性函数值为散列地址,即h(key)=a*key+b,(a,b为常数)
(2)除留余数法
    散列函数为:h(key)=key mod p
p=Tablesize=17,一般,p取素数
(3)数值分析法
    分析数字关键字在各位上的变化情况,取比较随机的位作为散列地址
比如:取11位手机号码key的后4位作为地址---散列函数为:h(key)=atoi(key+7)
(4)折叠法
    把关键词分割成位数相同的几个部分,然后叠加
(5)平方取中法
3、字符关键词的散列函数构造
(1)一个简单的散列函数---ASCII码加和法
    对字符型关键词key定义散列函数如下:
    h(key)=(Ekey[i])mod TableSize---特别容易出现冲突
(2)简单的改进---前3个字符移位法
    h(key)=(key[0]*27^2+key[1]*27+key[2])mod TableSize---空间浪费
(3)好的散列函数---移位法(左移5位相当于乘以32)
    h(key)={key[n-i-1]*32^i}mod TableSize
4、处理冲突的方法
(1)换个位置:开放地址法
(2)同一位置的冲突对象组织在一起:链地址法
5、开放地址法(Open Addressing)
    一旦产生了冲突(该地址已经有其他元素),就按某种规则去寻找另一空地址
(1)若发生了第i次冲突,试探的下一个地址将增加di,
    hi(key)=(h(key)+di)mod TableSize (1<=i<TableSize)
(2)di决定了不同的解决冲突方案:线性探测(di=i)、平方探测(di=+-i^2)(万一超出,则取余操作)、双散列(di=i*h2(key))
6、线性探测
    注意“聚集”现象
7、散列表查找性能分析
(1)成功平均查找长度(ASLs)---查找表中关键词的平均查找比较次数(其冲突次数加1)
(2)不成功平均查找长度(ASLu)---不在散列表中的关键词的平均查找次数(不成功)---查找空位为止的查找次数
8、平方探测法(Quadratic Probing)---二次探测
(1)以增量序列1^2,-1^2,2^2,……,q^2,-q^2且q<=|TableSize/2|循环试探下一个存储地址
    TableSize为散列表表长
(2)注意会一直有空位,一直跳来跳去找不到
(3)有定理显示:如果散列表长度TableSize是某个4K+3(k是正整数)形式的素数时,平方探测法就可以探查到整个散列表空间
9、双散列探测法(Double Hashing)
(1)双散列探测法:
    di为i*h2(key),h2(key)是另一个散列函数探测序列成:h2(key),2h2(key),3h2(key),……
(2)对任意的key,h2(key)!=0
(3)探测序列还应该保证所有的散列存储单元都应该能够被探测到,选择以下形式有良好的效果:
    h2(key)=p-(key mod p)
其中:p<TableSize,p、TableSize都是素数
10、再散列(Rehashing)
(1)当散列元素太多(即装填因子太大)查找效率会下降;
    实用最大装填因子一般取[0.5,0.85]
(2)当装填因子过大时,解决方法是加大扩大散列表,这个过程叫“再散列”
11、分离链接法(Separate Chaining)
(1)分离链接法:将相应位置上冲突的所有关键词存储在同一个单链表中
(2)不放元素,放链表指针
struct HashTbl
{
    int TableSize;
    List TheLists;
}*H;
12、散列表的性能分析
(1)平均查找长度(ASL)用来度量散列表查找效率:成功、不成功
(2)关键词的比较次数,取决于产生冲突的多少
(3)影响产生冲突的三个因素
    a、散列函数是否均匀
    b、处理冲突的方法
    c、散列表的装填因子
(4)分析不同冲突处理方法、装填因子对效率的影响
(5)选择合适的h(key),散列法的查找效率期望是常数O(I),它几乎与关键字的空间的大小n无关,也适合于关键字直接比较计算量大的问题
(6)以较小的装填因子为前提,因此散列方法是一个以空间换时间
(7)散列方法的存储对关键字是随机的,不便于顺序查找关键字,也不适合于范围查找,或最大值最小值查找
13、开放地址法---性能分析
(1)散列表是一个数组,存储效率高,随机查找
(2)散列表有“聚集”现象
14、分离链法
(1)散列表是顺序存储和链式存储的结合,链表部分的存储效率和查找效率都比较低
(2)关键字删除不需要“懒惰删除”法,从而没有存储“垃圾”
(3)太小的装填因子可能导致空间浪费,大的装填因子又将付出更多的时间代价,
    不均匀的连表长度导致空间效率的严重下降
 


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值