各位好,这里是太阳终于出来啦,之前有考408的朋友觉得数据结构的算法题无从下手,我给他的建议是多去上手实操,毕竟有实时的输出反馈对于初学者而言帮助很大,但是他又和我抱怨对着空白的编译器无从下手,这才想到原来课本上的源码大多只有一个小的函数块,对于很多不熟悉编译的初学者属实有点困难。
所以在这里提供一下每个数据结构的模板希望更帮到大家,每个模板有相应的使用说明。
目录
六、二叉树范例:树的带权路径长度&中缀表达式(2014&2017)
写在前面
1.本人并不擅长编程,各位可以交流学习,如果有错误欢迎指出。
2.初学者建议使用DEVC++进行编程,本文也仅针对DEV做演示。
3.不保证思路和解决方式是最佳思路,也不能保证完全正确,如有错误可以指出。其中涉及到专业名词的部分可能会有描述错误,请谅解。
4.本文为了方便理解,删去了一些在408考试中不太看中的(如内存分配,栈满判断(为了方便这里不给栈设上限))部分,仅保留代码的核心部分,请勿将其用作正式设计或是比赛。
一、链表模板:合并两个有序链表
代码如下:
#include <iostream>
using namespace std;
typedef struct LNode
{
int data;
struct LNode *next;
}LNode,*LinkList;
int n1=10,n2=10;//N1 N2分别为链表一和链表二元素(节点)个数 ,可修改
int a1[10]={2,3,5,7,14,19,20,24,29,38}; //链表L1中的10个初始元素,可修改
int a2[10]={1,4,11,23,34,37,42,48,53,59}; //链表L1中的10个初始元素,可修改
LinkList L3;
//将你定义的函数名写在这里
void CreateList(LinkList &L,int n,int a[]);
void MixList(LinkList &L1,LinkList &L2s);
void Print(LinkList &L);
void CreateList(LinkList &L,int n,int a[])
{
LNode *p=NULL, *r=NULL;
int i;
L=new LNode;
L->next=NULL;
r=L;
for(i=0;i<n;i++)
{
p=new LNode;
p->data=a[i];
p->next=NULL;
r->next=p;
r=p;
}
}
void Print(LinkList &L){
LNode *p=NULL;
p=L->next;
while(p!=NULL)
{
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
int main()
{
LinkList L1,L2;
LNode *p;
CreateList(L1,n1,a1);
CreateList(L2,n2,a2);
//下面按程序的逻辑填写函数
MixList(L1,L2);
Print(L3);//这个是输出函数填写对应要打印的链表
return 0;
}
//这里之后请替换为对应题目的代码
void MixList(LinkList &L1,LinkList &L2)
{
LNode *p=NULL,*p1=NULL,*p2=NULL;
int i;
L3=new LNode;
L3->next=NULL;
p1=L1->next;
p2=L2->next;
while(p1 != NULL && p2 != NULL)
{
if(p1->data < p2->data)
{
p=new LNode;
p->data=p1->data;
p->next=L3->next;
L3->next=p;
p1=p1->next;
}
else
{
p=new LNode;
p->data=p2->data;
p->next=L3->next;
L3->next=p;
p2=p2->next;
}
}
if(p1!=NULL)
{
while(p1!=NULL)
{
p=new LNode;
p->data=p1->data;
p->next=L3->next;
L3->next=p;
p1=p1->next;
}
}
if(p2!=NULL)
{
while(p2!=NULL)
{
p=new LNode;
p->data=p2->data;
p->next=L3->next;
L3->next=p;
p2=p2->next;
}
}
}
这个范例程序的输入和输出如下:
二、链表范例:交错排列链表(2019)
下面按2019年408统考真题为例。
2019年的真题要求将链表中的元素 a1,a2,a3……,an-1,an。转变为a1,an,a2,an-1……。
参考的算法实现给出了如下代码:
void change_list(LinkList &L){//注:LinkList &L和node *h等价
LNode *p,*q,*r,*s;
p=q=L;
while(q->next!=NULL){ //寻找中间结点
p=p->next; //p走两步
q=q->next;
if(q->next!=NULL) q=q->next; //q走两步
}
q=p->next; //将q调整为后半段链表的首节点
p->next=NULL;
while(q!=NULL){ // 逆置后半段
r=q->next;
q->next=p->next;
p->next=q;
q=r;
}
s=L->next; //s为前半段首个数据节点
q=p->next; //q为后半段首个数据节点
p->next=NULL;
while(q!=NULL){
r=q->next;
q->next=s->next;
s->next=q;
s=q->next;
q=r;
}
}
接下来我们在DEV中新建源代码,复制刚才模板中的完整代码,然后修改以下项。
1.将函数名复制到对应地方。
![](https://img-blog.csdnimg.cn/e18bd2ae71fd4c7ea8e6176ac1c82610.png)
2.在主函数main中替换对应的函数项,由于这题没有数据的要求,我们直接用原本程序中我初始化好的L1或L2,并替换print将其输出。
3.把整个函数复制到替换区域
接下来点击输出就可以了(截图的时候void输错了,代码没有问题)。
注:有些变量命名在各个版本中会不一样,请按需调整,如我的模板中节点采用的是LNode,在有些教材上是直接用Node,报错也要看看大小写有没有出问题。
本题的完整代码如下:
#include <iostream>
using namespace std;
typedef struct LNode
{
int data;
struct LNode *next;
}LNode,*LinkList;
int n1=10,n2=10;//N1 N2分别为链表一和链表二元素(节点)个数 ,可修改
int a1[10]={2,3,5,7,14,19,20,24,29,38}; //链表L1中的10个初始元素,可修改
int a2[10]={1,4,11,23,34,37,42,48,53,59}; //链表L1中的10个初始元素,可修改
LinkList L3;
//将你定义的函数名写在这里
void CreateList(LinkList &L,int n,int a[]);
void MixList(LinkList &L1,LinkList &L2s);
void Print(LinkList &L);
void change_list(LinkList &L);
void CreateList(LinkList &L,int n,int a[])
{
LNode *p=NULL, *r=NULL;
int i;
L=new LNode;
L->next=NULL;
r=L;
for(i=0;i<n;i++)
{
p=new LNode;
p->data=a[i];
p->next=NULL;
r->next=p;
r=p;
}
}
void Print(LinkList &L){
LNode *p=NULL;
p=L->next;
while(p!=NULL)
{
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
int main()
{
LinkList L1,L2;
LNode *p;
CreateList(L1,n1,a1);
CreateList(L2,n2,a2);
//下面按程序的逻辑填写函数
change_list(L1);
Print(L1);
return 0;
}
//这里之后请替换为对应题目的代码
void change_list(LinkList &L){//注:LinkList &L和node *h等价
LNode *p,*q,*r,*s;
p=q=L;
while(q->next!=NULL){ //寻找中间结点
p=p->next; //p走两步
q=q->next;
if(q->next!=NULL) q=q->next; //q走两步
}
q=p->next; //将q调整为后半段链表的首节点
p->next=NULL;
while(q!=NULL){ // 逆置后半段
r=q->next;
q->next=p->next;
p->next=q;
q=r;
}
s=L->next; //s为前半段首个数据节点
q=p->next; //q为后半段首个数据节点
p->next=NULL;
while(q!=NULL){
r=q->next;
q->next=s->next;
s->next=q;
s=q->next;
q=r;
}
}
三、栈模板:判断回文数
代码如下:
#include <iostream>
#include <string.h>
#define OK 1
#define ERROR 0
#define OVERFLOW -2
using namespace std;
typedef char Elemtype; //根据题目要求修改变量类型
typedef struct {
Elemtype *base;
Elemtype *top;
int size;
}Sqstack;
//将你定义的函数名写在这里
int Createstack(Sqstack &S,int k);//初始化
int Judge(Sqstack &S,int k);
Elemtype Pop(Sqstack &S);//出栈 根据题目要求修改变量类型
void Push(Sqstack &S, char a);//入栈 根据题目要求修改变量类型
Elemtype Pop(Sqstack &S){
Elemtype e;
if(S.base==S.top) cout<<"栈是空的"<<endl;
else{
*S.top--;
e=*S.top;
return e;
}
}
void Push(Sqstack &S,Elemtype a){
*S.top=a;
*S.top++;
}
int Createstack(Sqstack &S,int k)
{
int i;
S.base=new Elemtype[k];
if(!S.base) exit(OVERFLOW);
S.top=S.base;
return OK;
}
int main()
{
Sqstack S;
int n;
//下面按程序的逻辑填写函数
cout<<"请输入字符串大小"<<endl;
cin>>n;
Createstack(S,n);
Judge(S,n);
return 0;
}
//这里之后请替换为对应题目的代码
int Judge(Sqstack &S,int k)
{
Elemtype a[k],e,f(0);
int i,j;
cout<<"请输入字符串"<<endl;
scanf("%s",a);
for(i=0;i<strlen(a)/2;i++){
Push(S,a[i]);
}
j=i;
if(strlen(a)%2==1) j++;
for(i=0;i<strlen(a)/2;i++)
{
e=Pop(S);
if(e!=a[j])
{
cout<<"不是回文数"<<endl;
f=1;
break;
}
j++;
}
if(f==0) cout<<"是回文数"<<endl;
}
这个程序的输出如下:
注:由于栈的处理和链表差不多,这里不提供范例,大家参考链表的做法是一样的,内置提供了POP(会返回栈顶元素)和PUSH,如果有需要可以自行添加其他操作的函数。在使用时请按要求修改整体变量类型!这里由于是字符运算所以用的是char。
typedef char Elemtype; //根据题目要求修改变量类型
//找到这句话并修改其中的char为对于题目的数据类型
四、队列模板:循环队列基本操作
队列只在在2019年算法题中考察了循环队列,队列的炒作其实和栈类似,这两个数据结构彼此也能互相转化,那么一下便只罗列循环队列的一些基本操作。下面代码中内置了出队列和进队列函数EnQueue()和DeQueue(),操作方法类似,这里不再演示。
#include <iostream>
#define OK 1
#define ERROR 0
#define OVERFLOW -2
using namespace std;
typedef struct QNode{
int data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct
{
QueuePtr rear;
}LinkQueue;
//将你定义的函数名写在这里
void CreateQueue(LinkQueue &Q);
int Judge(LinkQueue &Q);
void EnQueue(LinkQueue &Q,int e);
void DeQueue(LinkQueue &Q);
void Basic(LinkQueue &Q);
void CreateQueue(LinkQueue &Q){
Q.rear=new QNode;
Q.rear->next=Q.rear;
}
int Judge(LinkQueue &Q){
if(Q.rear->next=Q.rear) return OK;
else return ERROR;
}
void EnQueue(LinkQueue &Q,int e){
QNode *p;
p=new QNode;
p->data=e;
p->next=Q.rear->next;
Q.rear->next=p;
}
void DeQueue(LinkQueue &Q){
QNode *p;
p=Q.rear->next;
while(p->next->next!=Q.rear){
p=p->next;
}
p->next=Q.rear;
}
int main()
{
LinkQueue Q;
QNode *p;
int n;
//下面按程序的逻辑填写函数
CreateQueue(Q);
Basic(Q);
return 0;
}
//这里之后请替换为对应题目的代码
void Basic(LinkQueue &Q){
QNode *p;
int i,f,m,j(0);
while(f!=4){
cout<<"请输入要执行的操作:"<<endl;
cout<<"(1)入队列"<<endl<<"(2)出队列"<<endl<<"(3)输出队列内元素"<<endl<<"(4)退出"<<endl;
cin>>f;
system("CLS");
if(f==1){
cout<<"请输入入队元素"<<endl;
cin>>m;
EnQueue(Q,m);
j++;
}
else if(f==2){
DeQueue(Q);
j--;
}
else if(f==3){
cout<<"队列元素从尾至头依次为:"<<endl;
p=Q.rear;
for(i=1;i<=j;i++){
p=p->next;
cout<<p->data<<" ";
}
cout<<endl;
}
cout<<endl;
}
}
这段代码用以实现循环队列的控制,输出如下:
![](https://img-blog.csdnimg.cn/bf2f1f3bc5ee443192942a3b78461a45.png)
五、二叉树模板:树基本操作
#include <iostream>
using namespace std;
typedef int Elemtype; //根据题目要求修改变量类型
typedef struct BiTNode{
Elemtype data;
struct BiTNode *lchild, *rchild;
int level;//层数
int weight; // 权值
}BiTNode,*BiTree;
//将你定义的函数名写在这里
int CreateTree(BiTree &B);
int print(BiTree &B);//前序遍历函数
int print2(BiTree &B);//中序遍历函数
int check(BiTree &B, int n);
int min(int x, int y);
int n=8,t(0),l(1),ma;
//n是节点个数,可修改
Elemtype a[8]={1,2,4,5,6,7,3,8};
int b[16]={1,1,1,1,0,0,1,1,0,0,0,0,0,1,0,0};
//a是节点值,大小需要同节点个数一样,可修改(按前序遍历)
//b是标志,按中序遍历,两个一组,1,1表示有左孩子有右孩子,以此类推
int c[8]={1,2,3,4,5,6,7,8};//c为权值
int CreateTree(BiTree &B){
Elemtype m;
int f1,f2,w;
m=a[t];
w=c[t];
if(t==n) {
B=NULL;
return 1;
}
else{
B= new BiTNode;
B->data=m;
B->weight=w;
f1=b[t*2];
f2=b[t*2+1];
t++;
B->lchild=NULL;
B->rchild=NULL;
if(f1==1) CreateTree(B->lchild);
if(f2==1) CreateTree(B->rchild);
}
return 1;
}
int print(BiTree &B){
if(B!=NULL) {
cout<<B->data<<" ";
if(B->lchild) print(B->lchild);
if(B->rchild) print(B->rchild);
}
else return 0;
}
int print2(BiTree &B){
if(B!=NULL) {
if(B->lchild) {
l++;
print2(B->lchild);
}
cout<<B->data<<" ";
B->level=l;
if(B->rchild){
l++;
print2(B->rchild);
}
if(l>ma) ma=l;
l--;
}
else return 0;
}
int check(BiTree &B, int n){
if(B!=NULL) {
if(B->level==n) {
cout<<B->data<<" ";
if(B->rchild && !B->lchild || B->lchild && !B->rchild)
cout<<"(这个结点度为1)";
}
if(B->lchild) check(B->lchild,n);
if(B->rchild) check(B->rchild,n);
}
else return 0;
}
int min(int x, int y){
if (x<y) return x;
else return y;
}
int main(){
BiTree B;
int i;
CreateTree(B);
//可以选择保留各个遍历的输出,也可以选择替换掉
cout<<endl;
cout<<"前序遍历为:";
print(B);
cout<<"中序遍历为:";
print2(B);
cout<<endl;
cout<<"层序遍历为:";
for(i=1;i<=ma;i++)
check(B,i);
return 0;
}
//这里没有过多的操作,只需要把代码复制到这里即可
这里内置了一个这样的树,如图:
输出如下:
六、二叉树范例:树的带权路径长度&中缀表达式(2014&2017)
树的算法题在408中出现的还是蛮多的,但是归根结底还是对几个遍历的考察,对于几个而最重要的不过前序,中序和层序,2014年的带权树和2017年的中缀表达式一个是层序遍历,一个是中序遍历,而两种遍历方式都在上面的模板中给到了。
6.1树的带权路径长度(2014)
2014年的题目要求我们计算带权树的权值,而权值和层数都已经在上面的模板中给出,我们只需要写一个遍历函数把他们乘起来相加就行了。
注:层序遍历实际上最好的办法是用队列,但上面的代码用了标记,可以做但不是最好,各位可以根据自己的情况进行修改。
这里的参考范例是(线序的方法)
int WPL(BiTree &B){
return WPL_PreOrder(B,0);
}
int WPL_PreOrder(BiTree &B,int deep){
static int WPL;
if(B->lchild==NULL&&B->rchild==NULL) {
WPL+=B->weight*deep;
}
if(B->lchild) WPL_PreOrder(B->lchild,deep+1);
if(B->rchild) WPL_PreOrder(B->rchild,deep+1);
return WPL;
}
一共用到了两个函数,那和前面链表一样:
1.将函数名复制到对应地方。
2.在主函数main中替换对应的函数项,由于这题有一个返回值输出,所以我们可以直接让函数输出。
3.把整个函数复制到替换区域
就可以输出得到需要的结果了。
4,6,7,8,为叶子结点。权值分别为3,5,6,8,层数分别为,2,3,3,2
所以WPL为(3+8)*2+(5+6)*3=55
完整代码如下:
#include <iostream>
using namespace std;
typedef struct BiTNode{
int data;
struct BiTNode *lchild, *rchild;
int level;//层数
int weight; // 权值
}BiTNode,*BiTree;
//将你定义的函数名写在这里
int CreateTree(BiTree &B);
int print(BiTree &B);//前序遍历函数
int print2(BiTree &B);//中序遍历函数
int check(BiTree &B, int n);
int min(int x, int y);
int WPL(BiTree &B);
int WPL_PreOrder(BiTree &B,int deep);
int n=8,t(0),l(1),ma;
//n是节点个数,可修改
int a[8]={1,2,4,5,6,7,3,8},b[16]={1,1,1,1,0,0,1,1,0,0,0,0,0,1,0,0};
//a是节点值,大小需要同节点个数一样,可修改(按前序遍历)
//b是标志,按中序遍历,两个一组,1,1表示有左孩子有右孩子,以此类推
int c[8]={1,2,3,4,5,6,7,8};//c为权值
int CreateTree(BiTree &B){
int m,f1,f2,w;
m=a[t];
w=c[t];
if(t==n) {
B=NULL;
return 1;
}
else{
B= new BiTNode;
B->data=m;
B->weight=w;
f1=b[t*2];
f2=b[t*2+1];
t++;
B->lchild=NULL;
B->rchild=NULL;
if(f1==1) CreateTree(B->lchild);
if(f2==1) CreateTree(B->rchild);
}
return 1;
}
int print(BiTree &B){
if(B!=NULL) {
cout<<B->data<<" ";
if(B->lchild) print(B->lchild);
if(B->rchild) print(B->rchild);
}
else return 0;
}
int print2(BiTree &B){
if(B!=NULL) {
if(B->lchild) {
l++;
print2(B->lchild);
}
cout<<B->data<<" ";
B->level=l;
if(B->rchild){
l++;
print2(B->rchild);
}
if(l>ma) ma=l;
l--;
}
else return 0;
}
int check(BiTree &B, int n){
if(B!=NULL) {
if(B->level==n) {
cout<<B->data<<" ";
if(B->rchild && !B->lchild || B->lchild && !B->rchild)
cout<<"(这个结点度为1)";
}
if(B->lchild) check(B->lchild,n);
if(B->rchild) check(B->rchild,n);
}
else return 0;
}
int min(int x, int y){
if (x<y) return x;
else return y;
}
int main(){
BiTree B;
int i;
CreateTree(B);
//可以选择保留各个遍历的输出,也可以选择替换掉
cout<<WPL(B);
return 0;
}
//这里没有过多的操作,只需要把代码复制到这里即可
int WPL(BiTree &B){
return WPL_PreOrder(B,0);
}
int WPL_PreOrder(BiTree &B,int deep){
static int WPL;
if(B->lchild==NULL&&B->rchild==NULL) {
WPL+=B->weight*deep;
}
if(B->lchild) WPL_PreOrder(B->lchild,deep+1);
if(B->rchild) WPL_PreOrder(B->rchild,deep+1);
return WPL;
}
6.1中缀表达式(2017)
中缀表达式其实就是中序遍历的输出,这里的不同就是要修改原来的树,我们根据题目例子左边的树修改如下:
//找到这句话,然后像下面这样修改树的内容:
int n=8,t(0),l(1),ma;
//n是节点个数,可修改
Elemtype a[8]={'*','+','a','b','*','c','-','d'};
int b[16]={1,1,1,1,0,0,0,0,1,1,0,0,0,1,0,0};
//a是节点值,大小需要同节点个数一样,可修改(按层序遍历)
//b是标志,按中序遍历,两个一组,1,1表示有左孩子有右孩子,以此类推
int c[8]={1,2,3,4,5,6,7,8};//c为权值
记得回开头修改变量类型
typedef char Elemtype; //根据题目要求修改变量类型
这题题目给出的范例为:
void BtreeToE(BiTree &B){
return BtreeToExp(B,0);
}
void BtreeToExp(BiTree &B,int deep){
if(B==NULL) return; // 空结点返回
if(B->lchild==NULL&&B->rchild==NULL) { //若为叶节点
cout<<B->data;
}
else{
if(deep>0) cout<<"("; // 不为根节点要输出括号
if(B->lchild) BtreeToExp(B->lchild,deep+1);
cout<<B->data;
if(B->rchild) BtreeToExp(B->rchild,deep+1);
if(deep>0) cout<<")"; // 不为根节点要输出括号
}
}
同理:
1.将函数名复制到对应地方。
2.在主函数main中替换对应的函数项,由于这题有一个返回值输出,所以我们可以直接让函数输出。
3.把整个函数复制到替换区域
就可以输出得到需要的结果了。
七、图模板:EL路径判断(2021)
图在408的考察也不多,很少考察代码,这里就以2021年的真题作为例子,这道真题甚至在题目中给出了做题方法,相信各位也能看懂后面的代码,就没写注释了。
#include <iostream>
#include <time.h>
#include <stdlib.h>
#define random(a,b) (rand()%(b-a+1)+a)
using namespace std;
typedef int Elemtype; //根据题目要求修改变量类型
typedef struct{ //这是一个图
Elemtype vex[50][50];//存放顶点
int arcs[50][50];//邻接矩阵
int vexnum,arcnum;//边数和节点数量
}AMGC;
int n=8;//顶点数量,可修改
//将你定义的函数名写在这里
void CreateAMGC(AMGC &G);
void IsExistEL(AMGC &G);
void CreateAMGC(AMGC &G){
G.arcnum=n;
for(int i=0;i<50;i++){
for(int j=0;j<50;j++)
G.arcs[i][j]=0;
}
srand(time(NULL));//不想随机删了这行
for(int i=1;i<=20;i++){
int x,y;
x=random(0,n-1);
y=random(0,n-1);
if(G.arcs[x][y]==0) G.vexnum++;
G.arcs[x][y]=1;
G.arcs[y][x]=1;
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
cout<<G.arcs[i][j]<<" ";
cout<<endl;
}
}
int main(){
AMGC G;
CreateAMGC(G);
//下面按程序的逻辑填写函数
IsExistEL(G);
return 0;
}
//这里没有过多的操作,只需要把代码复制到这里即可
void IsExistEL(AMGC &G){
int degree,i,j,count=0;
for(i=0;i<G.vexnum;i++){
degree=0;
for(j=0;j<G.vexnum;j++) degree+=G.arcs[i][j]; //计算各顶点度
if(degree%2!=0) count++; //奇数统计
}
if (count==0 || count==2) cout<<"存在EL路径";
else cout<<"不存在EL路径";
}
此处为了方便图的生成,我用了随机数,所以每次运行生成的图是不一样的,如果想要每次生成的图是一样的那么关闭随机种子即可,即删去下面这行代码。
srand(time(NULL));//不想随机删了这行,位于第27行
运行结果时,我添加了邻接矩阵,方便查看,如下:
以上,文章中的代码cpp文件已经打包上传,点这里,感谢你的时间,再会。