md文档网址:https://gitee.com/infiniteStars/wang-dao-408-notes/blob/master/408/数据结构.md
1 绪论
1.1算法的基本概念
- 程序与算法的区别和联系
联系:程序是计算机指令的有序集合,是算法用某种程序设计语言的表述,是算法在计算机上的具体实现。
区别:在语言描述上不同,程序必须是用规定的程序设计语言来写,而算法的描述形式包括自然语言、伪代码、流程图和程序语言等;算法所描述的步骤—定是有限的,而程序可以无限地执行下去,比如一个死循环可以称为程序,但不能称为算法。
1.2数据结构的基本概念
数据结构是由某一数据对象及该对象中所有数据元素之间的关系组成的。数据结构包括数据的逻辑结构,存储结构及数据的运算三方面的内容。
1.3数据抽象和抽象数据类型
抽象数据类型(ADT)是一个数据模型以及在其上定义的运算集合。最主要的特征是数据封装和信息隐蔽。
1.4描述数据结构和算法
- 算法的五个特征
- 输入:0个及以上
- 输出:1个及以上
- 可行性
- 确定性
- 有穷性
1.5算法分析的基本方法
算法的时间复杂度一般是指程序运行从开始到结束所需的时间。
算法执行时间需通过依据该算法编制的程序在计算机上运行所消耗的时间来度量。
度量算法执行时间的方法:事后统计法,事前估计法
影响算法时间效率最主要因素是问题规模。
2 线性表
2.1线性表的定义及基本操作
2.2线性表的顺序存储
1. 数据结构定义
#define MaxSize 100 //定义顺序表的最大长度
typedef struct {
ElemType data[MaxSize];
int length; //顺序表的当前长度
}SqlList; //顺序表的类型定义
2. 基本操作
插入操作
- 判断索引是否越界
- 判断元素个数是否大于最大长度
- 索引 i 之后的元素往后移一位
- 将元素e放入位置 i 处
删除操作
- 判断索引是否越界
- 删除索引 i 处的元素 e
- 将 i 后边的元素向前移一位
2.3线性表的链接存储
1.单链表
1.数据结构定义
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域
}LNode, *LinkList;
2. 基本操作
插入——头插法
- 将结点s插入到a1前面
s.next = L.next
L.next = s
插入——尾插法
- 将结点 s 插到最后
r.next = s //r为指向尾结点的指针
r = s
删除操作
- 删除结点p后边的结点q
p.next = p.next.next
free(q)
2. 双链表
1. 数据结构定义
typedef struct DNode{
ElemType data;
struct Dnode *prior,*next; //前驱和后继指针
}Dnode,*DLinkList;
2. 基本操作
插入操作
s.next = p.next //第一步
p.next.prior = s //第二步
s.prior = p //第三步
p.next = s //第四步
删除操作
p.next = q.next
q.next.prior = p
free(q)
3. 循环链表
循环单链表
循环双链表
4.静态链表
1. 概念
- 静态链表是借助数组来描述线性表的链式存储结构
2. 数据结构
#define MaxSize 50 //静态链表的最大长度
typedef struct{
ElemType data;
int next; //下一个元素的数组下标
}SLinkList[MaxSize];
3 栈和队列
3.1栈和队列的基本概念
3.2栈和队列的顺序存储结构
1. 栈的顺序存储
#define MaxSize 50;
typedef struct{
ElemType data[MaxSize]; //存放栈中元素
int top; //栈顶指针
} SqStack;
2. 栈的基本操作
- 初始化时栈顶指针 top 指向 -1
入栈
S.data[++S.top] = x; //指针先加1,再入栈
出栈
x = S.data[S.top--]; //先出栈,指针再减1
3.共享栈
两个栈的栈顶指针都指向栈顶元素,top0=-1时0号栈为空,top1=MaxSize时1号栈为空;仅当两个栈顶指针相邻(top1-top0=1)时,判断为栈满。当0号栈进栈时top0先加1再赋值,1号栈进栈时top1先减1再赋值:出栈时则刚好相反。
4. 队列的顺序存储
#define MaxSize 50
typedef struct{
ElemType data[MaxSize];
int front,rear; //队头指针和队尾指针
}SqQueue;
- 初始状态(队空条件):Q.front== Q.rear ==0。
- 进队操作:队不满时,先送值到队尾元素,再将队尾指针加1。
- 出队操作:队不空时,先取队头元素值,再将队头指针加1。
5.循环队列
区分队满和队空的三种方法
三种方法进栈,入栈操作都一样
- 进栈:队尾指针进1——Q.rear = (Q.rear+1)%MaxSize
- 出栈:队首指针进1——Q.front = (Q.front+1)%MaxSize
- 牺牲一个存储单元
- 队满条件: (Q.rear+1)%MaxSize == Q.front。
- 队空条件:Q.front==Q.rear
- 队列中元素的个数: (Q.rear-Q.front+MaxSize) % Maxsize。
- 类型中增设表示元素个数的数据成员。这样,队空的条件为Q.size=0;队满的条件为Q.size=MaxSize。这两种情况都有Q.front=Q.rear
- 类型中增设tag 数据成员,以区分是队满还是队空。tag等于0时,若因删除导致Q.front=Q.rear,则为队空;tag等于1时,若因插入导致Q.front==Q.rear,则为队满。
3.3栈和队列的链式存储结构
1. 栈的链式存储
typedef struct Linknode{
ElemType data;
struct Linknode *next;
} LiStack;
- 出栈和进栈都在链表的表头进行
2. 队列的链式存储
typedef struct LinkNode{ //链式队列结点
ElemType data;
struct LinkNode *next;
}LinkNode;
typedef struct{ //链式队列
LinkNode *front,*rear; //队头和队尾指针
}LinkQueue;
- 队列判空
Q.front = Q.rear
- 入队
//创建一个新结点s
Q.rear->next = s
Q.rear = s
- 出队
LinkNode *p = Q.front->next; //p为队列的第一个结点,Q.front指向头结点
Q.front->next = p->next;
if (Q.rear == p){ //队列中只有一个元素
Q.rear = Q.front; //队尾指针指向头结点
}
free(p);
3. 双端队列
双端队列是指允许两端都可以进行入队和出队操作的队列。
- 输出受限的双端队列
- 输入受限的双端队列
3.4表达式计算(栈)
中缀表达式,后缀表达式,前缀表达式
3.5递归(栈)
将递归算法变为非递归算法
4 数组
4.1数组的基本概念
4.2特殊矩阵
1. 对称矩阵的压缩存储
- 按行优先,下标从 0 开始
2. 三角矩阵的压缩存储
- 下三角矩阵(按行优先,下标从 0 开始)
- 下三角矩阵(按行优先,下标从 0 开始)
3.三对角矩阵
- 按行优先,下标从 0 开始
4.3稀疏矩阵
- 将非零元素及相应的行和列构成一个三元组(行标,列表,值)
- 可以按行优先(行三元组表)或者列优先(列三元组表)存在在一个三元组中。
- 稀疏矩阵快速转置算法所需的num数组与k数组
- num[j]统计稀疏矩阵A中列号为 j 的非零元素个数
- k[j] 统计稀疏矩阵A中列号从 0 到 j-1 的非零元素个数之和。
5 树和二叉树
5.1树的基本概念
-
度为 m 的树中第 i 层上至多有 m^(i-1) 个节点
5.2二叉树
5.2.1二叉树的定义及主要特征
- 非空二叉树上的叶子结点数等于度为2的结点数加1,即n0 =n2 + 1。
- 非空二叉树上第k层上至多有 2^(k-1) 个结点(k≥1)。
- 高度为h的二叉树至多有2^h - 1个结点(h≥1)。
5.2.2二叉树的顺序存储和链式存储
1. 顺序存储
2.链式存储
- 链式数据结构定义
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild; //左,右孩子指针
}BiTNode,*BiTree;
在含有n个结点的二叉链表中,含有 n+1 个空链域
5.2.3二叉树的遍历
1.先序遍历
void PreOrder(BiTree T){
if (T!=null){
visit(I);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
2.中序遍历
void InOrder(BiTree T){
if (T!=null){
visit(I);
InOrder(T->lchild);
visit(I);
InOrder(T->rchild);
}
}
3.后序遍历
void PostOrder(BiTree T){
if (T!=null){
visit(I);
PostOrder(T->lchild);
PostOrder(T->rchild);
visit(I);
}
}
4. 层次遍历
void LevelOrder(BiTree T){
InitQueue(Q); //初始化辅助队列
BiTree p;
EnQueue(Q,T); //将根结点入队
while (!IsEmpty(Q)){
DeQueue(Q,p); //删除队头元素,并用p返回(赋值给p)
visit(p);
if (p->lchild!=null)
EnQueue(Q,p->lchild);
if (p->rchild!=null)
EnQueue(Q,p->rchild);
}
}
5.2.4 线索二叉树的基本概念和构造
概念
线索二叉树是以一定的规则将二叉树中的结点排列成一个线性序列,从而得到几种遍历序列,使得该序列中的每个结点(第一个和最后一个结点除外)都有一个直接前驱和直接后继。
引入线索二叉树是为了加快查找结点前驱和后继的速度
构造
数据结构
typedef struct ThreadNode{
ElemType data;
struct ThreadNode *lchild,*rchild;
int ltag,rtag;
}ThreadNode,*ThreadTree;
通过中序遍历对二叉树线索化
void Inthread(ThreadTree &p,ThreadTree &pre){
if (p!=null){
Inthread(p->lchild,pre); //递归,线索化左子树
if (p->lchild == NULL){
p->lchild = pre;
p->ltag = 1;
}
if (pre!=NULL && pre->rchild==NULL){
pre->rchild = p;
pre->rtag = 1;
}
pre = p; //标记当前结点为刚刚访问过的结点
Inthread(p->rchild,pre); //递归,线索化右子树
}
}
通过中序遍历建立中序搜索二叉树
void CreateInThread(ThreadTree T){
ThreadTree pre = null;
if (T!=null){
Inthread(T,pre); //线索化二叉树
pre->rchild = NULL;
pre->rtag = 1;
}
}
5.3树和森林
5.3.1树的存储结构
1.双亲表示法
#define MAX_TREE_SIZE 100 //树中最多结点数
typedef struct{
ElemType data;
int parent; //双亲位置域
}PTNode;
typedef struct{
PTNode nodes[MAX_TREE_SIZE];
int n; //结点数
}PTree;
该存储结构利用了每个结点(根结点除外)只有唯一双亲的性质,可以很快得到每个结点的双亲结点,但求结点的孩子时需要遍历整个结构。
2.孩子表示法
这种存储方式寻找子女的操作非常直接,而寻找双亲的操作需要遍历n个结点中孩子链表指针域所指向的n个孩子链表。
3.孩子兄弟表示法(左孩子,右兄弟)
typedef struct CSNode{
Elemtype data;
struct CSNOde *nextchild,*nextsibling;
}CSNode,*CSTree;
这种存储表示法比较灵活,其最大的优点是可以方便地实现树转换为二叉树的操作,易于查找结点的孩子等,但缺点是从当前结点查找其双亲结点比较麻烦。
5.3.2森林和二叉树的转换
- 将森林中每棵树转换成二叉树(左孩子右兄弟)
- 将第二个二叉树插到 第一个二叉树的右孩子处,以此类推
5.3.3树和森林的遍历
树的后根遍历也叫做中根遍历
5.4树和二叉树的应用
5.4.1二叉排序树
1.查找
BSTNOde *BST_Search(BiTree T,ElemType key){
while (T!=NULL && T->data!=key){
if (key<T->data)
T = T->lchild;
else
T = T->rchild;
}
return T;
}
2.插入
- 插入的结点一定是一个新添加的叶结点
int BST_Insert(BiTree &T,KeyType k){
if (T==null){
T = (BiTree)malloc(sizeof(BSTNode));
T->data = k;
T->lchild = T->rchild = null;
return 1;
}
else if (k==T->data){ //树中存在相同关键字的结点,插入失败
return 0;
}
else if (k<T->data){
return BST_Insert(T->lchild,k);
}
else
return BST_Insert(T->rchild,k);
}
3.删除
- 叶结点,直接删除
- 只有一颗左(右)子树,直接删除,左(右)子树接替
- 左右子树都有,让结点z的直接后继(或直接前驱)代替z,然后在子树中删除直接后继,变为第一或第二种情况
4. 查找性能
- 二叉排序树的查找效率主要取决于树的高度。
- 平均查找长度为O(log2n),最坏情况下平均查找长度为O(n)
5.4.2 平衡二叉树
- 平均查找长度为O(log2n)
1. 插入
例:以关键字序列{16,3,7,11,9,26,18,14,15}构造一颗AVL树(二叉平衡树)
5.4.3哈夫曼(Huffman)树和哈夫曼编码
哈夫曼树的构造
给定n个权值分别为W1, W2,……,Wn的结点,构造哈夫曼树的算法描述如下:
- 将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F。
- 构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和。
- 从F中删除刚才选出的两棵树,同时将新得到的树加入F中。
- 重复步骤2和3,直至F中只剩下一棵树为止。
6 图
6.1图的基本概念
6.2图的存储及基本操作
6.2.1邻接矩阵法
#define MaxVertexNum 100 //顶点数目的最大值
typedef char VertexType; //顶点的数据类型
typedef int EdgeType //带权图边上权值的数据类型
typedef struct{
VertexType Vex[MaxVertexNum]; //顶点表
EdgeType Edge[MaxVertexNum][MaxVertexNum]; //边表
int vexnum,arcnum; //图的当前顶点数和弧数
}MGraph;
6.2.2邻接表表示法
#define MaxVertexNum 100 //图中顶点数目的最大值
typedef struct ArcNode{ //边表结点
int adjvex;
struct ArcNode *next;
}ArcNode;
typedef struct VNode{ //顶点表结点
VertexType data; //顶点信息
ArcNode *first; //指向第一条以赴该节点的指针
}VNode,AdjList[MaxVertexNum];
typedef struct{
AdjList vertices; //邻接表
int vexnum,arcnum; //图中顶点数和弧数
}ALGraph;
6.3图的遍历
6.3.1深度优先搜索
bool visited [MAX VERTEX NUM];//访问标记数组
void BFSTraverse(Graph G){ //对图G进行广度优先遍历
for(i=0;i<G.vexnum;++i)
visited[i]=FALSE; //访问标记数组初始化
InitQueue (Q); //初始化辅助队列Q
for(i=0;i<G.vexnum; ++i)//从0号顶点开始遍历
if(!visited[i]) //对每个连通分量调用一次BFS
BFS(G,i); //vi未访问过,从vi开始BFS
void BFS(Graph G, int v){ //从顶点v出发,广度优先遍历图G
visit(v); //访问初始顶点V
visited[v]=TRUE; //对v做已访问标记
Enqueue(Q, v); //顶点v入队列Q
while(!isEmpty (Q)){
DeQueue(Q, v); //顶点v出队列
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){ //检测v所有邻接点
if(!visited[w]){ //w为v的尚未访问的邻接顶点
visit(w); //访问顶点w
visited[w]=TRUE; //对w做已访问标记
EnQueue(Q,w); //顶点w入队列
}
}
}
}
性能分析
- 最坏情况下,空间复杂度O(|V|)
- 采用领接表存储,时间复杂度为 O(|V|+|E|)。采用邻接矩阵存储,时间复杂度为 O(|V|^2)
6.3.2广度优先搜索
bool visited[MAX_VERTEX_NUM]; //访问标记数组
void DFSTraverse(Graph G){ //对图G进行深度优先遍历
for(v=0;v<G.vexnum;++v)
visited[v] =FALSE; //初始化已访问标记数据
for (v=0;v<G.vexnum;++v) //本代码中是从v=0开始遍历
if (!visited[v])
DFS(G,v);
}
void DFS (Graph G,int v){ //从顶点v出发,深度优先遍历图G
visit(v) ; //访问顶点v
visited[v]=TRUE; //设已访问标记
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
if (!visited[w]) //w为v的尚未访问的邻接顶点
DFS(G,w);
}
性能分析
- 空间复杂度O(|V|)
- 采用领接表存储,时间复杂度为 O(|V|+|E|)。采用邻接矩阵存储,时间复杂度为 O(|V|^2)
6.4图的基本应用
6.4.1拓扑排序
步骤
- 从AOV网中选择一个没有前驱的顶点并输出。
- 从网中删除该顶点和所有以它为起点的有向边。
- 重复①和②直到当前的AOV网为空或当前网中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。
算法实现
bool Topologicalsort (Graph G){
InitStack(S); //初始化栈,存储入度为0的顶点
for (int i=0;i<G.vexnum;i++)
if(indegree[i]==0)
Push(s,i); //将所有入度为0的顶点进栈
int count=0; //计数,记录当前已经输出的顶点数
while(!IsEmpty(S)){ //栈不空,则存在入度为0的顶点
Pop (s,i); //栈顶元素出栈
print[count++]=i; //输出顶点i
for(p=G.vertices[i].firstarc;p;p=p->nextarc){
//将所有i指向的顶点的入度减1,并且将入度减为0的顶点压入栈S
v=p->adjvex;
if(!(--indegree[v]))
Push (s,v); //入度为0.入栈
)
}
if (count<G.vexnum)
return false; //排序失败,有向图中有回路
else
return true; //拓扑排序成功
}
}
6.4.2关键路径
- 正向找最大,反向找最小
- 最早时间=最晚时间 :关键路径
6.4.3 最小代价生成树
1.Prim算法
初始时从图中任取一顶点(如顶点1)加入树T,此时树中只含有一个顶点,之后选择一个与当前T中顶点集合距离最近的顶点,并将该顶点和相应的边加入T,每次操作后T中的顶点数和边数都增1。以此类推,直至图中所有的顶点都并入T,得到的T就是最小生成树。此时T中必然有n-1条边。
2.Kruskal算法
初始时为只有n个顶点而无边的非连通图T= {V,{}},每个顶点自成一个连通分量,然后按照边的权值由小到大的顺序,不断选取当前未被选取过且权值最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加入T,否则舍弃此边而选择下一条权值最小的边。以此类推,直至T中所有顶点都在一个连通分量上。
6.4.4最短路径
7 搜索(Search)
7.1搜索的基本概念
7.2顺序搜索法
王海艳数据结构版
1.无序表的顺序搜索
int Search(listSet L,ElemType x){
int i;
for (i=0;i<n;i++){
if (L.element[i]==x)
return i;
}
return -1;
}
- 搜索成功的情况下平均查找长度 ASL = (n+1)/2
- 搜索失败的情况下平均查找长度 ASL = n
2. 有序表的顺序搜索
int Search(listSet L,ElemType x){
int i;
for (i=0;L.element[i]<x;i++){
if (L.element[i]==x)
return i;
}
return -1;
}
- 搜索成功的情况下平均查找长度 ASL = (n+1)/2
- 搜索失败的情况下平均查找长度 ASL = n/2+2
7.3二分搜索法
int Binary Search(SeqList L,ElemType key)
int low=0,high=L.TableLen-1,mid;
while.(low<=high){
mid=(low+high)/2; //取中间位置
if(L.elem[mid] ==key)
return mid; //查找成功则返回所在位置
else if(L.elem[mid]>key)
high=mid-1; //从前半部分继续查找
else
low=mid+1; //从后半部分继续查找
}
return-1; //查找失败,返回-1
}
-
时间复杂度为O(log2n)
-
判定树
在等概率情况下,查找成功(圆形结点)的ASL=(1×1+2×2+3x4+4×4)/11 =3,查找不成功(方形结点)的ASL=(3x4+4x8)/12= 11/3。
7.4 B-树及其基本操作
B树适用于外搜索的理由
B树的每个节点可以存储多个关键字,它将节点大小设置为磁盘页的大小、充分利用了磁盘预读的功能。每次读取磁盘页时就会读取整个节点。也正因每个节点存储着非常多个关键字、树的深度就会非常的小。进而要执行的磁盘读取操作次数就会非常少,更多的是在内存中对读取进来的数据进行查找。
1.插入
2. 删除
3. B-树的特性
7.5散列(Hash)表
1.概念
散列函数:一个把查找表中的关键字映射成该关键字对应的地址的函数,记为Hash(key)=Addr
散列(Hash)表:根据关键字而直接进行访问的数据结构。
常用的散列函数:
- 直接定址法:H(key)=a x key + b
- 除留余数法:H(key)=key % p
- 平方取中法
- 折叠法:将关键字值从左到右划分成位数相等的若干个部分值(位数与散列地址位数相等),部分值叠加,获得散列地址。如果计算结果超过地址位数,则将最高位去掉。
处理冲突的方法:
-
开放定址法
取定某一增量序列后,对应的处理方法就是确定的。通常有以下4种取法:①线性探测法。
②平方探测法(二次探测法)
③再散列法
④伪随机序列法 -
拉链法
2. 线性探测法
3.平方散列法
4. 再散列法(双散列法)
- 易错:第二个散列函数是下一个探测地址的增量
5. 伪随机序列法
当di=伪随机数序列时,称为伪随机序列法。
6.拉链法
- ASL失败 = (2+3+1+0+0+2+0+0+0+0+0)/11 = 8/11 (王道书上的方法,以这个为准)
7.6搜索算法的分析及应用
8 内排序
8.1排序的基本概念
8.2简单选择排序
- 选择最小元素,放在队列前端
void SelectSort(ElemType A[],int n){
for (i=0;i<n-1;i++){
min=i;
for (j=i+1;j<n;j++){
if (A[i]<A[min])
min=j;
}
if (min!=i)
swap(A[i],A[min]);
}
}
8.3直接插入排序
- 一个一个往前移
void InsertSort(ElemType A[],int n){
int i,j;
for (i=2;i<=n;i++){
if (A[i]<A[i-1]){
A[0]=A[i]; //复制为哨兵,A[0]不存放元素
for (j=i-1;A[0]<A[j];--j)//找到最后一个比A[0]的元素
A[j+1] = A[j]; //依次往后移
A[j+1] = A[0]; //将A[0]插入
}
}
}
8.4冒泡排序(bubble sort)
- 一个一个比较
- 下边代码是每趟排序选出一个最小位置,与上图过程不一致
void BubbleSort(ElemType A[],int n){
for (i=0;i<n-1;i++){
flag = false;
for (j=n-1;j>i;j--)
if (A[j-1]>A[j]){
swap(A[j-1],A[j]);
flag = true;
}
if (flag = false)
return; //本趟遍历没有发生交换,说明已经有序
}
}
8.5希尔排序(shell sort)
- 增量之间的插入排序(常用增量)
public void sort(Comparabel[] a){
int len=a.length;
int h=1;
while (h<len/2)
h=2*h+1;
while(h>=1){
for (int i=h;i<len;i++){
for (int j=i;j>=h && less(a[j],a[j-h]);j-=h)
exch(a,j,j-h);
}
h=h/2;
}
}
8.6快速排序
王道
void QuickSort (ElemType A[],int low,int high){
if(low<high){ //递归跳出的条件
//Partition ()就是划分操作,将表A[low…high]划分为满足上述条件的两个子表
int pivotpos=Partition(A,low, high); //划分
QuickSort(A,low,pivotpos-1); //依次对两个子表进行递归排序
QuickSort(A,pivotpos+1,high);
}
}
int Partition(ElemType A[],int low,int high){ //一趟划分
ElemType pivot=A [low];//将当前表中第一个元素设为枢轴,对表进行划分
while(low<high){ //循环跳出条件
while(low<high&&A[high]>=pivot)
--high;
A[low]=A[high]; //将比枢轴小的元素移动到左端
while(low<high&&A[low]<=pivot)
++low;
A[high]=A[low]; //将比枢轴大的元素移动到右端
A[low]=pivot; //枢轴元素存放到最终位置
return low; //返回存放枢轴的最终位置
}
}
南邮
- 示例
待排序序列 48 36 68 72 12 48 02
第一趟 (12 36 02 48) 48 (72 68)
第二趟 (02) 12 (36 48) 48 (72 68) 对左边进行排序
第三趟 02 12 36 48 48 72 68 对(36,48)进行排序
第四趟 02 12 36 48 48 68 72 对右边进行排序
8.7堆排序
- ①建堆 ②m/2 ③建大根堆 ④与最后一个换
8.8两路合并排序(merge sort)
- 分组再结合
//将前后两个相邻的顺序表合成一个
ElemType *B = (ElemType *)malloc((n+1)*sizeof(ElemType)); //辅助数组B
//A[low,mid],A[mid+1,high]有序,将他们两个合并到一起
void merge(ElemType A[],int low,int mid,int high){
for (int k=low;k<=high;k++){
B[k]=A[k];
}
for (i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){
if (B[i]<B[j])
A[k]=B[i++];
else
A[k]=B[j++];
}
//若一个表未检测完,复制
while (i<=mid)
A[k++]=B[i++];
while (j<=high)
A[k++]=B[j++];
}
//归并排序
void MergeSort(ElemType A[],int low,int high){
if (low<high){
int mid=(low+high)/2;
MergeSort(A,low,mid);
MergeSOrt(A,mid+1,high);
merge(A,low,mid,high);
}
}
8.9基数排序
8.10各种内部排序算法的比较
不稳定性:快些(希)选一堆好友来聊天
时间快:快些归队
8.11内部排序算法的应用
-
若n较小,可以采用选择排序或插入排序。当记录本身信息量较大时,使用选择排序较好。
-
若初始状态已基本有序,可以选用插入排序或冒泡排序。
-
所需的辅助空间:堆排序<快速排序<归并排序
-
交换次数最小的排序是简单选择排序
-
排序速度与数据的初始排列状态没有关系的是简单选择排序。