本文主要是介绍966,967吉大数据结构笔记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
数据结构
辅导书本无非王道数据结构和天勤,如果你把这两本书看了好几遍,可以看一下数据结构1800,这本书非常不错,它长下面这样子,时间充裕的话就看,紧张的话就直接刷真题吧。
由于这本书题量过大,建议只看链表,树,图这三个章节,这三个章节侧重选择题和编码题,一些填空题可以忽略,吉大不考这类题型,如果你时间充裕那上面的请自动忽略。还有最重要的一点就是真题,这个务必要过几遍,达到看到题就会做的水准,差不多就130+的水平,吉大的题目不难,很多都是原题,还有以下的一些题型。
顺便一提,今年树和图的算法大题一个都没考。。。。。全是链表还有千年没命过题的文件操作,只能说吉大命题太诡异了。大家务必要重温一下文件操作,很多东西要复习全面,基于去年没考树图代码题,今年考的可能性很大,请大家务必注意,链表大题也不要丢下,也可能杀个回马枪,总之,复习要全面吧。
以下的代码仅供参考,所以部分代码没有备注,每个人的代码习惯不一样,所以大家重点注意的是命题风格以及题型。
以下的所有题型来源于1800题,王道,天勤以及历年真题。
链表题
这里我只写几种常见的类型,还有更多请把王道课后习题多做几遍
//链表的插入排序
void insert(Linklist &L){Lnode *p = L->next; //如果你看了王道或者天勤应该知道我定义的结构体Lnode *pre;Lnode *r = p->next;p->next = NULL;p=r;while(p!=NULL){r = p->next; //记录下一节点pre = L;while(pre->next != NULL && pre->next->data < p->data)pre = pre->next; //找到插入位置p->next = pre->next; // 这行和下面一行都是插入操作pre->next = p;p = r; //向前推进一个节点}
}
树变形
1,改进的层序遍历(非递归实现计算树的深度以及宽度)
void level(BTNode *p){int front,rear,level = 0,length = 0;int count = 0,width = 1,cursor; //这里width为宽度,level为深度BTNode *q;if(p != NULL){rear = (reat+1)%maxsize;que[rear] = p;cursor = rear;while(front != rear){front = (front+1)%maxsize;count ++;q = que[front];if(q->lchild != NULL){rear = (rear+1)%maxsize;que[rear] = q->lchild;}if(q->rchild != NULL){rear = (rear+1)%maxsize;que[rear] = q->rchild;}if(front == cursor){ //是否扫描完一层cursor = rear; //将下一层的最后一个节点赋值给扫描器level ++; //层数+1width = count > width ? count : width; //取最大宽度count = 0; //重新计算下一层}}}
}
图算法
邻接矩阵的图遍历算法
首先,需要我们掌握图的结构体
typedef struct{char data;char info;
} vertexType;
typedef struct {int edges[maxsize][maxsize]int vnums,enums;vertexType vex[maxsize]
}mGraph
基于邻接矩阵的非递归深度优选遍历
void DFS_Nonrecursion(mGraph *G, int visit[], int stackVT){int stack[maxsize];int top = -1;int i,j,curVT;stack[++top] = stackVT;while(top != -1){curVT = stack[top--];cout<<G->vex[curVT].data;for( i = G->vnums-1; i>0; i--){if(G->edges[curVT][i] == 1 && visit[i] == 0){stack[++top] = i;visit[i] = 1;}}}
}
基于邻接矩阵的广度优先遍历
void BFS(mGraph *G, int visit[], int startVT){int i,queue[maxsize],front=0,rear = 0,vertex;queue[++rear] = startVT;while(front != rear){front = (front+1)%maxsize;vertex = queue[front];cout<<G->vex[vertex].data;for(i = 0; i<G->nums;i++){if(G->edges[vertex][i] == 1 && visit[i] == 0){rear = (rear+1) % maxsize;queue[rear] = i;visit[i] = 1;}}}
}
基于邻接矩阵的深度优先遍历(递归)
void DFS(mGraph *G, int visit[], int startVT){int j;cout<<G->vex[startVT].data;for(j = 0; j<G->vnums; j++){if(G->edges[starVT][j] == 1 && visit[j] == 0){visit[j] = 1;DFS(G,visit,j);}}
}
判断图是否有回路(邻接矩阵,有向图)
int flag = 0;
void IsCircle(int n, int v){if(flag == 1)return; //回路for(int i = 0; i<n; i++){if(visit[i] == 0 && a[v][i] == 1){visit[i] = 1;IsCircle(n,i);visit[i] = 0;}else if(a[v][i] == 1 && v!= i) flag = 1;}
}
书上有拓扑排序的解决方案,邻接表的实现(有向图)天勤
下面是无向图回路的判断(连通图)
void DFS(AGraph *G, int v, int &flag){ArcNode *p;if(visit[v] == 1){flag = 1;return;}visit[v] = 1;p = G->adjlist[v].firstArc;while(p!=NULL){DFS(G, p->adjvex,flag);p = p->nextarc;}
}至此完毕
如果是非连通图
for(i = 0; i<G->n; i++)if(visit[i] == 0)DFS(G,i,flag);
总结下来:
类型 | 时间复杂度 | 空间复杂度 |
---|---|---|
邻接矩阵 | o(n^2) | o(n^2) |
邻接表 | o(n+e) | o(n) |
后序非递归模板
为了区分同一节点的两次出栈,设置flag。
flag = 1,表示第一次出栈,只遍历完左子树,该节点不能访问
flag = 0, 表示第二次出栈,遍历完右子树,可以访问
typedef struct element{BTNode * ptr;int flag;
}element;
void postorder(BTNode *bt){int top = -1;element *s[maxsize];while(bt != NULL || top != -1){while(bt != NULL){top ++;s[top].ptr = bt;s[top].flag = 1;bt = bt->lchild;}// 标记一while(top != -1 && s[top].flag == 2){//标记二bt = s[top--].ptr;visit[bt->data);}if(top!=-1){s[top].flag = 2;bt = s[top].ptr->rchild;}}
}以上是后序非递归的模板
后序非递归模板可用于根节点到某一节点的路径(在标记一处写代码),也可以求公共祖先(标记二),也可以交换左右子树
求p,q节点的最近公共祖先
BTNode *CommonAncester(BTNode *bt, BTNode*p, BTNode *q){if(bt == NULL || bt == p || bt == q ) return bt;BTNode *left = CommonAncester(bt->lchild,p,q);BTNode *right = CommonAncester(bt->rchild,p,q);if((left == p && right == q) || (left ==q || right == p))return bt;return (left != NULL} ? left:right;
}
// 如果p,q 分别在左右子树中,则根节点就是最近的公共祖先,分别对左右子树进行递归,
// 如果某一子树返回为空,则证明返回上一层寻找。
n个节点的完全二叉树存放在数组A[n]中,建立一棵用二叉链表表示的二叉树根用tree指向。
BTNode *Create(ElementType a[], int i){BTNode * tree;if(i <= n){tree = (BTNode) malloc(sizeof(BTNode));tree->data = A[i];if(2*i > n)tree->lchild = NULL;elsetree->lchild = Create(A, 2*i);if(2*i+1>n)tree->rchild = NULL;elsetree->rchild = Create(A, 2*i+1);}return tree;
}
对上面那棵树进行前序非递归遍历
void preorder(ElemType bt[], int n){int i = 1,top = 0,s[];while(i<= n || top > 0){while(i<n){visit(bt[i]); //访问根节点if(2*i+1 <= n)s[++top] = 2*i+1; //柚子树入栈i = 2*i; //不断访问左子树}if(top>0)i = s[top--]; //开始访问柚子树}
}
由二叉树的中序遍历和后序遍历建立二叉树
BiTree IntoPost(ElemType in[], post[], int l1, int h1, int l2, int h2){//in和post是中序序列和后序序列,l1,h1是第一个和最后一个节点的下标BiTree bt = (BiTree)malloc(sizeof(BiNode));bt->data = post[h2]; //后序序列最后一个元素是根节点for(int i = l1;i<=h1;i++){if(in[i] == post[h2])break; //在中序序列中查找根节点}if(i == l1)bt->lchild = NULL;elsebt->lchild = IntoPost(in,post,l1,i-1,l2,l2+i-l1-1);if(i == h1)bt->rchild = NULL;elsebt->rchild = IntoPost(in, post, i+1, h1, l2+i-l1,h2-1);return bt;
}
已知满二叉树先序遍历序列,求其后序遍历序列
void change(char pre[], int L1, inr R1, char post[], int L2, int R2){if(L1 <= R2){post[R2] = pre[L1];change(pre, L1+1,(L1+1+R1)/2, post, L2, (L2+R2-1)/2);change(pre, (L1+1+R1)/2+1, R1, post, (L2+R2-1)/2+1, R2-1);}
}先序遍历的第一个元素为根节点,将除第一个元素之外的元素序列分为前后两个相等的序列,前一半
为左子树的节点,后一半为柚子树的节点。只需将根节点移至序列的末尾,再分别递归处理前一半,后一半。
求有向图中邻接表的入度
void indegree(Adjlist g){for(i = 0; i<n; i++){num = 0; //入度for(j = 0;j<n;j++)if(i!=j){p = g[i].firstarc;while(p){if(p->adjvex == i)num++;p = p->next;}}}
}another way:
for(i =0;i<n;i++){p = G->adjlist[i].firstarc;while(p){indegree[p->adjvex]++; p = p->nextarc;}
}
下面是出度的算法
int out;
void outdegree(AdjList g){for(int i = 0; i< n; i++){out = 0;p = g[i].firstarc;while(p){out++;p = p->nextarc;}cout<<"顶点"<<g[i].vexdata<<"的出度为"<<out;}
}
邻接矩阵转邻接表
void AdjmatrixToAdjList(AdjMatrix gm, AdjList gl){for(i = 0; i<n; i++){cin>>gl[i].vertex;gl[i].firstarc = NULL;} //初始化for(i = 0;i<n;i++){for(j = 0;j<n;j++){if(gm[i][j] == 1){p = (ArcNode*)malloc(sizeof(ArcNode));p->adjvex = j;p->next = g[i].firstarc;g[i].firstarc = p;}}}
}
已知图邻接链表,设计算法生成相应的逆邻接表,时间复杂度为o(n+e)
void InvertAdjList(AdjList gin, AdjList gout){for(int i = 0; i<n; i++){gin[i].vertex = gout[i].vertex;gin[i].firstarc = NULL;for(int i = 0; i<n;i++){p = gout[i].firstarc;while(p!= NULL){j = p->adjvex;s= (ArcNode*)malloc(sizeof(ArcNode));s->adjvex = i;s->next = gin[j].firstarc;gin[j].firstarc = s;p = p->next;}}}
}
二叉树的查找与删除(真题出现过)
bool DeleteBST(BiTree *T, int key){if(T == NULL)return false;else if(key == T->data){Delete(T);return false;}else if(key < T->data)return DeleteBST(T->lchild, key);elsereturn DeleteBST(T->rchild, key);
}void Delete(BiTree *T){BiTree L;if(T->lchild == NULL && T->rchild == NULL)T = NULL;else if(T->rchild != NULL && T->lchild == NULL)T = T->rchild;else if(T->rchild == NULL && T->lchild != NULL)T = T->lchild;else{L = T->lchild;while(L->rchild) L = L->rchild; //标注一L->rchild = T->rchild; // 标注二T = T->lchild; // 标注三}
}这里的标注一是寻找最右子树,标注二是T的柚子树接到左子树的最右孩子的柚子树,
标注三是T的左子树直接作为T父节点的子树
图的深度非递归遍历
void DFS(graph g, int v){int i;arcnode *p;int visited[vnum],top = -1,stack[vnum];for(i = 0; i<g.vexnum;i++){visited[i] = 0;}visit(v);top++;stack[top] = v;visited[v] = 1;while(top > 0){v = stack[top];--top;p = g.adjlist[v].firstarc;while(p!=NULL && visited[p->adjvex] == 1)p = p->nextarc;if(p == NULL) --top;else{v = p->adjvex;visit(v);visited[v] = 1;top++;stack[top] = v;}}
}
打印vi到vj所有长度为L的简单路径。这类问题有模板,基本上解题思路大同小异,如果理解了这个问题。
int visited[Vnum],A[Vnum];
void dfs(graph *g, int vi, int vj, int L, int d){int v,i;arcnode *p;visited[vi] = 1;d++;A[d] = vi;if(vi == vj && d == L){cout<<"一条路径"; // dfs模板for(i = 0; i<=d; i++)cout<<A[i];return; // 退出这层递归}p = g->adjlist[vi].firstarc;while(p != NULL){v = p->adjlist;if(visited[v] == 0)dfs(g,v,vj,L,d);p = p->nextarc;}visited[vi] = 0;--d; //擦除访问标记,使顶点可以重用
}
hash 函数的构造方法
- 直接定址法
- 数字分析法
- 平方取中法
- 除留余数法
处理冲突的方法
- 开放定址法
- 链地址法
开放定址法包括 线性探查法(容易堆积),平方探查法(不能探查表上的所有单元)
a = 装填因子 = {关键字个数 n} / {表的长度 m} a越大,发生冲突的可能性越大,反之则反
散列表的平均查找长度依赖于a,不直接依赖于n 或 m
关于一些方法的总结:
数组的去重,可以先排序后用插入排序的思想,还可以用一个数组来计算元素的个数,计数排序的思想。
用dfs来拓扑排序
void DFS(Graph G, int v){visited[v] = 1;visit(v);p = G->adjlist[v].firstarc;while(p){if(visited[p->adjvex] == 0)DFS(G, p->adjvex);p = p->nextarc;}time = time+1;finishTime[v] = time;
}for(v = 0;v<G.n;v++)if(!visited[v])DFS(G,v);finishTime数组中结束的时间从小到大排列,既为TOPSort
总结图时间复杂度:
类型 | 时间复杂度 | 空间复杂度 |
---|---|---|
广+表 | o(n+e) | o(n) |
深+表 | o(n+e) | o(n) |
广+矩阵 | o(n^2) | o(n) |
深+矩阵 | o(o^2) | o(n) |
非递归的快排:
void quickSort_Nonrecursion(int a[], int n){int stack[maxsize];int top = -1;stack[++top] = n-1;while(top >= 0){int high = stack[top--];int low = stack[top--];int m = partition(a,low,high);if(m+1 < high){stack[++top] = m+1;stack[++top] = high;}if(low < middle -1){stack[++top] = low;stack[++top] = middle-1;}}
}partion函数在王道292页
MST专题
prim函数和kruskal函数都是针对无向图
prim
时间复杂度o(v^2),不依赖|E|,因此适用于稠密图
kruskal
时间复杂度o(log(E)),其实它的时间复杂度由排序算法决定的,而排序规模是由边数e决定的,因此适用于稀疏图
Dijkstra
见天勤,内容太多了。。。。
Floyd
它的核心思想,对每个中间点进行更新一次距离,中间点为两个顶点的必经点。
还有一些需要注意的是归并排序的比较次数,这个笔记有点多,就不一一写了。需要的可以私聊我拍笔记照片给你。
还有Dijkstra图的画法,这个今年考到了,下面给个例子。
顶点 | 第一趟 | 第二趟 |
---|---|---|
a | (a,b)2 | … |
b | 无穷 | … |
c | … | (a,b,d)5 |
集合 | a,b | {a,b,c} |
这个好像王道还是天勤上有一个例子,和上面的差不多。
基本总结的差不多,最后祝大家考上理想的大学
传送门 吉大高级程序设计笔记
这篇关于966,967吉大数据结构笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!