966,967吉大数据结构笔记

数据结构

辅导书本无非王道数据结构天勤,如果你把这两本书看了好几遍,可以看一下数据结构1800,这本书非常不错,它长下面这样子,时间充裕的话就看,紧张的话就直接刷真题吧。
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 ++;    //层数+1
				width = 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;
		else
			tree->lchild = Create(A, 2*i);
		if(2*i+1>n)
			tree->rchild = NULL;
		else
			tree->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;
	else
		bt->lchild = IntoPost(in,post,l1,i-1,l2,l2+i-l1-1);
	if(i == h1)
		bt->rchild = NULL;
	else
		bt->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);
		else
			return 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}

这个好像王道还是天勤上有一个例子,和上面的差不多。

基本总结的差不多,最后祝大家考上理想的大学

传送门 吉大高级程序设计笔记

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值