一、各种数据结构的创建(初始化)
1.1 线性表——顺序表
// 顺序表的存储结构
#define MAXSIZE 100 // 顺序表可能达到的最大长度
typedef struct{
ElemType *elem; // 存储空间的基地址
int length; // 当前长度
}SqList; // 顺序表的结构类型为SqList
// 初始化
bool InitList(SqList &L){
L.elem = (ElemType*)malloc(sizeof(ElemType));
if(!L.elem) return false;
L.length = 0;
return true;
}
1.2 线性表——单链表
// 单链表的存储结构
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
// 初始化
bool InitList(LinkList &L){
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
return true;
}
1.3 线性表——双链表
// 双向链表的存储结构
typedef struct DuLNode{
ElemType data; // 数据域
struct DuLNode *prior; // 前指针
struct DuLNode *next; // 后指针
}DuLNode,*DuLinkList;
// 初始化
bool InitDuList(DuLinkList &D){
D = (DuLinkList)malloc(sizeof(DuLNode));
D->prior = NULL;
D->next = NULL;
}
1.4 栈——顺序栈
// 顺序栈的存储结构
#define MAXSIZE 100
typedef struct{
ElemType *base; // 栈底指针
ElemType *top; // 栈顶指针
int stacksize; // 栈可用的最大容量
}SqStack;
// 初始化
void InitStack(SqStack &s){
s.base = (ElemType*)malloc(sizeof(ElemType));
if(!s.base) return false;
s.top = s.base;
s.stacksize = MAXSIZE;
return true;
}
1.5 栈——链栈
// 链栈的存储结构
typedef struct StackNode{
ElemType data;
struct StackNode *next;
}StackNode,*LinkStack;
// 初始化
bool InitLinkStack(LinkStack &s){
s = NULL;
return true;
}
1.6 队列——顺序队列
// 顺序队列的存储结构
#define MAXSIZE 100;
typedef struct{
ElemType *base;
int front;
int rear;
}SqQueue;
// 初始化
bool InitQueue(SqQueue &q){
q.base = (ElemType*)malloc(sizeof(ElemType)*MAXSIZE);
if(!q.base) return false;
q.front = q.rear = 0;
return true;
}
1.7 队列——链队列
// 链队列的存储结构
typedef struct QNode{ // 队列的节点
ElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct{ // 链队列
QueuePtr front; // 队头指针
QueuePtr rear; // 队尾指针
}LinkQueue;
// 初始化
bool InitQueue(LinkQueue &q){
q.front = q.rear = (LinkQueue*)malloc(sizeof(LinkQueue));
q.front->next = NULL; // 头节点的指针域置空
return true;
}
1.8 串——顺序
// 串的 定长 顺序存储结构
#define MAXLEN 255 // 串的最大长度
typedef struct{
char ch[MAXLEN+1]; // 存储串的一维数组
int length; // 串的当前长度
}SString;
// 串的 堆式 顺序存储结构
typedef struct{
char *ch;
int length;
}SString;
1.9 串——链式
// 串的链式存储结构
#define CHUNKSIZE 80
typedef struct Chunk{ // 节点
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct{
Chunk *head,*tail; // 串的头和尾指针
int length; // 串的当前长度
}LString;
1.10 树——二叉树的二叉链表
// 二叉树的二叉链表存储表示
typedef struct BiTNode{
ElemType data; // 节点的数据域
struct BiTNode *lchild,*rchild; // 左右孩子指针
}BiTNode,*BiTree;
1.11 树——二叉树的二叉线索树
// 二叉线索树存储表示
typedef struct BiThrNode{
ElemType data;
struct BiThrNode *lchild,*rchild; // 左右孩子指针
int LTag,RTag; // 左右标志
}BiThrNode,*BiThrTree;
注意: \color{green}{注意:} 注意:
LTag= { 0 lchid域指示节点的左孩子 1 lchid域指示节点的前驱 RTag= { 0 rchid域指示节点的右孩子 1 rchid域指示节点的后继 \text{LTag=}\left\{ \begin{array}{l} 0\quad\text{lchid域指示节点的左孩子}\\ 1\quad\text{lchid域指示节点的前驱} \end{array} \right.\\ \quad\\ \text{RTag=}\left\{ \begin{array}{l} 0\quad\text{rchid域指示节点的右孩子}\\ 1\quad\text{rchid域指示节点的后继} \end{array} \right.\\ LTag={0lchid域指示节点的左孩子1lchid域指示节点的前驱RTag={0rchid域指示节点的右孩子1rchid域指示节点的后继
1.12 图——邻接矩阵表示法
// 图的邻接矩阵存储表示
#define MaxInt 32767 // 表示极大值 即无穷大
#define MVNum 100 // 最大顶点数
typedef char VerTexType; // 假设顶点的数据类型为字符型
typedef int ArcType; // 假设边的权值类型为整型
typedef struct{
VerTexType vexs[MVNum]; // 顶点表
ArcType arcs[MVNum][MVNum]; // 邻接矩阵
int vexnum,arcnum; // 图的顶点数、边数
}AMGraph;
1.13 图——邻接表表示法
// 图的邻接表的存储表示
#define MVNum 100 // 最大顶点数
typedef struct ArcNode{ // 边节点
int adjvex; // 该边指向的顶点的位置
struct ArcNode *nextarc; // 指向下一条边的指针
OtherInfo info; // 和边相关的信息
}ArcNode;
typedef struct VNode{
VerTexType data;
ArcNode *firstarc; // 指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum]; // AdjList 表示邻接表的类型
typedef struct{
AdjList vertices;
int vexnum,arcnum; // 图的顶点数、边数
}ALGraph;
图示:
\color{blue}{图示:}
图示:
二、算法大集合
2.1 串——BF算法
模式匹配算法,指定主串中查找的起始位置pos
算法步骤:
用 i,j 表示主串S,模式串T中正待比较的字符位置,i 的初始值为pos,j 的初始值为1
两串均未到末尾,循环执行:
S.ch[i] 和 T.ch[j] 比较,相同就 i,j 分别指向下一位置。
若不相等,指针退后重新比较,从主串的下一字符 i=i-j+2 起重新和模式串的第一个字符 j=1 比较
如果 j > T.length 说明匹配成功,返回 i - T.length;否则不成功就返回 0
int BF(SString S,SString T,int pos){
i = pos;
j = 1;
while(i <= S.length && j <= T.length){
if(S.ch[i] == T.ch[j]){
i++;j++;
}else{
i = i - j + 2; // 回溯
j = 1;
}
}
if(j > T.length){
// 匹配成功
return i - T.length;
}else{
return 0;
}
}
BF算法的时间复杂度: \color{red}\text{BF算法的时间复杂度:} BF算法的时间复杂度:
O ( n × m ) O(n\times m) O(n×m)
2.2 串——KMP算法
KMP算法是BF算法的升级版,简化了算法的时间复杂度,即不对 i 进行回溯,使模式串自己把以前和主串一样的部分排开,从不一样的地方开始匹配。
int next[100]; // 因为后面要使用next数组,先在堆内存开辟出来,初始化为0
int KMP(SString S,SString T,int pos){
int i = pos;
int j = 1;
get_next(T,next); // 调用get_next()函数,将数据存到next数组中
while(i <= S.length && j <= T.length){
if(S.ch[i] == T.ch[j]){
i++;j++;
}else{
//i = i - j + 2; // 回溯
//j = 1;
j = next[j]; // 模式串自己匹配,需要用next数组辅助,这样就知道j要去哪里匹配了
}
}
if(j > T.length){
// 匹配成功
return i - T.length;
}else{
return 0;
}
}
// 以下是求next数组
void get_next(SString T,int next[]){
int i = 1,j = 0;
next[1] = 0; // 防止脏数据
while(i < T.length){
if(j == 0 || T.ch[i] == T.ch[j]){
i++;
j++;
next[i] = j; // 由这里可以看出,next[2] 一定为 1
}else{
j = next[j]; // 不相等了,就让 j 去回溯
}
}
}
例如:模式串为 a b a b a a b a b ,它的 n e x t 数组为: \color{green}{例如:模式串为ababaabab,它的next数组为:} 例如:模式串为ababaabab,它的next数组为:
j j j | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
模式串 | a | b | a | b | a | a | b | a | b |
next[ j j j] | 0 \color{red}{0} 0 | 1 \color{red}{1} 1 | 1 \color{red}{1} 1 | 2 \color{red}{2} 2 | 3 \color{red}{3} 3 | 4 \color{red}{4} 4 | 2 \color{red}{2} 2 | 3 \color{red}{3} 3 | 4 \color{red}{4} 4 |
!注意: \color{red}{!注意:} !注意:
当模式串为aaaab,在主串aaabaaaab中时,KMP的匹配效率很低,因为模式串的next数组为:01234。所以要改进next数组。
使用nextval数组来修正之前的next数组,提升KMP算法的整体效率!
算法核心思想: 最大前缀和、最大后缀和 算法核心思想:\color{red}{最大前缀和、最大后缀和} 算法核心思想:最大前缀和、最大后缀和
void get_nextval(SString T,int nextval[]){
int i = 1,j = 0;
nextval[1] = 0; // 防止脏数据
while(i < T.length){
if(j == 0 || T.ch[i] == T.ch[j]){
i++;
j++;
if(T.ch[i] != T.ch[j])
nextval[i] = j;
else
nextval[i] = nextval[j];
}else{
j = nextval[j];
}
}
}
例如:模式串为 a b a b a a b a b ,它的 n e x t v a l 数组为: \color{green}{例如:模式串为ababaabab,它的nextval数组为:} 例如:模式串为ababaabab,它的nextval数组为:
j | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
模式串 | a | b | a | b | a | a | b | a | b |
next[j] | 0 \color{red}{0} 0 | 1 \color{red}{1} 1 | 1 \color{red}{1} 1 | 2 \color{red}{2} 2 | 3 \color{red}{3} 3 | 4 \color{red}{4} 4 | 2 \color{red}{2} 2 | 3 \color{red}{3} 3 | 4 \color{red}{4} 4 |
nextval[j] | 0 | 1 | 0 | 1 | 0 | 4 | 1 | 0 | 1 |
这样就可以替换KMP算法中的next数组了!附完整KMP代码: \color{gray}\text{这样就可以替换KMP算法中的next数组了!附完整KMP代码:} 这样就可以替换KMP算法中的next数组了!附完整KMP代码:
int nextval[100];
int next[100];
int KMP(SString S,SString T,int pos){
i = pos;
j = 1;
get_next(T,next);
get_nextval(T,nextval);
while(i <= S.length && j <= T.length){
if(S.ch[i] == T.ch[j]){
i++;j++;
}else{
j = nextval[j];
}
}
if(j > T.length){
// 匹配成功
return i - T.length;
}else{
return 0;
}
}
void get_next(SString T,int next[]){
int i = 1,j = 0;
next[1] = 0; // 防止脏数据
while(i < T.length){
if(j == 0 || T.ch[i] == T.ch[j]){
i++;
j++;
next[i] = j; // 由这里可以看出,next[2] 一定为 1
}else{
j = next[j]; // 不相等了,就让 j 去回溯
}
}
}
void get_nextval(SString T,int nextval[]){
int i = 1,j = 0;
nextval[1] = 0; // 防止脏数据
while(i < T.length){
if(j == 0 || T.ch[i] == T.ch[j]){
i++;
j++;
if(T.ch[i] != T.ch[j])
nextval[i] = j;
else
nextval[i] = nextval[j];
}else{
j = nextval[j];
}
}
}
KMP算法的时间复杂度: \color{red}\text{KMP算法的时间复杂度:} KMP算法的时间复杂度:
O ( n + m ) O(n+m) O(n+m)
2.3 树——哈夫曼算法
哈夫曼树又称最优树,是一类带权路径长度最短的数
树的带权路径长度(WPL):
W P L = ∑ k = 1 n w k l k WPL=\sum\limits_{k=1}^nw_kl_k WPL=k=1∑nwklk
最优二叉树,哈夫曼树:WPL最小的二叉树
// 哈夫曼树的存储表示
typedef struct{
int weight; // 节点的权值
int parent,lchild,rchild; // 节点的双亲、左孩子、右孩子
}HTNode,*HuffmanTree;
算法分析:
初始化:置零,输入权值
创建树:选择两个权值最小的,合并
// 选择Select
void Select(HuffmanTree &HT,int n,int &s1,int &s2){
int min;
for(int i = 1; i <= n; i++){
if (HT[i].parent == 0){
min = i;
break;
}
}
for(int i = min+1; i <= n; i++){
if(HT[i].parent == 0 && HT[i].weight < HT[min].weight){
min = i;
}
}
s1 = min; // 第一个最小值找到
for(int i = 1; i <= n; i++){
if (HT[i].parent == 0 && i != s1){
min = i;
break;
}
}
for(int i = min+1; i <= n; i++){
if(HT[i].parent == 0 && HT[i].weight < HT[min].weight && i != s1){
min = i;
}
}
s2 = min; // 第二个最小值也找到了
}
// 构建哈夫曼树
void CreatHuffmanTree(HuffmanTree &HT,int n){
if(n <= 1) return; // 简单的判断
int m = 2*n -1; // 因为有n个叶子节点,哈夫曼树就要有2n-1个节点
HT = (HuffmanTree)malloc(sizeof(HTNode) * (m+1)) // 开辟空间,因为0号单元不用就多开一份
for(int i = 1; i <= m; i++){ // 置零
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
}
for(int i = 1; i <= n; i++){
scanf("%d",&HT[i].weight);
}
/*---------------------初始化哈夫曼树完成-----------------------*/
for(int i = n+1; i <= m; i++){
int s1,s2;
Select(HT,i-1,s1,s2); // 建立一个函数,选择两个最小的权值的索引放在s1,s2中
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
}
2.4 图——深度优先搜索(DFS)
深度优先搜索(DFS)是图的遍历,类似于树的先序遍历,是树的先序遍历的推广
- 采用 采用 采用 邻接矩阵 \color{red}{邻接矩阵} 邻接矩阵 表示的图的深度优先搜索 表示的图的深度优先搜索 表示的图的深度优先搜索
bool visited[MVNum]; // 访问标志数组,初始值为 false
void dfs(AMGraph G,int v){
visited[v] = true; // 先将访问的顶点设为 true
for(int w = 0; w < G.vexnum; w++){ //依次检查邻接矩阵 v 所在的行
if((G.arcs[v][w] != 0) && (!visited[w]))
dfs(G,w); // 如果 G.arcs[v][w] != 0 说明 w 是 v 的邻接点,若 w 未访问,递归调用 dfs
}
}
- 采用 采用 采用 邻接表 \color{red}{邻接表} 邻接表 表示的图的深度优先搜索 表示的图的深度优先搜索 表示的图的深度优先搜索
bool visited[MVNum]; // 访问标志数组,初始值为 false
void dfs(ALGraph G,int v){
visited[v] = true; // 先将访问的顶点设为 true
ArcNode *p = G.vertices[v].firstarc; // p 指向 v 的边链表的第一个边节点
while(p!=NULL){
int w = p->adjvex; // 表示 w 是 v 的邻接点
if(!visited[w]) dfs(G,w); // 如果 w 未访问,递归调用 dfs
p = p->nextarc; // p 指向下一个边节点
}
}
DFS算法的时间复杂度:(n为图中顶点数,e为图中边数) \color{red}\text{DFS算法的时间复杂度:(n为图中顶点数,e为图中边数)} DFS算法的时间复杂度:(n为图中顶点数,e为图中边数)
邻接矩阵
O ( n 2 ) O(n^2) O(n2)
邻接表
O ( n + e ) O(n+e) O(n+e)
2.5 图——广度优先搜索(BFS)
广度优先算法是尽可能对横向进行搜索,需要引进队列这种数据结构
算法分析:
设置一个visited数组,对访问过后的顶点进行记录。
设置一个队列,使访问中的那个顶点入队列
只要队列不空就一直执行以下操作:
队头元素 u u u出队
依次检查 u u u的所有邻接点 w w w,若visited[ w w w]的值为false,则访问。
算法实现: \color{green}{算法实现:} 算法实现:
bool visited[100]; // 对访问过后的节点进行记录。
// 入队
bool EnQueue(SqQueue &Q,int n){
if((Q.rear+1) % MAXSIZE == Q.front){
return false;
}
Q.base[Q.rear] = n;
Q.rear = (Q.rear + 1) % MAXSIZE;
return true;
}
// 出队
bool DeQueue(SqQueue &Q,int &n){
if(Q.front == Q.rear){
return false;
}
n = Q.base[Q.front];
Q.front = (Q.front+1) % MAXSIZE;
return true;
}
// 判断队列是否为空
bool QueueEmpty(SqQueue Q){
if(Q.front == Q.rear) return false;
else return true;
}
int FirstAdjVex(Graph G,int v){
// 假设图使用 邻接矩阵 存储
for(int i = 0; i < vexnum; i++){
if(arcs[v-1][i] != 0){
return i;
}
}
return -1;
}
int NextAdjVex(Graph G,int u,int w){
for(int i = w+1; i < vexnum; i++){
if(arcs[u-1][i] != 0){
return i;
}
}
return -1;
}
void bfs(Graph G,int v){
SqQueue Q; // 声明一个队列
InitQueue(Q); // 初始化队列
visited[v] = true;// 访问的顶点被标记
EnQueue(Q,v); // 顶点 v 入队
while(!QueueEmpty(Q)){
// 队列非空
int u; // 记录要出队的顶点
DeQueue(Q,u); // 队头出队,记为 u
for(int w = FirstAdjVex(G,v); w >= 0; w = NextAdjVex(G,u,w)){
// FirstAdjVex(G,v) 表示 u 的第一个邻接点
// NextAdjVex(G,u,w) 表示 u 相对于 w 的下一个邻接点
if(!visited[w]){
printf("%d ",w);
visited[w] = true;
EnQueue(Q,w);
}
}
}
}
BFS算法的时间复杂度:(n为图中顶点数,e为图中边数) \color{red}\text{BFS算法的时间复杂度:(n为图中顶点数,e为图中边数)} BFS算法的时间复杂度:(n为图中顶点数,e为图中边数)
邻接矩阵
O ( n 2 ) O(n^2) O(n2)
邻接表
O ( n + e ) O(n+e) O(n+e)
2.6 图——普利姆算法
**普利姆算法是用来解决最小生成树的一种算法**
此算法不考代码实现,只要求手算就行了 \color{red}{此算法不考代码实现,只要求手算就行了} 此算法不考代码实现,只要求手算就行了
//返回顶点在邻接矩阵中对应的下标
int LocateVex(AMGraph& G, char *v1)
{
for (int i = 0; i<G.vexnum; i++)
{
if (v1 == G.vexs[i])
return i;
}
}
void CreateUDN(AMGraph& G)
{
int i, j, k,w;
string v1,v2;
cin >> G.vexnum >> G.arcnum; //输入总顶点数和总边数
for (i = 0; i < G.vexnum; ++i)
cin >> G.vexs[i]; //依次输入点的信息***顶点数组
for (i = 0; i < G.vexnum; ++i)
for (j = 0; j < G.vexnum; ++j)
G.arcs[i][j] = MaxInt; //初始化邻接矩阵,使每个权值初始化为极大值
for (k = 0; k < G.arcnum; ++k) //构造邻接矩阵
{
cin >> v1 >> v2 >> w; //输入一条边依附的顶点及其权值
i = LocateVex(G, &v1[0]); //确定v1和v2在G中的位置,即顶点数组的下标
j = LocateVex(G, &v20]);
G.arcs[i][j] = w; //边<v1,v2>的权值置为w
G.arcs[j][i] = G.arcs[i][j]; //置<v1,v2>的对称边<v2,v1>权值为w
}
}
struct closedge
{
VerTexType adjex;//顶点
ArcType lowcost;//权值
}closedge[MVNum];//该数组用来记录从顶点集U到U-V的权值的最小的边
int Min(struct closedge *p,int G)
{
int min=100,k;
for(int i=0;i<G;i++)
{
if(p[i].lowcost!=0&&p[i].lowcost<min)
{
min=p[i].lowcost;
k=i;
}
}
return k;
}
void show(AMGraph G)
{
for (int i = 0; i < G.vexnum; i++)
{
for (int j = 0; j < G.vexnum; j++)
{
// if (G.arcs[i][j] == MaxInt)
cout << G.arcs[i][j] << "\t";
/// else
// cout << G.arcs[i][j] << "\t";
}
cout << endl<<endl;
}
}
//普利姆算法
void Prim(AMGraph G,char *u)//u为起始顶点
{
int k,j,i;
string u0,v0;
k=LocateVex(G,&u[0]);//k存储u的下标
for(j=0;j<G.vexnum;++j)//对U-V的每一个顶点初始化
if(j!=k)
closedge[j]={u,G.arcs[k][j]};//初始化
closedge[k].lowcost=0;
for(i=1;i<G.vexnum;++i)
{//其余的n-1个顶点生成n-1条边
k=Min(closedge,G.vexnum);//找出边上权最小的边对应的另一个顶点的下标
u0=closedge[k].adjex;//找出最小边在U中的点的下标
v0=G.vexs[k];//找出最小边在U-V中的点的下标
cout<<u0<<"——>"<<v0<<" "<<endl;//输出两点
closedge[k].lowcost=0;//初始化,相当于是标记已访问过了
for(j=0;j<G.vexnum;++j)
if(G.arcs[k][j]<closedge[j].lowcost)//找已经存在U中的最小权
closedge[j]={G.vexs[k],G.arcs[k][j]};
}
}
普利姆算法的时间复杂度: \color{red}\text{普利姆算法的时间复杂度:} 普利姆算法的时间复杂度:
O ( n 2 ) O(n^2) O(n2)
普利姆算法适用于求稠密图的最小生成树 \color{gray}{普利姆算法适用于求稠密图的最小生成树} 普利姆算法适用于求稠密图的最小生成树
2.7 图——克鲁斯卡尔算法
**克鲁斯卡尔算法也是求最小生成树的一种算法**
此算法不考代码实现,只要求手算就行了 \color{red}{此算法不考代码实现,只要求手算就行了} 此算法不考代码实现,只要求手算就行了
#include<iostream>
#include<stdio.h>
using namespace std;
typedef char VerTexType;
typedef int ArcType;
#define MaxInt 32767
#define MVNum 100
#define ArcNum 100
#define OK 1
#define ERROR -1
int Vexset[MVNum];//辅助数组表示连通分量
typedef int status;
typedef struct{
VerTexType vexs[MVNum] {'A','B','C','D','E','F'};
ArcType arcs[MVNum][MVNum];
int vexnum = 6,arcnum = 10;
}AMGraph;
typedef struct{
VerTexType Head;//起点
VerTexType Tail;//终点
ArcType lowcast;
}Edge[ArcNum];
status CreateUDN(AMGraph &G){//创建无向图
for(int i=0;i<G.vexnum;i++){
for(int j=0;j<G.vexnum;j++){
if(i==j){
G.arcs[i][j] = 0;
}else
G.arcs[i][j] = MaxInt;//初始状态全部节点之间相互不可达
}
}
G.arcs[0][1]=6;G.arcs[0][2]=1;G.arcs[0][3]=5;
G.arcs[1][2]=5;G.arcs[1][4]=3;
G.arcs[2][3]=5;G.arcs[2][4]=6;G.arcs[2][5]=4;
G.arcs[3][5]=2;
G.arcs[4][5]=6;
for(int i=0;i<G.vexnum;i++){
for(int j=i+1;j<G.vexnum;j++){
if(G.arcs[i][j]!=MaxInt){
G.arcs[j][i] = G.arcs[i][j];
}
}
}//矩阵对称
return OK;
}
void ShowGraph(AMGraph G){
cout<<" ";
for(int i=0;i<G.vexnum;i++){
cout<<" "<<G.vexs[i];
}
cout<<endl;
for(int i=0;i<G.vexnum;i++){
cout<<G.vexs[i]<<" ";
for(int j=0;j<G.vexnum;j++){
if(G.arcs[i][j]==MaxInt){
cout<<"* ";
}else{
cout<<G.arcs[i][j]<<" ";
}
}
cout<<endl;
}
}
int LocateVex(AMGraph G, VerTexType v){
int i;
for(i=0;i<G.vexnum;i++){
if(G.vexs[i]==v){
return i;
}
}
return ERROR;
}
VerTexType Transform(AMGraph G, int vn){
return G.vexs[vn];
}
void InitailEdge(AMGraph G,Edge &edge){//初始化边表
int arcnum = 0;
for(int i=0;i<G.vexnum;i++){//纵列为起点
for(int j=i+1;j<G.vexnum;j++){//横行为终点
if(G.arcs[i][j]!=MaxInt&&G.arcs[i][j]!=0){
edge[arcnum].Head = Transform(G,i);
edge[arcnum].Tail = Transform(G,j);
edge[arcnum].lowcast = G.arcs[i][j];
arcnum++;
}
}
}
}
void sort(AMGraph G,Edge &edge){
VerTexType tv;
ArcType tl;
for(int i=0;i<G.arcnum;i++){
for(int j=0;j<G.arcnum-i-1;j++){
if(edge[j].lowcast>edge[j+1].lowcast){
tv = edge[j].Head;
edge[j].Head = edge[j+1].Head;
edge[j+1].Head = tv;
tv = edge[j].Tail;
edge[j].Tail = edge[j+1].Tail;
edge[j+1].Tail = tv;
tl = edge[j].lowcast;
edge[j].lowcast = edge[j+1].lowcast;
edge[j+1].lowcast = tl;
}
}
}
}
void ShowEdge(AMGraph G,Edge edge){
for(int i=0;i<G.arcnum;i++){
cout<<edge[i].Head<<"-"<<edge[i].lowcast<<"-"<<edge[i].Tail<<endl;
}
}
void ShowVexset(AMGraph G){
for(int i=0;i<G.vexnum;i++){
cout<<Vexset[i]<<" ";
}
cout<<endl;
}
void Kruskal(AMGraph &G){
Edge edge;
InitailEdge(G,edge);
// ShowEdge(G,edge);
sort(G,edge);
// ShowEdge(G,edge);
for(int i=0;i<G.vexnum;i++){
Vexset[i] = i;//每个节点自成一个分量
}
int headi,taili;//边起点的下标、边终点的下标
int headt,tailt;//操作连通分量时的中间量
for(int i=0;i<G.arcnum;i++){
headi = LocateVex(G,edge[i].Head); //起点下标
taili = LocateVex(G,edge[i].Tail); //终点下标
headt = Vexset[headi];//获取起点的连通分量
tailt = Vexset[taili];//获取终点的连通分量
if(headt!=tailt){//如果两个点不是同一个连通分量
cout<<edge[i].Head<<"-"<<edge[i].lowcast<<"-"<<edge[i].Tail<<endl;
for(int j=0;j<G.vexnum;j++){
if(Vexset[j]==headt){//更新Vexset数组,把改起点的连通分量改成和终点连通分量一致(其实就是合并连通分量)
Vexset[j] = tailt;
// ShowVexset(G);
}
}
}
}
}
int main(){
AMGraph G;
CreateUDN(G);
ShowGraph(G);
Kruskal(G);
return 0;
}
克鲁斯卡尔算法的时间复杂度:(e表示边的条数) \color{red}\text{克鲁斯卡尔算法的时间复杂度:(e表示边的条数)} 克鲁斯卡尔算法的时间复杂度:(e表示边的条数)
O ( e log e ) O(e\text{log}e) O(eloge)
克鲁斯卡尔算法适用于求稀疏图的最小生成树 \color{gray}{克鲁斯卡尔算法适用于求稀疏图的最小生成树} 克鲁斯卡尔算法适用于求稀疏图的最小生成树
2.8 图——迪杰斯特拉算法
**迪杰斯特拉算法,典型的计算最短距离的算法,很有用的!但是只能求非负权值的图**
算法实现: \color{red}{算法实现:} 算法实现:
需要引入的数据结构:
一维数组visited:记录已经被确定的最短路径长度,true表示确定
一维数组path:记录当前最短路径上 v i v_i vi的直接前驱编号,其初始值为-1
一维数组val:记录从源点到终点 v i v_i vi的当前最短路径长度,其初始值为源点到各点 v i v_i vi的权值
算法步骤: \color{red}{算法步骤:} 算法步骤:
初始化:
将源点 v 0 v_0 v0加入visited数组中,即viisited[ v 0 v_0 v0] = true
v 0 v_0 v0到各个终点的最短路径初始化为权值,即val[ i i i] = G.arcs[ v 0 v_0 v0][ v i v_i vi]
若 v 0 v_0 v0和顶点 v i v_i vi之间有弧,则将 v i v_i vi的前驱置为 v 0 v_0 v0,即path[ i i i] = v 0 v_0 v0,否则path[ i i i] = -1
循环n-1次,执行下列操作
选择下一条最短路径的终点 v k v_k vk,使得:
v a l [ k ] = m i n { D [ i ] ∣ v i ∈ V − S } val[k]=min\lbrace D[i]|v_i\in V-S\rbrace val[k]=min{D[i]∣vi∈V−S}
将 v k v_k vk加到visited中,即visited[ v k v_k vk] = true
根据条件更新最短路径长度val,若条件val[ k k k] + G.arcs[ k k k][ i i i] < val[ i i i],则更新val[ i i i] = val[ k k k] + G.arcs[ k k k][ i i i],同时更改 v i v_i vi的前驱为 v k v_k vk,path[ i i i] = k k k
算法实现: \color{green}{算法实现:} 算法实现:
void Dijkstra(AMGraph G,int v0){
int n = G.vexnun; // n为G中的顶点数
/*---------初始化--------*/
bool visited[n];
int val[n];
int path[n];
for(int i = 0; i < n; i++){
visited[i] = false;
val[i] = G.arcs[v0][i];
if(val[i] < MaxInt) path[i] = v0; // 初始化,让和v0连通的所有顶点的前驱都为v0
else path[i] = -1;
}
/*-------初始化完成------*/
for(int i = 1; i < n; i++){ // 循环 n-1次
int min = MaxInt; // 声明最小值
for(int w = 0; w < n; w++){
if(!visited[w] && val[w] < min){ // 先找一个最短路径的顶点
v = w;
min = val[w];
}
}
visited[v] = true;
for(int w = 0; w < n; w++){
if(!visited[w] && (val[v] + G.arcs[v][w] < val[w])){
val[w] = val[v] + G.arcs[v][w]; // 更新最短距离
path[w] = v; // 更新前驱
}
}
}
}
例 \color{gray}{例} 例
给定邻接矩阵求 v 0 v_0 v0到各点的距离
[ ∞ ∞ 10 ∞ 30 100 ∞ ∞ 5 ∞ ∞ ∞ ∞ ∞ ∞ 50 ∞ ∞ ∞ ∞ ∞ ∞ ∞ 10 ∞ ∞ ∞ 20 ∞ 60 ∞ ∞ ∞ ∞ ∞ ∞ ] \begin{bmatrix}\infin&\infin&10&\infin&30&100\\\infin&\infin&5&\infin&\infin&\infin\\\infin&\infin&\infin&50&\infin&\infin\\\infin&\infin&\infin&\infin&\infin&10\\\infin&\infin&\infin&20&\infin&60\\\infin&\infin&\infin&\infin&\infin&\infin\end{bmatrix} ∞∞∞∞∞∞∞∞∞∞∞∞105∞∞∞∞∞∞50∞20∞30∞∞∞∞∞100∞∞1060∞
- 初始化结果 \color{red}{初始化结果} 初始化结果
v | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
visited | true | false | false | false | false | false |
val | 0 | ∞ \infin ∞ | 10 | ∞ \infin ∞ | 30 | 100 |
path | -1 | -1 | 0 | -1 | 0 | 0 |
- 求解过程中参数变化 \color{red}{求解过程中参数变化} 求解过程中参数变化
终点 | i = 1 i=1 i=1 | i = 2 i=2 i=2 | i = 3 i=3 i=3 | i = 4 i=4 i=4 | i = 5 i=5 i=5 |
---|---|---|---|---|---|
v 1 v_1 v1 | ∞ \infin ∞ | ∞ \infin ∞ | ∞ \infin ∞ | ∞ \infin ∞ | ∞ \infin ∞ |
v 2 v_2 v2 | 10( v 0 , v 2 v_0,v_2 v0,v2) | ||||
v 3 v_3 v3 | ∞ \infin ∞ | 60( v 0 , v 2 , v 3 v_0,v_2,v_3 v0,v2,v3) | 50( v 0 , v 4 , v 3 v_0,v_4,v_3 v0,v4,v3) | ||
v 4 v_4 v4 | 30( v 0 , v 4 v_0,v_4 v0,v4) | 30( v 0 , v 4 v_0,v_4 v0,v4) | |||
v 5 v_5 v5 | 100( v 0 , v 5 v_0,v_5 v0,v5) | 100( v 0 , v 5 v_0,v_5 v0,v5) | 90( v 0 , v 4 , v 5 v_0,v_4,v_5 v0,v4,v5) | 60( v 0 , v 4 , v 3 , v 5 v_0,v_4,v_3,v_5 v0,v4,v3,v5) | |
v k v_k vk | v 2 v_2 v2 | v 4 v_4 v4 | v 3 v_3 v3 | v 5 v_5 v5 | 无 |
path | path[3] = 2 | path[3] = 4,path[5] = 4 | path[5] = 3 | ||
visited | { v 0 , v 2 v_0,v_2 v0,v2} | { v 0 , v 2 , v 4 v_0,v_2,v_4 v0,v2,v4} | { v 0 , v 2 , v 4 , v 3 v_0,v_2,v_4,v_3 v0,v2,v4,v3} | { v 0 , v 2 , v 4 , v 3 , v 5 v_0,v_2,v_4,v_3,v_5 v0,v2,v4,v3,v5} |
- 最终结果 \color{red}{最终结果} 最终结果
v | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
visited | true | false | true | true | true | true |
val | 0 | ∞ \infin ∞ | 10 | 50 | 30 | 60 |
path | -1 | -1 | 0 | 4 | 0 | 3 |
迪杰斯特拉算法的时间复杂度: \color{red}\text{迪杰斯特拉算法的时间复杂度:} 迪杰斯特拉算法的时间复杂度:
O ( n 2 ) O(n^2) O(n2)
2.9 图——弗洛伊德算法
**求最短路径的第二种方式——弗洛伊德算法,但是它可以求带负权值的图**
算法实现: \color{red}{算法实现:} 算法实现:
引入的数据结构:
二维数组path[ i i i][ j j j]:最短路径上顶点 v j v_j vj的前一项顶点的序号
二维数组val[ i i i][ j j j]:记录顶点 v i v_i vi和 v j v_j vj之间的最短路径长度
void Floyd(AMGraph G){
for(int i = 0; i < G.vexnum; i++){
for(int j = 0; j < G.vexnum; j++){
val[i][j] = G.arcs[i][j]; // 初始化
if(val[i][j] < MaxInt && i != j) path[i][j] = i; // 初始有边的顶点的前驱
else path[i][j] = -1; // 无边就直接为-1
}
}
for(int k = 0; k < G.vexnum; k++){
for(int i = 0; i < G.vexnum; i++){
for(int j = 0; j < G.vexnum; j++){
if(val[i][k] + val[k][j] < val[i][j]){
val[i][j] = val[i][k] + val[k][j];
path[i][j] = path[k][j];
}
}
}
}
}
弗洛伊德算法的时间复杂度: \color{red}\text{弗洛伊德算法的时间复杂度:} 弗洛伊德算法的时间复杂度:
O ( n 3 ) O(n^3) O(n3)
2.10 查找——顺序查找
数据结构定义 : \color{red}{数据结构定义:} 数据结构定义:
// 数据元素定义
typedef struct{
KeyType key;
InfoType otherinfo;
}ElemType;
// 顺序表
typedef struct{
ElemType *R;
int length;
}SSTable;
**普通顺序查找**
int Search(SSTable ST,KeyType key){
for(int i = ST.length; i >= 1; i--){ // 闲置ST.R[0]不用
if(ST.R[i] == key){
return i; // 返回位置
}
}
return 0; // 未找到返回0
}
**设置监视哨的顺序查找**
int Search(SSTable ST,KeyType key){
ST.R[0].key = key;
for(int i = ST.length; ST.R[i].key != key; i--);
return i;
}
算法的平均查找长度 ( A S L ) : \color{gray}{算法的平均查找长度(ASL):} 算法的平均查找长度(ASL):
A S L = 1 n ∑ i = 1 n i = n + 1 2 ASL=\dfrac{1}{n}\sum\limits_{i=1}^ni=\dfrac{n+1}{2} ASL=n1i=1∑ni=2n+1
算法的时间复杂度: \color{gray}{算法的时间复杂度:} 算法的时间复杂度:
O ( n ) O(n) O(n)
2.11 查找——折半查找
折半查找也称二分查找,效率较高,但是要求表必须是线性表且已经排好序。
int Search_Bin(SSTable ST,KeyType key){
int low = 1;
int high = ST.length; // 设置查找区间
while(low <= high){
int mid = (low + high) / 2;
if(key == ST.R[mid].Key) return mid;
else if(key < ST.R[mid].Key) high = mid - 1;
else low = mid + 1;
}
}
算法的平均查找长度 ( A S L ) : \color{gray}{算法的平均查找长度(ASL):} 算法的平均查找长度(ASL):
A S L = n + 1 n log 2 ( n + 1 ) − 1 ASL=\dfrac{n+1}{n}\text{log}_2(n+1)-1 ASL=nn+1log2(n+1)−1
算法的时间复杂度: \color{gray}{算法的时间复杂度:} 算法的时间复杂度:
O ( log n ) O(\text{log}n) O(logn)