说明 本文整合自王道和里昂学长的PPT(无盈利侵权必删),是完整的408代码大题解题方案,整合好的pdf文档可以联系我获取
快速排序 代码实现
快速排序算法以huafen()函数为核心
如何划分? 第一趟
首先将左指针L对应的数组元素设为mid值,我们之后要将mid值和其他值对比然后找到mid值该在的位置
先从R向前寻找第一个小于mid的值(如果没有R- -一直找),如果有,就把他和目前存着mid值的A[L]互换位置
再从L向后寻找第一个大于mid的值(如果没有L++一直找),如果有,就把上一步的A[R]换过来
第二趟
上一趟完成了第一次交换,把小于mid的换到了右边,而把大于mid的换到了左边
第二趟又在新的R找到了小于mid的元素,放在之前发现的左边覆盖,以此类推
最后一趟
当最后一趟开始时,若是L++导致L==R则
A[L]换上了右边更小一点的元素。
而下一步L++,已不满足L<R的条件。而这一步的L就是上一步的R(L==R ),A[R]的值已经迁移到了其左边。
因此设A[L]=mid,就把两者很好的拆开了
当最后一趟开始时,若是R- -导致L==R则
也就是上一趟将**大于mid的A[L]**的值放到右边时,将较大的值换到了A[R]中。
此时R- -到R==L ,因此此时A[L]中的存储的是已经是到了还没有**R–**时的右边。
因此A[L]=mid,即可拆开
快速排序算法 因此在快排中调用这段代码
先将A中的LR成以mid为中间的两段,左边小于mid,右边大于mid
我们的划分传来的是mid的位置,因此要想把整个数组排序好,只需要排好左右一直调用。
将数组的最左边L和mid的前一个位置进行划分,穷举下去即可解决
完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void Qsort (int A[], int L, int R) { if (L > R) return ; int M = huafen(A, L, R); Qsort(A, L, M-1 ); Qsort(A, M+1 , R); } int hualen (int A[], int L, int R) { int mid = A[L]; while (L < R) { while (A[R] >= mid && L < R) R--; A[L] = A[R]; while (A[L] <= mid && L < R) L++; A[R] = A[L]; } A[L] = mid; return L; }
划分函数的妙用 求数组中第k小元素的方法
按理说我们只需要快速排序好,再直接利用数组的随机查找特性找到A[k-1]就可以
但是huafen函数可以直接找到某个元素的具体位置,我们可以直接用M接收huafen函数传来的此时的位置
若这个位置比已知位置小,则k-1在M前面,则设L=M+1继续试
1 2 3 4 5 6 7 8 9 10 int func (int A[], int n, int k) { int L = 0 , R = n - 1 , M = 0 ; while (1 ) { M = huafor(A, L, R); if (M == k - 1 ) break ; else if (M > k - 1 ) R = M - 1 ; else if (M < k - 1 ) L = M + 1 ; } return A[k-1 ]; }
归并排序 归并排序用于两个数组排到一个数组里的排序中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int Merge (int A[], int N, int B[], int M, int C[]) { int i = 0 , j = 0 , k = 0 ; while (i < N && j < M) { if (A[i] <= B[j]) C[k++] = A[i++]; else C[k++] = B[j++]; } while (i < N) C[k++] = A[i++]; while (j < M) C[k++] = B[j++]; return 1 ; }
链表 查找+删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void deletX (LinkList L, int x) { LNode *pre = L; LNode *p = pre->next; while (p != NULL ) { if (p->data == x) { LNode *q = p; p = p->next; pre->next = p; free (q); } else { pre = p; p = p->next; } } }
查找+插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void InsertX (LinkList L, int x) { LNode *pre = L; LNode *p = pre->next; while (p != NULL ) { if (p->data > x) { break ; } else { pre = p; p = p->next; } } LNode *q = (LNode *)malloc (sizeof (LNode)); q->data = x; q->next = p; pre->next = q; }
头插法(原地逆转链表)
在勾画的绿色部分正是逆置的核心出装,将新插入的结点(在原链表中更靠后)不断放前,而实现逆置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void ListReserve (LinkList L) { LinkList head = (LNode *) malloc (sizeof (LNode)); head->next = NULL ; while (L->next != NULL ){ LNode *p = L->next; L->next = L->next->next; p->next = head->next; head->next = p; } L->next = head->next; free (head); }
尾插法保持原序
以下是我自己写的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void ListPart (LinkList C) { LinkList A=(LNode*)malloc (sizeof (LNode)) LinkList B=(LNode*)malloc (sizeof (LNode)) A->next=NULL ; B->next=NULL ; int count=1 ; LNode *q=A; while (C->next != NULL ){ if (count%2 == 1 ) { LNode*p=C->next; C->next=C->next->next; q->next=p; p->next=NULL ; q=p; count++; } if (count%2 == 0 ) { LNode *p = C->next; L->next = C->next->next; p->next = B->next; B->next = p; count++; } } free (C); }
二叉树 前/中/后序遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 typedef struct BiTNode { int data; struct BiTNode *lchild ,*rchild ; }BiTNode,*BiTree; void PreOrder (BiTree root) { if (root==NULL ) return ; visit(root); PreOrder(root->lchild); PreOrder(root->rchild); } void InOrder (BiTree root) { if (root==NULL ) return ; InOrder(root->lchild); visit(root); InOrder(root->rchild); } void PostOrder (BiTree root) { if (root==NULL ) return ; PostOrder(root->lchild); PostOrder(root->rchild); visit(root); }
层序遍历 在层序遍历中需要使用到队列,也就需要队列的操作
1 2 3 4 5 6 7 8 void InitQueue (Queue &Q) bool IsEmpty (Queue Q) void EnQueue (Queue &Q, BiTNode * x) void DeQueue (Queue &Q, BiTNode * &x)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void LevelOrder (BiTree T) { Queue Q; 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 2 3 4 5 6 7 8 9 10 int height = 0 ; void PreOrder (BiTree T, int n) { if (T == NULL ) return ; if (n > height) height = n; PreOrder(T->lchild, n + 1 ); PreOrder(T->rchild, n + 1 ); }
调用时用**PreOrder(T, 1
**)
原理是每过一层则+1并和目前高度比较
(方法二)后序遍历 1 2 3 4 5 6 7 8 9 10 int PostOrder (BiTree T) { if (T == NULL ) return 0 ; int left = PostOrder(T->lchild); int right = PostOrder(T->rchild); if (left > right) return left + 1 ; else return right + 1 ; }
以该图为例,g和h没有左右结点了,故直接返回0+1给e作为e的left和right,e再进行比较以大的深度+1作为自己的深度交给b,b也这么做并和c一起交给a
后序遍历以孩子出发从孩子开始数并返给根节点最长的版本
求树宽
此处先用先序遍历统计每一层的结点数,也就是使用先序遍历先根后孩子的特点来统计
1 2 3 4 5 6 7 8 9 int width[MAX]; void PreOrder (BiTree T, int level) { if (T == NULL ) return ; width[level]++; PreOrder(T->lchild, level + 1 ); PreOrder(T->rchild, level + 1 ); }
再找到宽度数组中最大的一项,将其作为树宽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void treeWidth (BiTree T) { for (int i = 0 ; i < MAX; i++) width[i] = 0 ; PreOrder(T, 0 ); int maxWidth = 0 ; for (int i = 0 ; i < MAX; i++) { if (width[i] > maxWidth) maxWidth = width[i]; } printf ("树的宽度是%d" , maxWidth); }
求树WPL
求树的WPL还是要使用树的先序遍历
使用了求树高和树宽的思想
先找到叶子节点(也就是左右孩子为空)并且将此处的带权路径长度加入WPL
如果不是叶子结点则向下找到其他叶子节点高度
1 2 3 4 5 6 7 void PreOrder (BiTree T, int level) { if (T == NULL ) return ; if (T->lchild==NULL && T->rchild==NULL ) WPL += level*T->weight; PreOrder(T->lchild, level + 1 ); PreOrder(T->rchild, level + 1 ); }
特殊树型判断 二叉排序树判定
中序遍历出来的序列是升序序列,所以二叉排序树序列中下一个结点必然大于前面所有的结点(也就是MAX{前面结点})这里用temp表示
所以只要后面遍历到的小于temp,则直接判定失败
若不小于则继续向右子树遍历,直到为NULL退回
1 2 3 4 5 6 7 8 9 10 int temp=MIN_INT; bool isBST=true ; void InOrder (BiTree T) { if (T == NULL ) return ; InOrder(T->lchild); if (T->data >= temp) temp=T->data; else isBST=false ; InOrder(T->rchild); }
平衡二叉树判定
此树判定和方法二找树深一样
通过后序遍历的特点:先访问孩子再进行操作,由最后的孩子也就是结点A的左右子树都退回来自己的高度之后,再进行左右子树高度判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 bool isBalance=true ; int PostOrder (BiTree T) { if (T == NULL ) return 0 ; int left = PostOder(T->lchild); int right = PostOder(T->rchild); if (left-right>1 ) isBalance=false ; if (left-right<-1 ) isBalance=false ; if (left>right) return left+1 ; else return right+1 ; }
完全二叉树判定 这里先借用一下层序遍历的图
层序遍历,因为完全二叉树是按顺序排放,所以要看每一层的结点的逐个讨论、
四种情况分别在访问这个结点时讨论一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 bool isComplete = true ; bool flag = false ; void visit (BiTNode * p) { if (p->lchild == NULL && p->rchild == NULL ) { if (flag) { isComplete = false ; } flag = true ; } else if (p->lchild != NULL && p->rchild == NULL ) { if (flag) { isComplete = false ; } flag = true ; } else if (p->lchild == NULL && p->rchild != NULL ) { isComplete = false ; } else { if (flag) { isComplete = false ; } } }
图
图的数据结构定义 邻接矩阵 这是邻接矩阵的数据结构
这是邻接矩阵的初始化操作
邻接表 这是邻接表的数据结构,在图ALGraph中存储一个顶点表数组和顶点数和边数
在顶点表中存储这个结点的数据和第一个依附该点的边
边表则是指出这条边指向的点和下一条边的指针 以及该边权值
这个表依旧要执行初始化操作
先将顶点表数组中的第一条边全部置为NULL
再将其顶点数和边数先更新
用封装好的AddEdge(图,出点,入点,权值)函数添加边
AddEdge函数是这么写的
首先先创建一个边结点,将其的权值和指向的结点j配置好
再将他连接到i结点第一条边之前,让这条新边指向老的第一条边(类似头插法将靠后的插在头边)
最后让i的点表连接到这条边作为first
最后可以看到他的逻辑结构长这样
图的遍历 深度优先遍历
1 2 3 4 5 6 7 8 9 10 void DFS (MGraph G,int i) { visit(i); visited[i]=true ; for (int j=0 ;j<G.G.numVertices;j++) if (visited[j]==false &&G.edge[i][j]==1 ){ DFS(G,j); } }
首先先把第一个访问的结点打上true,对第一结点比如A,找他向别人发射过的边,直到j<G.vexnum(穷尽了所有点)
当遇到没有遍历到过的且存在的邻接点时,将其投入DPS
然后一直DPS递归下去,直到再没有邻接的点,逐层返回没有访问完邻接点的结点中继续访问他们邻接点
1 2 3 4 5 6 7 8 9 10 11 12 13 void DFS (ALGraph G,int i) { visit(i); visited[i]=true ; for (EdgeNode* p=G.VertexList[i].first ; p!=NULL ; p=p->next){ int j=p->index; if (visited[j]==false ){ DFS(G,j); } } }
前面步骤一样,依旧从i点开始遍历。先找到i点的第一个边,将其位置存放到p中
j就是此时p指向的点,若此点没有被访问过则开始深度遍历
直到遍历到最后一个能遍历到的点,就往上返回,继续访问之前没有访问完其他边的结点。
广度优先遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void BFS (MGraph G, int v) { visit(v); visited[v]=true ; EnQueue(Q, v); while (!isEmpty(Q)){ DeQueue(Q, v); for (int w=0 ; w<G.numVertices; w++){ if (visited[w]==false && G.edge[v][w]==1 ){ visit(w); visited[w]=true ; EnQueue(Q, w); } } } }
广度优先遍历类似于图的层序遍历
从v出发,先将v存入队列中
当队列不为空时就出队一个结点,并且从第0个结点开始遍历,寻找v指向的且没有被访问过的结点,找到一个入队一个直到找不到为止
然后从i结点找到的第一个结点开始继续重复这个步骤,直到所有结点都找完使队列为空
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void BFS (ALGraph G, int v) { visit(v); visited[v]=true ; EnQueue(Q, v); while (!isEmpty(Q)){ DeQueue(Q, v); for (EdgeNode *p=G.VertexList[v].first; p!=NULL ; p=p->next){ int w = p->index; if (visited[w]==false ){ visit(w); visited[w]=true ; EnQueue(Q, w); } } } }
套路和上面一样,入队之后先找到第一个边指向的结点看是否被访问过,若没有则全部入队直到v结点没有邻接的点为止。
然后又从v第一条边邻接点继续广度优先遍历
拓扑排序 先按一下思路分析
拓扑排序的步骤:
选择入度为0的顶点并输出
从图中删除该顶点与所有以它为弧的边
重复步骤1、2,直到不存在入度为0的顶点
拓扑序列
条件
存在且唯一
每次选择结点都只有一个结点可选
存在不唯一
选择结点时不止一个结点可选
不存在
图中有环,环中顶点未被访问
先拿王道的流程图说一下思想
首先我们需要循环找到入度为0的点并且全部入队。
此处如果队列里元素个数超过1,
入度为0的点肯定超过2个也就是拓扑排序不唯一。
若是有多个连通分量,一个连通分量多个入度为0,另一个连通分量是有环图,则很明显不存在拓扑排序
然后当我们出队时,我们将计数器+1,来记录已经出队的数
出队之后将所有出队的点指向的点,把他们的入度-1,然后继续寻找入度为0的点循环这个过程
当队空之后我们根据count数进行判断
如果count等于顶点数,那么说明从顶点到最后一个点只有一条路径。没错,拓扑排序唯一
若count数不等于也就是小于顶点数,说明存在环路,始终有点入度不为0互相堵着
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 int uniquely1 (MGraph G) { int inDegree[MAXV] = {0 }; int queue [MAXV]; int front = 0 , rear = 0 ; int count = 0 ; for (int i = 0 ; i < G.vexnum; i++) { ArcNode *p = G.AdjList[i].firstarc; while (p != NULL ) { inDegree[p->adjvex]++; p = p->nextarc; } } for (int i = 0 ; i < G.vexnum; i++) { if (inDegree[i] == 0 ) queue [rear++] = i; } while (front < rear) { if (rear - front > 1 ) return 0 ; int v = queue [front++]; count++; ArcNode *p = G.AdjList[v].firstarc; while (p != NULL ) { inDegree[p->adjvex]--; if (inDegree[p->adjvex] == 0 ) queue [rear++] = p->adjvex; p = p->nextarc; } } if (count == G.vexnum) return 1 ; else return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 int uniquely1 (MGraph G) { int inDegree[MAXV] = {0 }; int queue [MAXV]; int front = 0 , rear = 0 ; int count = 0 ; for (int i = 0 ; i < G.numVertices; i++) for (int j = 0 ; j < G.numVertices; j++) if (G.Edge[i][j] != 0 ) inDegree[j]++; for (int i = 0 ; i < G.numVertices; i++) if (inDegree[i] == 0 ) queue [rear++] = i; while (front < rear) { if (rear - front > 1 ) return 0 ; int v = queue [front++]; count++; for (int i = 0 ; i < G.numVertices; i++) if (G.Edge[v][i] != 0 ) { inDegree[i]--; if (inDegree[i] == 0 ) queue [rear++] = i; } } if (count == G.numVertices) return 1 ; else return 0 ; }