考研数据结构-Data Structure of Kao Yan
Zhou Bo(Suzhou University of Science and Technology)
Email:xh_zhoubo@163.com QQ:2364339378
主要王道代码风格太乱了(怀疑不是一个人敲的,我统一了一下)
有问题的地方欢迎评论区指正~ 参考:王道+一些算法模板
线性表那一块应该主要考察算法要不就是双指针,要不就是分治,实在不行就是暴力,自己主要练一下链表那块的规范
因此主要整理树(重中之重)、图、串这一块的重点代码题
考研代码的描述思想也很重要,要不阅卷老师可能看不懂…打低分
1.树[后期重点看递归思想的题目]
二叉树
1.1 已知一棵二叉树按顺序结构进行存储,设计一个算法,求编号分别为i和j的两个结点的最近的公共祖先的值。
首先我们知道二叉树中任一结点i的双亲结点的编号为i/2.
1)若i>j,则i所在层次大于等于j所在层次.结点i的双亲结点编号为i/2.
若i/2=j,则结点i/2是原结点i和结点j的最近公共祖先结点;若i/2 ≠ \neq =j,则令i=i/2,即以该结点i的双亲结点为起点,递归查找
2)若j>i,则j所在层次大于等于i所在层次.结点j的双亲结点编号为j/2.
若j/2=j,则结点j/2是原结点i和结点j的最近公共祖先结点;若j/2 ≠ \neq =i,则令j=j/2,同上
ElemType SearchCommAncestor(SqTree T,int i, int j){
if(T[i]!='#'&&T[j]!='#'){//结点存在
while(i!=j){
if(i>j) i/=2;
else j/=2;
}
return T[i];
}
}
1.2 递归写法-先序遍历(根左右)、 中序遍历(左根右)、后序遍历(左右根)【考研很多题目都是从这里延申的】
//每个结点都访问且仅访问一次,均为O(n)
//最坏情况:二叉树是有n个结点且深度为n的单支树,空间:O(n)
//先序遍历
void PreOrder(BiTree T){
if(T!=null){
visit(T->data);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
//中序遍历
void InOrder(BiTree T){
if(T!=null){
InOrder(T->lchild);
visit(T->data);
InOrder(T->rchild);
}
}
//后序遍历
void PostOrder(BiTree T){
if(T!=null){
PostOrder(T->lchild);
PostOrder(T->rchild);
visit(T->data);
}
}
1.3 以上三种遍历的非递归写法
先序遍历非递归:
在中序遍历的基础上只需把访问结点操作放在入栈操作前面
void InOrder(){
InitStack(S);
BiTree p=T;
while(p||!IsEmpty(S)){
if(p){
visit(p->data);Push(S,p);
p=p->lchild;
}
else {
Pop(S,p);
p=p->rchild;
}
}
}
中序遍历非递归:
1)沿着根的左孩子依次入栈,直到左孩子为空(说明已经找到可以输出的结点)
2)栈顶元素出栈访问:若右孩子非空则执行1);若其右孩子为空,继续执行2)
void InOrder(){
InitStack(S);
BiTree p=T;//p是遍历指针
while(p||!IsEmpty(S)){
if(p){
Push(S,p);
p=p->lchild;
}
else {
Pop(S,p);visit(p->data);
p=p->rchild;
}
}
}
后序遍历非递归:【可以用来求解根到某结点的路径或两个结点的最近公共祖先等】
1)沿着根的左孩子依次入栈,直到左孩子为空
2)读栈顶元素:若其右孩子不空且未被访问过,将右子树转执行1);否则栈顶元素出栈并访问
第2)步,必须分清返回时是从左子树返回的还是右子树返回的,因此设定一个辅助指针r,指向最近访问过的结点
void PostOrder(){
InitStack(S);
BiTree p=T;
r=NULL;
while(p||!IsEmpty(S)){
if(p){
Push(S,p);
p=p->lchild;
}
else {
GetTop(S,p);
if(p->rchild&&p->rchild!=r)p=p->rchild;//右子树转执行1)
pop(S,p);
visit(p->data);
r=p;//记录最近访问过的结点
p=NULL;//结点访问完后重置p指针
}
}
}
}
1.4 层次遍历算法(本质即BFS,用队列维护)
void LevelOrder(BiTree T){
InitQueue(Q);
BiTree p;
EnQueue(Q,T);
while(!IsEmpty(Q)){
DeQueue(Q,p);
visit(p);
if(p->lchild!=NULL) EnQueue(Q,p->lchild);
if(p->rchild!=NULL) EnQueue(Q,p->rchild);
}
}
遍历是二叉树各种操作的基础,可以在遍历过程中对结点进行各种操作,例如,对于一颗已知树求结点的双亲、求结点的孩子结点、求二叉树的深度、求二叉树的叶子结点个数、判断两棵二叉树是否相同等
1.5 试给出二叉树的自上而下、从右到左的层次遍历算法
利用原有的层次遍历算法,出队的同时将各结点入栈,在所有结点入栈后再从栈顶开始访问
void InvertLevelOrder(BiTree T){
Stack S;Queue Q;
if(T!=NULL){
InitStack(S);
InitQueue(Q);
EnQueue(Q,T);
while(!IsEmpty(Q)){
Dequeue(Q,p);
Push(S,p);//改动主要是在这里
if(p->lchild) EnQueue(Q,p->lchild);
if(p->rchild) EnQueue(Q,p->rchild);
}
while(!IsEmpty(S)){
Pop(S,p);
visit(p->data);
}
}
}
1.6 假设二叉树采用二叉链表存储结构,设计一个非递归算法求二叉树的高度.
因为层次遍历是将每一层元素都输出后才遍历下一层,所以我们可以在遍历完每一层的最后一个结点后高度+1
设置last指针只想当前层的最右结点,每次层次遍历出队时和last比较,若两者相等,则层数+1,并让last指向下一层的最右结点,直到遍历完成.level值即为高度.
int Btdepth(BiTree T){
if(!T) return 0;//树空,高度为0
int front=-1,rear=-1;//队头队尾
int last=0,depth=0;
BiTree Q[MaxSize];
Q[++rear]=T;
BiTree p;
while(front<rear){
p=Q[front++];
if(p->lchild) Q[rear++]=p->lchild;
if(p->rchild) Q[rear++]=p->rchild;
if(front==rear){
depth++;
last=rear;
}
}
return depth;
}
当然也可以用递归,实现如下:
int Btdepth(BiTree T){
if(T==NULL) return 0;
int ldepth = Btdepth(T->lchild);
int rdepth = Btdepth(T->rchild);
return max(ldepth+1,rdepth+1);
}
如果是求某层的结点个数、每层的结点个数、树的最大宽度等,我的实现是:
int width[MaxSize],num=0,MaxWidth=-1;
int Btdepth(BiTree T){
if(!T) return 0;//树空,高度为0
int front=-1,rear=-1;//队头队尾
int last=0,depth=0,num;
BiTree Q[MaxSize];
Q[++rear]=T;
BiTree p;
while(front<rear){
p=Q[front++];
if(p->lchild) Q[rear++]=p->lchild,num++;
if(p->rchild) Q[rear++]=p->rchild,num++;
if(front==rear){
//如果是求每层或者某层的结点个数
width[depth++]=num;
//如果是求树的最大宽度
MaxWidth=max(num,MaxWidth);
last=rear;
num=0;
}
}
return depth;
//return MaxWidth
}
1.7 设一棵二叉树中各结点的值互不相同,其先序序列和中序遍历算法分别存在两个一维数组A[1…n]和B[1…n]中,试编写算法建立该二叉树的链表.
1)根据先序序列确定树的根结点
2)根据根结点在中序序列中划分出二叉树的左、右子树结点包含哪些结点,在先序序列的次序中的次序确定子树的根结点,即回到步骤1),如此重复,直到每棵子树仅有一个结点(该树的根结点为止)
BiTree PreInCreat(ElemType A[],ElemType B[],int l1,int h1,int l2,int h2){
//l1,h1为先序的第一和最后一个结点下标,l2,h2为中序的第一和最后一个结点下标
//初始调用时l1=l2=1,h1=h2=n;
root (BiTNode*)malloc(sizeof(BiTNode));
root->data=A[l1];
int i;//注意这里是global variable
for(i=l2;B[i]!=root->data;i++);
llen=i-l2,rlen=h2-i;
if(llen) root->lchild=PreInCreat(A,B,l1+1,l1+llen,l2,l2+llen-1);
else root->lchild=NULL;
if(rlen) root->rchild=PreInCreat(A,B,h1-rlen+1,h1,h2-rlen+1);
else root->rchild=NULL;
return root;
}
1.8 二叉树按二叉链表形式存储,写一个判别给定二叉树是否是完全二叉树的算法.
根据完全二叉树的定义,具有n个结点的完全二叉树与满二叉树中编号从1~n的结点一一对应.
采用层次遍历,将所有结点加入队列(包括空结点).遇到空结点时,查看其后是否有非空结点.若有,则二叉树不是完全二叉树.
bool isComplete(BiTree T){
InitQueue(Q);
if(!T) return 1;//空树为满二叉树
EnQueue(Q,T);
while(!IsEmpty(Q)){
DeQueue(Q,p);
if(p) EnQueue(Q,p->lchild),EnQueue(Q,p->rchild);
else {
while(!IsEmpty(Q)){
DeQueue(p);
if(p) return 0;//结点非空,则二叉树为非完全二叉树
}
}
}
}
1.9 假设二叉树采用二叉链表存储结构存储,试设计一个算法,计算一棵给定二叉树的所有双分支结点的个数.
双分支结点:既有左孩子又有右孩子的结点
递归模型如下:
if b=NULL f(b)=0;
if *b为双分支结点, f(b)=f(b->lchild)+f(b->rchild)+1
else f(b)=f(b->lchild)+f(b->rchild)
int cntDsonNodes(BiTree T){
if(b==NULL) return 0;
else if(b->lchild!=NULL&&b->rchild!=NULL)
return cntDsonNodes(T->lchild)+cntDsonNodes(T->rchild)+1;
else
return cntDsonNodes(T->lchild)+cntDsonNodes(T->rchild);
}
1.10 设树是一棵采用链式结构存储的二叉树,试编写一个把树B中所有结点的左右孩子进行交换的函数.
1)首先交换b结点的左孩子的左、右子树
2)然后交换b结点的右孩子的左、右子树
3)最后交换b结点的左、右子树
void Swap(BiTree T){
if(T){
swap(T->lchild);
swap(T->rchild);
temp=b->lchild;
b->lchild=b->rchild;
b->rchild=temp;
}
}
1.11 假设二叉树采用二叉链表结构存储,设计一个算法,求先序遍历序列中第k(1<=k<=二叉树中结点个数)个结点的值.
设置一个全局变量i记录已访问过的结点的序号,其初值是根结点在先序序列中的序号,即1.
当二叉树T为空时返回特殊字符’#’;
当i==k时表示找到了满足条件的结点,返回T->data;
当i ≠ \neq =k时,递归地在左子树中查找,若找到则返回该值,否则继续递归地在右子树中查找,并返回结果.
int i=1;
ElemType PreNode(BiTree T,int k){
if(b==NULL) return '#';
if(i==k) return b->data;
i++;
ch=PreNode(T->lchild,k);
if(ch!='#') return ch;
ch=PreNode(T->rchild,k);
if(ch!='#') return ch;
}
如果求中序、后序第k个呢?
1.12 设有一棵满二叉树(所有结点值均不同),已知其先序序列为pre,设计一个算法求后序序列post.
对于一般二叉树,仅根据先序或后序不能确定另一个遍历序列.但对满二叉树,任意一个结点的左、右子树均有相等的结点数;同时,先序序列的第一个结点作为后序序列的最后一个结点,由此得到将先序序列pre[l1…h1]转换为post[l2…h2]的递归模型如下:
f(pre,l1,h1,post,l2,h2)=不做任何事情,hl<l1时
f(pre,l1,h1,post,l2,h2) post[h2]=pre[l1] 其他情况
取中间位置half=(h1-l1)/2;
将pre[l1+1,l1+half]左子树转换为post[l2,l2+half-1] 即f(pre,l1+1,l1+half,post,l2,l2+half-1);
将pre[l1+half+1,h1]右子树转换为post[l2+half,h2-1]即f(pre,l1+half+1,h1,l2+half,h2-1).
void PreToPost(ElemType pre[],int l1,int h1,ElemType post[],int l2,int h2){
int half;
post[h2]=pre[h1];
half=(h1-l1)/2;
PreToPost(pre,l1+1,l1+half,post,l2,l2+half-1);//转换左子树
PreToPost(pre,l1+half+1,h1,post,l2+half,h2-1);//转换右子树
}
1.13 设计一个算法将二叉树的叶结点按从左到右的顺序连成一个单链表,表头指针为head.二叉树按二叉链表方式存储,链接时用叶结点的指针域存放单链表指针.
先序、中序和后序遍历对于叶结点的访问都是从左到右,这里以中序遍历为例实现.
设置前驱结点指针pre,初始为空.第一个叶结点由指针head指向,遍历到叶结点时,就将它前驱的rchild指针指向它,最后一个指针为空.
//时间:O(n),空间:O(n)
LinkedList head,pre=NULL;
LinkedList InOrder(BiTree T){
if(T){
InOrder(T->lchild);
if(T->lchild==NULL&&T->rchild==NULL){//叶结点
if(pre==NULL){
head=T;
pre=T;
}
else{
pre->rchild=T;
pre=T;
}
}
InOrder(T->rchild);
pre->rchild=NULL;
}
return head;
}
1.14 试设计判断两棵二叉树是否相似的算法.所谓二叉树T1和T2相似,指的是T1和T2都是空的二叉树或都只有一个根结点;或T1的左子树和T2的左子树是相似的,且T1的右子树和T2的右子树都是相似的.
递归:若T1和T2都是空树,则相似;若有一个为空另一个不空,则必不相似;否则递归地比较它们的左、右子树是否相似.递归函数的定义如下:
1)if T1T2NULL f(T1,T2)=1;
2)else if T1NULL || T2NULL f(T1,T2)=0;
3)else f(T1,T2)=f(T1->lchild,T2->lchild) && f(T1->rchild,T2->rchild)
int IsSimilar(BiTree T1,BiTree T2){
int leftS,rightS;
if(T1==NULL&&T2==NULL) return 1;
else if(T1==NULL||T2==NULL) return 0;
else {
leftS=IsSimilar(T1->lchild,T2->rchild);
rightS=IsSimilar(T1->rchild,T2->rchild);
return leftS&&rightS;
}
}
1.15 二叉树的带权路径长度(WPL)是二叉树中所有叶结点的带权路径长度之和.给定一棵二叉树T,采用二叉链表存储,结点结构为left,weight,right.其中叶结点的weight域保存该结点的非负权值.设root为指向T的根结点的指针,请设计T的WPL算法.
采用先序遍历,用一个static变量记录wpl,把每个结点的深度作为递归的一个参数传递
1)若该结点是叶结点,则变量+该结点的深度与权值之积
2)若该结点是非叶结点,则左子树不空的时候,对左子树调用递归算法,右子树非空,对右子树调用递归算法,深度参数均为本结点的深度参数+1
3)最后返回计算出的wpl
typedef struct BiTNode{
int weight;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
int WPL(BiTree root){
return wpl_PreOrder(root,0);
}
int wpl_PreOrder(BiTree root, int depth){
static int wpl=0;
if(root->lchild==NULL&&root->rchild==NULL)
wpl+=depth*root->weight;
if(root->lchild!=NULL)
wpl_PreOrder(root->lchild,depth+1);
if(root->rchild!=NULL)
wpl_PreOrder(root->rchild,depth+1);
return wpl;
}
树与森林
1.16 编程求以孩子兄弟表示法存储的森林的叶子结点数.
typedef struct node{
Elemtype data;
struct node *fch,*nsib;
}*Tree;
int Leaves(Tree T){
if(T==NULL) return 0;
if(T->fch==NULL) return 1+Leaves(T->nsib);
else return Leaves(T->fch)+Leaves(T->nsib);
}
1.17 以孩子兄弟链表为存储结构,请设计递归算法求树的深度.
int Height(CSTree T){
int hc,hs;
if(T==NULL)return 0;
else {
hc=Height(T->lchild);
hs=Height(T->rchild);
if(hc+1>hs) return hc+1;
else return hs;
}
}
二叉树的应用(二叉排序树(BST)与平衡二叉树(AVL))
1.18 试编写一个算法判断给定的二叉树是否是二叉排序树.
对于二叉排序树来说,其中序遍历序列为一个递增有序序列.因此,对给定的二叉树进行中序遍历,若能始终保持前一个值比后一个值小,则说明该二叉树是一颗二叉排序树.
ElemType pre=-1e9;//保存当前结点前驱的值
int JudgeBST(BiTree T){
int ans1,ans2;
if(T==NULL)return 1;
else{
ans1=JudgeBST(T->lchild);
if(ans1==0||pre>=T->data) return 0;
pre=T->data;//保存当前结点的关键字
ans2=JudgeBST(T->rchild);
return ans2;
}
}
1.19 设计一个算法,求出指定结点在给定二叉排序树中的层次.
在二叉排序树中,查找一次就下降一层.因此查找该结点所用的次数就是该结点在二叉排序树中的层次.
int Level(BiTree T, BSTNode *p){
int cnt=0;
BiTree t=T;
while(T!=NULL){
cnt++;
while(t->data!=p->data){
if(p->data<t->data) t=t->lchild;
else t=t->rchild;
cnt++;
}
}
return cnt;
}
1.20 采用二叉树遍历的思想编写一个判定二叉树是否是平衡二叉树的算法.
设置二叉树的平衡标记为balance,标记返回二叉树T是否是平衡二叉树;h为二叉树的高度.
1)若T为空,则高度为0,balance=1;
2)若T仅有根结点,则高度为1,balance=1;
3)否则对T的左右子树进行递归运算,返回左、右子树的高度和平衡标记,T的高度为最高子树的高度+1.若左、右子树的高度差>1,则balance=0;若左、右子树的高度差小于等于1,且左、右子树均平衡时,balance=1,否则balance=0.
int Judge_AVL(BiTree T,int &balance,int &h){//balance为二叉树的平衡标记
int bl,br,hl,hr;//左、右子树的平衡标记和高度
if(T==NULL) h=0,balance=1,return balance;
else if(T->lchild==NULL&&T->rchild==NULL)h=1,balance=1,return balance;
else{
Judge_AVL(T->lchild,bl,h1);
Judge_AVL(T->rchild,br,hr);
h=(hl>hr?hl:hr)+1;
if(abs(hl-hr)<2) balance=bl&&br;
else balance=0;
}
return balance;
}
1.21 设计一个算法,从大到小输出二叉排序树中所有值不小于k的关键字.
二叉排序树中,最左下结点即为关键字最小的结点,最右下结点即为关键字最大的结点,只要求出这两个结点即可,而不需要比较关键字.
KeyType MinKey(BSTNode *T){
while(T->lchild!=NULL) T=T->lchild;
return T->data;
}
KeyType MaxKey(BSTNode *T){
while(bt->rchild!=NULL) T=T->rchild;
return T->data;
}
上面的代码二叉树指针都是用T表示的 下面统一用bt表示~ 感觉bt更形象一些 至于最终采用哪种看读者喜好
1.22 从大到小输出二叉排序树中所有值不小于k的关键字.
二叉排序树中右子树所有的结点值均大于根结点值,左子树中所有结点均小于根结点值.为了从大到小输出,先遍历右子树,再访问根结点,后遍历左子树.
void Output(BSTNode *bt,KeyType k){
if(bt==NULL)return;
if(bt->rchild!=NULL) Output(bt->rchild,k);
if(bt->data>=k) printf("%d",bt->data);
if(bt->lchild!=NULL) Output(bt->lchild,k);
}
1.23 编写一个递归算法,在一棵由n个结点的、随机建立起来的二叉排序树上查找第k(1<=k<=n)小的元素,并返回指向该结点的指针.要求O(log2n).二叉排序树中的每个结点中除data,lchild,rchild外增加1个count成员保存以该结点为根的子树中上的结点个数.
二叉排序树的根结点设为*bt
-
bt->lchild为空
1)k==1,即*bt为第k小的元素,查找成功
2)k$\neq$1,则第k小的元素必在*bt的右子树
-
bt->lchild非空
1)bt->lchild->count==k-1,即*bt为第k小的元素,查找成功
2)bt->lchild->count>k-1,则第k小的元素必在*bt的左子树,继续到 *bt的左子树中查找
3)bt->lchild->count<k-1,则第k小的元素必在*bt的右子树,继续到 *bt的右子树中查找
左右子树的搜索规则相同
BSTNode *Search_small(BSTNode *bt,int k){ if(k<1||k>bt->count) return NULL; if(bt->lchild==NULL){ if(k=1) return bt; else return Search_small(k-1); } else{ if(bt->lchild->count==k-1) return bt; if(bt->lchild->count>k-1) return search_Small(bt->lchild,k); if(bt->lchild->count<k-1) return search_Small(bt->rchild,k-(bt->lchild->count+1)); } }
其他例题:递归实现
1)统计二叉树中度为1的结点个数
2)统计二叉树中度为2的结点个数
3)统计二叉树中度为0的结点个数
4)统计二叉树的高度
5)统计二叉树的宽度
6)从二叉树中删去所有叶结点
7)计算指定结点*p的层次
8)计算二叉树中各结点中的最大元素值
9)交换二叉树中每个结点的两个子女
10)以先序次序输出一棵二叉树中所有结点的数据值及结点所在的层次
2.图[最短路径+最小生成树]
2.1朴素Dijkstra [O(n^2+m)]
关键:每当一个顶点加入集合S后,可能需要修改源点v0到集合V-S中可达顶点当前的最短路径长度
时间复杂是 O(n^2+m), n表示点数,m表示边数
int g[N][N]; // 存储每条边
int dist[N]; // 存储1号点到每个点的最短距离
bool st[N]; // 存储每个点的最短路是否已经确定
// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n - 1; i ++ )
{
int t = -1; // 在还未确定最短路的点中,寻找距离最小的点
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
// 用t更新其他点的距离
for (int j = 1; j <= n; j ++ )
dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true;
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
2.2 堆优化版Dijkstra [O(mlogn)](毕竟用到了堆优化+代码量大,不太会考,后期不看)
//时间复杂度O(mlogn), n 表示点数,m 表示边数
typedef pair<int, int> PII;
int n; // 点的数量
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储所有点到1号点的距离
bool st[N]; // 存储每个点的最短距离是否已确定
// 求1号点到n号点的最短距离,如果不存在,则返回-1
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1}); // first存储距离,second存储节点编号
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
2.3 Floyd[O(n^3)]
时间复杂度是 O(n^3), n 表示点数
初始化:
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
2.4 朴素版Prim求最小生成树
关键:不断选择与当前T中顶点集合距离最近的顶点,并将该顶点和相应的边加入T,每次操作后T的顶点数和边数+1
时间复杂度是 O(n^2+m), n表示点数,m表示边数
int n; // n表示点数
int g[N][N]; // 邻接矩阵,存储所有边
int dist[N]; // 存储其他点到当前最小生成树的距离
bool st[N]; // 存储每个点是否已经在生成树中
// 如果图不连通,则返回INF(值是0x3f3f3f3f), 否则返回最小生成树的树边权重之和
int prim()
{
memset(dist, 0x3f, sizeof dist);
int res = 0;
for (int i = 0; i < n; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
if (i && dist[t] == INF) return INF;
if (i) res += dist[t];
st[t] = true;
for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
}
return res;
}
2.5 Kruskal求最小生成树(代码感觉不会考 代码量大+并查集 后期不看)
关键:每个顶点自成一个连通分量,不断选取当前未被选取过且权值最小的边,添加的时候注意不能形成"环状"
时间复杂度是 O(mlogm), n表示点数,m表示边数
int n, m; // n是点数,m是边数
int p[N]; // 并查集的父节点数组
struct Edge // 存储边
{
int a, b, w;
bool operator< (const Edge &W)const
{
return w < W.w;
}
}edges[M];
int find(int x) // 并查集核心操作
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges, edges + m);
for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集
int res = 0, cnt = 0;
for (int i = 0; i < m; i ++ )
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if (a != b) // 如果两个连通块不连通,则将这两个连通块合并
{
p[a] = b;
res += w;
cnt ++ ;
}
}
if (cnt < n - 1) return INF;
return res;
}
3.KMP
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m)
{
j = ne[j];
// 匹配成功后的逻辑
}
}
4.BFS例-图中结点的层次
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}