机缘
我是大学生,计算机专业,之前做实验有不懂的地方,都会去上网搜,那时候还没有ChatGPT,无意间发现多次搜索中,总是有CSDN的身影,后来发现CSDN是一个专门用来交流的社区,尤其是IT方面的,然后几乎有实验问题都在上面搜索,也很感谢各位博主的付出。
我那是还很小白,从没有想过去写博客,只是一味的去看别人的博客,虽然有时候搜到一些不能解决自己问题的,但是坚持下去还是能找到。直到大二的那年暑假去培训,发现我的任课老师有写博客的习惯,同时我也想增强自身的实力,也想去回报那些曾经帮助我解决问题的博主,希望自己的博客能帮助后来者解决遇到的问题。
收获
在创作的过程中,我形成了系统化的思维,写文章更加规范,同时对自己写的东西也进行了验证,这个要求对知识准确无误,总不能把错误的东西写上去,做事得有责任。所以在创作的过程中,不仅增加对知识的掌握程度,同时也锻炼了自己坚持不懈,做事负责的精神。
当然我也收获了来自猿友的关注和点赞,你的点赞是我们创作的动力。
日常
对于日常的创作,在培训过程中,我会把知识进行理解和实践后,整理成一篇博客,这个过程相当于做笔记,一方面方便复习,另一方便也可以帮助到有需要的猿友。在学校里,一些难以解决的实验,我会在自己独立完成后,整理成博客,记录实验过程中的心得和易错点。在某种程度上认为,只有把做过的实验发布成博客,才能算是真正做过实验,而不是简单的为做实验而做实验,这也是当代大学生最需要学习地方(不一定要发博客,反正不能为做实验而做实验)。
如何平衡创作和工作学习,这个问题对于目前作为大学生的我来说,不是问题,大学生其实并没有那么忙,只是有人整天想着玩而已,时间是公平的,我也很喜欢玩的,但是我会发几篇博客后再玩,两者之间形成动态平衡。
当你把创作作为一种习惯,那么创作就不是一件麻烦事,就像正常人每天都要喝水一样。
成就
目前已经发布了Linux理论专栏,C++专栏,接下来几个月准备发布系统编程和网络编程等嵌入式方面的专栏。
下面演示几个精彩的代码。
1、传教士与野人渡河问题(人工智能课程某个实验)
原博客链接:C语言版,传教士与野人渡河问题,使用深度优先搜索法求解(DFS)
#include <stdio.h> #include <stdlib.h> // 定义状态节点 typedef struct node { int src_x; // 起始岸传教士人数 int src_y; // 起始岸野人人数 int dest_x; // 目的岸传教士人数 int dest_y; // 目的岸野人人数 int location; // 船的状态,-1表示在目的岸,1表示在起始岸 struct node *prev; // 前指针 struct node *next; // 后指针 }node; node *head; // 状态链表的头节点 static int N = 0; // 船的最大载人数 static int X = 0; // 起始岸的传教士人数 static int Y = 0; // 起始岸的野人人数 static int count = 0; // 渡河方案 // 一些函数的声明 node *initList(); void del(node *new); int checkSame(node *p); void display(node *head); int checkNo(int X, int Y); void tail_insert(node *head, node *new); void addnew(int x, int y, int location); void Select(int X, int Y, int location); void goRiver(int x, int y, int location); int checkAll(int X, int Y, int location); int main(void) { head = initList(); // 初始化状态链表 if(!head) { printf("初始化状态链表失败\n"); return -1; } printf("请输入起始岸传教士人数:"); scanf("%d", &X); printf("请输入起始岸野人人数:"); scanf("\n%d", &Y); printf("请输入船的最大载人数:"); scanf("\n%d", &N); // 把起始状态插入到链表中 node *new = initList(); new->src_x = X; new->src_y = Y; new->location = 1; tail_insert(head, new); printf("说明:渡河方案表示格式(起始岸传教士人数,起始岸野人人数,目的岸传教士人数,目的岸野人人数,船的状态)\n"); printf("其中船的状态表示格式(-1表示船在目的岸,1表示船在起始岸)\n"); printf("开始计算渡河方案\n"); printf("计算中,请稍等...\n"); Select(X, Y, 1); printf("渡河方案总共有%d种\n", count); return 0; } // 从起始岸或者目的岸选择人渡河,采用深度优先搜索法和剪枝回溯法 void Select(int X, int Y, int location) { int x, y; // x, y满足以下三条不等式 // x <= X 表示选择出的传教士人数不能超过岸上传教士的人数 // y <= Y 表示选择出的野人人数不能超过岸上野人的人数 // x + y <= N, 即y <= N - x, 表示选择出的总人数不能超过上船的最大载人数 for(x = 0; x <= X; x++) { for(y = 0; y <= Y && y <= N-x; y++) { if((x == 0 && y > 0) || (x > 0 && x >= y)) { // 如果是从起始岸选择,则必须至少选出2个人 // 否则一个人划船没意思,会出现死循环 if(location == 1 && x + y >= 2) { goRiver(x, y, location); } // 如果是从目的岸选择,则必须保证选完人上船后,必须有人留在目的岸上 // 否则从起始岸的划船到达目的岸没意思,会出现四循环 if(location == -1 && head->prev->dest_x + head->prev->dest_y - x - y > 0) { goRiver(x, y, location); } } } } // 本次状态搜索完后,要回溯上一个分支(剪枝回溯法) if(head->next->next != head) { del(head->prev); } } // 判断从岸上选出的人能否渡河 void goRiver(int x, int y, int location) { switch(checkAll(x, y, location)) { // 不能渡河 case 0: return; // 可以渡河 case 1: addnew(x, y, location); // 需要进行转态查重 // 具体例子是(2, 1, 0, 1, 1)->(0, 1, 2, 1, -1)->(2, 1, 0, 1, 1)->(0, 1, 2, 1, -1)... // 不去重会出现死循环,不信可以试试2个传教士,2个野人,船最大载人数为2的情况 if(checkSame(head->prev)) { del(head->prev); // 删除重复的状态 return; } // 全部已经渡河 if(!head->prev->src_x && !head->prev->src_y) { printf("第%d种渡河方案\n", ++count); display(head); // 打印渡河方案 del(head->prev); // 剪枝回溯 return; } // 人还没全部渡河,且下一次从起始岸选择人渡河 if(head->prev->location == 1) { Select(head->prev->src_x, head->prev->src_y, 1); } // 人还没全部渡河,且下一次从目的岸选择人渡河 else { Select(head->prev->dest_x, head->prev->dest_y, -1); } return; // 已经全部渡河 // 不过该条件基本不会出现, 因为case1中有判断下一个状态是否为目的态 case 2: printf("第%d种渡河方案\n", ++count); display(head); // 打印渡河方案 del(head->prev); // 剪枝回溯 return; } } // 检查链表中是否有重复的状态 int checkSame(node *p) { for(node *q = head->next; q != p; q = q->next) { // 只需要该状态的起始岸或者目的岸的人数进行比较,不需要两个岸都要进行比较 if(q->src_x == p->src_x && q->src_y == p->src_y && q->location == p->location) { return 1; } } return 0; } // 检查在起始岸或者目的岸的人数是否合法 int checkNo(int x, int y) { return x > 0 && x < y; } // 检查在起始岸和目的岸的人是否合法 int checkAll(int x, int y, int location) { int src_x, src_y, dest_x, dest_y; src_x = head->prev->src_x; src_y = head->prev->src_y; dest_x = head->prev->dest_x; dest_y = head->prev->dest_y; // 只要起始岸或者目的岸的人数不合法,就不能渡河 if(checkNo(src_x - x * location, src_y - y * location) || checkNo(dest_x + x * location, dest_y + y * location)) { return 0; } // 已经全部渡河,不需要再渡河 else if(location == -1 && src_x == 0 && src_y == 0 && dest_x == X && dest_y == Y) { return 2; } // 本次选择的人可以渡河,但未全部渡河 else { return 1; } } // 把新的状态插入到链表中 void addnew(int x, int y, int location) { node *p = initList(); if(!p) { printf("malloc fail\n"); return; } // 修改状态的信息 // 有个小技巧,关于location的,不需要分起始岸或者目的岸写 p->src_x = head->prev->src_x - x * location; p->src_y = head->prev->src_y - y * location; p->dest_x = head->prev->dest_x + x * location; p->dest_y = head->prev->dest_y + y * location; p->location = -head->prev->location; tail_insert(head, p); } // 生成一个状态节点 node *initList() { node *new = malloc(sizeof(node)); if(!new) { printf("malloc fail!\n"); return NULL; } new->src_x = 0; new->src_y = 0; new->dest_x = 0; new->dest_y = 0; new->location = 0; new->prev = new; new->next = new; return new; } // 打印渡河方案 void display(node *head) { // 链表不存在或者为空 if(!head || head->next == head) { return; } for(node *p = head->next; p != head; p = p->next) { printf("%d, %d, %d, %d, %d\n", p->src_x, p->src_y, p->dest_x, p->dest_y, p->location); } printf("\n"); } // 尾插法,把节点插到链表最末 void tail_insert(node *head, node *new) { new->prev = head->prev; new->next = head; head->prev->next = new; head->prev =new; } // 删除一个节点,本程序通常是链表最末的一个 void del(node *new) { new->prev->next = new->next; new->next->prev = new->prev; new->prev = new; new->next = new; free(new); }
2、单链表排序(没有找到好的博客,自己发奋写的)
原博客链接:C语言版--单链表排序,冒泡排序,选择排序,插入排序,快速排序,应有尽有,保证看懂,没有bug!交换节点版本!
#include <stdio.h> #include <stdlib.h> typedef struct node { int data; struct node *next; }node; //生成一个节点 node *initList(void) { node *new = malloc(sizeof(node)); if(!new) { printf("malloc fail!\n"); return NULL; } new->data = 0; new->next = NULL; return new; } //头插法 void head_insert(node *head, node *new) { new->next = head->next; head->next = new; } //遍历 node *traverse(node *head) { for(node *p = head->next; p; p = p->next) { printf("%d ", p->data); } printf("\n"); } //冒泡排序,最优版本 void bubble_sort(node *head) { int flag; node *p, *prev, *tail; tail = NULL; //tail以及tail后面的是排好序的元素,第一次还没有排好,所以为NULL while(1) { flag = 1; //flag用来标志是否已经排好序 //每次从head->next开始遍历,直到tail结束, prev是p的前驱节点 for(prev = head, p = head->next; p && p->next != tail; prev = prev->next) { //交换后,p已经移动到后面,不需要再遍历下一个 if(p->data > p->next->data) { flag = 0; //修改flag=0,标志本轮循环交换过 prev->next = p->next; //弹出p节点 p->next = p->next->next; //插入p节点 prev->next->next = p; //原来的p->next已经修改,需要用prev->next代替 } else //没有交换就继续遍历下一个 { p = p->next; } } printf("本轮排序移动出的最大值:%d\n", p->data); traverse(head); 显示每一轮排序结果 if(flag) //如果内层循环中都没有交换过,则所有节点都已经是排好序的 { printf("冒泡排序结束!\n"); break; } tail = p; //tail向前移一个,tail以及tail后面的是排好序的元素 } } //选择排序,初级版本 void choose_sort1(node *head) { node *p, *q, *max, *prior; p = malloc(sizeof(node)); //生成一个p节点 p->next = head->next; //p取代head head->next = NULL; //head是空链表 while(p->next) { prior = p; max = prior->next; for(q = max; q->next; q = q->next) { if(max->data < q->next->data) { max = q->next; prior = q; } } //从p链表中弹出一个最大的节点,用头插法插入到head链表中 prior->next = max->next; max->next = head->next; head->next = max; } free(p); } //选择排序,最优版本 void choose_sort(node *head) { node *q, *min, *prev, *tail; //tail及tail前面是排好序的,每次从tail后面选出一个最小值,插入到tail前面,直到等于NULL结束 //要额外保证p->next!=NULL,因为内层循环q=p->next; 用q->next来判断是否为空,可能会越界 for(tail = head; tail && tail->next; tail = tail->next) { //prev是min的前驱节点,q用来遍历,从min->next直到head链表最后一个 for(prev = tail, min = tail->next, q = tail->next; q->next; q = q->next) { if(min->data > q->next->data) //找到一个更小的节点,就记录 { min = q->next; prev = q; //单链表要额外记录min的前驱节点 } } printf("本轮排序选择出的最小值:%d\n", min->data); if(min != tail->next) //如果找到比min更小的节点,就插入到p后面 { prev->next = min->next; min->next = tail->next; tail->next = min; } traverse(head); //显示每一轮排序结果 } } //插入排序,最优版本 void insert_sort(node *head) { //头结点是空的或者表是空的或者表只有一个节点时候不用排 if(!head || !head->next||!head->next->next) { return; } node *p, *q, *tail; //head->next->next开始遍历,tail及tail前面的是排好序的,p是本轮待插入值, p是NULl时结束 for(tail = head->next, p = tail->next; p; p = tail->next) { //从head->next开始遍历,直到tail结束 for(q = head; q != tail; q = q->next) { if(p->data < q->next->data) //把插入后结束本次遍历 { tail->next = p->next; p->next = q->next; q->next = p; break; } } printf("本轮排序插入值:%d, ", p->data); if(tail == q) //在tail前面没有插入,就下移 { printf("已处于插入位置\n"); tail = tail->next; } else { //p已经处于插入位置,显示时需要用p->next->data printf("插入到%d的前面\n", p->next->data); } traverse(head); //显示每一轮排序结果 } } //快速排序的每一次划分 node *partition(node *head, node *tail) { //头结点是空的或者表是空的或者表只有一个节点时候不用排 //已经在调用函数前判断好了,进来的链表都是至少有两个元素的 node *p, *prev, *basic; basic = head->next; //basic是基准点 //从baisc后面开始遍历,找到比baisc小的就插入到head后面,直到tail结束,prev是p的前驱节点 //这里head可以理解为本次待划分的链表的头结点,tail是链表的最后一个节点的下一个可以理解为NULL for(prev = basic, p = basic->next; p && p != tail; p = prev->next) { if(basic->data > p->data) //用头插入法把所有比baisc小插入到head后面 { prev->next = p->next; p->next = head->next; head->next = p; } else //没有找到比basic小的就一直后移,prev与p同时后移 { prev = prev->next; } } return basic; } //快速排序 void quick_sort(node *head, node *tail) { //头结点是空的或者表是空的或者表只有一个节点时候不用排 //tail是链表的结束点,一开始等于NULL,后面等于basic if(!head || head->next == tail || head->next->next == tail) { return ; } //baisc前的节点都比basic小,baisc后的节点都比baisc大 node *basic = partition(head, tail); printf("本次划分节点:%d\n", basic->data); quick_sort(head, basic); //把head->next到basic前一个进行递归排序 quick_sort(basic, tail); //从basic->next到tail前一个进行递归排序 } int main(void) { int i, len; node *head, *new, *p, *q; printf("请输入单链表的长度: "); scanf("%d", &len); head = initList(); head->data = len; printf("请输入元素:"); for(int i = 0; i<len; i++) { new = initList(); scanf("%d", &new->data); head_insert(head, new); } printf("请选择排序方式,1.选择排序 2.冒泡排序 3.插入排序 4.快速排序: "); scanf("%d", &i); printf("排序前:\n"); traverse(head); switch(i) { case 1: choose_sort(head); break; case 2: bubble_sort(head); break; case 3: insert_sort(head); break; case 4: quick_sort(head, NULL); break; } printf("由小到大排序后:\n"); traverse(head); return 0; }
3、2048小游戏(某个项目)
原博客链接:C语言版---2048小游戏,简单易懂,看过就会
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <conio.h> #define N 4 //定义阶数 #define up 72 //键盘上键 #define down 80 //键盘下键 #define left 75 //键盘左键 #define right 77 //键盘右键 #define esc 27 //键盘esc键 //产生n个随机数 void Random(int *data, int n) { for(int i = 0; i<n; i++) { data[i] = rand()%N; } } //初始化游戏,随机生成两个数字方块 void initGame(int data[N][N], int num) { int a[4]; while(1) { Random(a, 4); //产生两个坐标 //如果坐标不重合,就退出 if(a[0] != a[2] && a[1] != a[3]) { data[a[0]][a[1]] = num; data[a[2]][a[3]] = num; break; } } } //判断是否还有空位 int checkSpace(int data[N][N]) { int i, j, n; n = 0; for(i = 0; i<N; i++) { for(j = 0; j<N; j++) { if(!data[i][j]) //还有空位 { n++; } } } return n; } //选择一个空坐标,生成数字 void produceXY(int data[N][N], int num) { int i, j, flag, a[2]; flag = checkSpace(data); if(!flag) //如果没有空位就不用产生数字 { return; } while(1) //一定可以找到空位并产生数字 { flag = 0; Random(a, 2); //随机产生一个坐标 for(i = 0; i<N; i++) { for(j = 0; j<N; j++) //尝试找出一个空坐标 { if(data[i][j] == 0 && a[0] == i && a[1] == j) { data[a[0]][a[1]] = num; //成功找到空坐标 return; } } } } } //有空位就向左移动 void pushLeft(int data[N]) { int i, j = 0; for (i = 0; i < N; i++) { if (data[i] != 0) { data[j] = data[i]; //每个位都要判断一次 j++; } } //将左移后的产生的新空位标识出来 for(;j<N;j++) { data[j] = 0; } } //复制二维数组,方便旋转 void copyData(int data[N][N], int tmp[N][N]) { int i, j; for(i = 0; i<N; i++) { for(j = 0; j<N; j++) { tmp[i][j] = data[i][j]; } } } //将游戏界面顺时针旋转90°*count, count表示旋转次数 void rotateMatrix(int data[N][N], int count) { int i, j, tmp[N][N]; while(count) { copyData(data, tmp); for(i = 0; i<N; i++) { for(j = 0; j<N; j++) { data[i][j] = tmp[N-j-1][i]; } } count--; } } //向左合并 int mergeLeft(int data[N][N]) { int i, j, flag; flag = 0; //假设不能向左合并 for(i = 0; i<N; i++) { pushLeft(data[i]); for(j = 0; j<N-1; j++) { if(data[i][j] == data[i][j+1]) //找到相等的就左右合并 { flag = 1; //有合并过就可以继续游戏 data[i][j] += data[i][j]; data[i][j+1] = 0; //标记空位 pushLeft(data[i]); //重新有空位就左移 } } } return flag; //返回标记值 } //向右合并 int mergeRight(int data[N][N]) { int flag = 0; rotateMatrix(data, 2); //将游戏界面先顺时针旋转180° flag = mergeLeft(data); //向左合并 rotateMatrix(data, 2); //还原游戏界面,顺时针旋转180° return flag; } //向上合并 int mergeUp(int data[N][N]) { int flag = 0; rotateMatrix(data, 3); //将游戏界面先顺时针旋转270° flag = mergeLeft(data); //向左合并 rotateMatrix(data, 1); //还原游戏界面,顺时针旋转90° return flag; } //向下合并 int mergeDown(int data[N][N]) { int flag = 0; rotateMatrix(data, 1); //将游戏界面先顺时针旋转90° flag = mergeLeft(data); //向左合并 rotateMatrix(data, 3); //还原游戏界面,顺时针旋转270° return flag; } //检查是否能继续游戏 int check(int data[N][N]) { int i, j, n; for(i = 0; i<N; i++) { for(j = 0; j<N; j++) { if(data[i][j] == 2048) //如果等于2048, 游戏胜利 { return 2; } } } n = checkSpace(data); if(n) //如果还有空位就可以继续游戏 { return 1; } n = 0; //如果没有空位, 但是还可以合并, 也可以继续游戏 for(i = 0; i<N; i++) { for(j = 0; j<N-1; j++) { if(data[i][j]==data[i][j+1] || data[j][i] == data[j+1][i]) { n++; //存在左右数字相等或者上下数字相等情况,可以继续游戏 } } } if(n) { return 1; } return 0; } //显示游戏界面 void show(int data[N][N]) { printf("\n\t2048小游戏"); for (int i = 0; i < N; i++) { printf("\n|______|______|______|______|\n|"); for (int j = 0; j < N; j++) { if (data[i][j] == 0) { printf("%5c |", ' '); } else { printf("%5d |", data[i][j]); } } } printf("\n|______|______|______|______|\n"); } int main(void) { char ch; int win; srand(time(0)); int data[N][N] = {0}; //游戏界面初始数据 initGame(data, 2); while(1) { show(data); win = check(data); //检查能否继续游戏 if(win == 0) { printf("游戏结束\n"); return 0; } if(win == 2) { printf("恭喜你,胜利了\n"); return 0; } ch = getch(); //获取一个字符,不回显 switch(ch) { case up: //上移 mergeUp(data); produceXY(data, 2); break; case down: //下移 mergeDown(data); produceXY(data, 2); break; case left: //左移 mergeLeft(data); produceXY(data, 2); break; case right: //右移 mergeRight(data); produceXY(data, 2); break; case esc: printf("2048小游戏不是这么简单的哦\n"); return 0; } system("cls"); //每次按键后,清屏,然后显示新的游戏界面 } return 0; }
憧憬
未来我主要从事嵌入式领域的开发,当然我也会继续保持写博客的习惯。如果猿友对嵌入式感兴趣可以提前点个关注,如果你是大学生,也可以关注一下,我写的博客很符合当代大学生学习IT的过程,特别是想认真学习的,毕竟我也是大学生,不过快毕业了。
我一直有一个关于计算机的梦想,就是能够独立做出一台计算机。从芯片,操作系统,编译器,日常办公软件的开发,全都由自己完成,很显然,芯片这方面没有机器是做不出来,最多只能设计芯片,那就设计芯片吧。所以接下来几年在从事嵌入式领域的过程中,我会更加关注硬件电路的设计,同时也进一步理解操作系统的原理和编译原理。虽然学校里讲过,但是感觉只是走个流程,离做出一个操作系统或者编译器还是有很遥远的距离。但是不怕,我约定三年为期,做出一个操作系统和编译器和设计一个芯片,不管性能怎样,只要能完成日常生活的要求,看视频,听音乐,上网,打游戏这些,就是成功的。
我目前快毕业了,有哪位独具慧眼的HR看到这篇文章,希望可以给我一个面试的机会,我一定不负众望。