头文件
#pragma once
#ifndef _INC_LS_2_
#define _INC_LS_2_
//本项目主要采用了邻接表的思想,但是表中头节点并不是通过顺序结构组成,而是通过链式连接,这样方便了内存的开辟,
//可以动态添加删除。同时虽然是邻接表但是某个表头结点的后面连接的还是表头结点,我称它为虚结点,并不是真正的生成了
//一个结点。这样复杂的关系构成了更像是图,看似不好处理,但是仔细思考后发现,孩子链(我称表头所连接的链)就是二叉树结构
//所以实现起来就很方便了。
/*为什么要使用这么复杂的结构呢,其实很大程度上解决了查询的问题,虽然我没写查询,但是该做的我都做了,只要给一个信息
* 我就能知道它转发自谁,更能找到这个结点转发转发的人是谁。。。。。。都能找到,而且最多只需要遍历一边就够了,只要找到
* Box中的一个结点。然后进而可以向前找这个结点的childPre,或者找这个结点的child,等等十分灵活,着重要注意的是,项目进行过程中,
* 对于NULL指针的处理十分重要,有的时候无用指针必须复制为NULL否则垃圾值影响项目。
*/
/*
姓名:李明翰
项目:第二个微博转发的项目
感想:我觉得我写的这个也还行了,脑袋一热写了个复杂的结构,
有点头疼,该说不说这个结构,十分灵活,这个结构也参考了集合合并,
也就是森林合并,本项目核心是删除结点,本想森林二叉树化(这是算法中的经典操作)奈何太太懒了,
还麻烦,眼前一亮的发现孩子链经过加工改造就是二叉树,同时想要进行深度优先搜索,找到所有要删除的结点,
但还不想遍历到没有用的结点,那就只能遍历要删除结点孩子链,项目的优点也在这儿,删除某条微博,
并不需要遍历到不需要删除的结点,遍历的都是需要删除的结点。优点不仅如此,查找也十分方便。
知识点:森林、森林合并、二叉树、层次遍历,集合合并(每一株树都是一个集合,这个集合都有共同的特点:微博内容相同)
*/
#define TITLE_SIZE 128
#define FILE_READ_SUCCESS 0
#define FILE_READ_FAIL 0
#define BUFFER_SIZE_MAX 160
#define INVAILED_ID -1
#define QUEUE_EMPTY 1
typedef struct {
struct Node* ListHead; //作为队列的头结点
struct Node* ListTail; //作为队列的尾结点
}List;
typedef struct Node {
int userID;
int fatherID;
char title[TITLE_SIZE];
struct Node* childPre; //为了好删除孩子链表所以增加pre指针
struct Node* child; //如果有孩子链,这个child就指向链,链的连接通过childPre和childNext连接,child是我设置的标志指针,起到了标记的作用。
struct Node* childNext; //孩子链中各个结点通过childNext链接
struct Node* pre; //作为表头结点时候的指向前一个的指针
struct Node* next; //Box表头通过pre和next链接
}Box_Node;
/*存储列表Box*/
/*缓存队列,保存想要删除的结点que*/
/*二叉树队列临时,二叉树层次遍历时候用到(虽然是一个图结构,但是某一部分符合二叉树)someque*/
/*释放资源*/
void freeList();
/*写文件*/
void writeFile();
/*放到列表或者队列尾部,多功能*/
/*改变队列或者链表的指针所以用双指针*/
/**
* @brief push压入队列,对于Box,que,someque这三个队列通用,压入的结点是利用next和pre这两个指针。
*
* @param[in] Head 要压入队列的头指针
* @param[in] Tail 要压入队列的尾指针
* @param[out] phHandle 解析后得到的句柄
*
* @return void
*
* @details 作用很大
*/
void push(Box_Node** Head, Box_Node** Tail, Box_Node* newNode);
/*
*
* @brief pop弹出队列front
* @param[in] Head 要弹出队列的头指针
* @param[in] Tail 要弹出队列的尾指针
* @param[out] Box_Node* 返回弹出的结点指针
*
* @return void
*
* @details 作用很大
*/
Box_Node* pop(Box_Node** Head, Box_Node** Tail);
/**
*
*
* @brief 判断队列是否空了
* @param[in] vec 要判断的队列
*
* @return int 返回1表示空了,0表示没空
*
* @details 作用很大
*/
int isempty(List vec);
/**
*
*
* @brief 创建结点,根据文档的create字段
* @param[in] str 输入一个字符串包含结点信息
*
* @return void
*
* @details 作用很大
*/
void createMB(char* str);
/**
*
*
* @brief 转发结点,根据文档的forward字段
* @param[in] str 输入一个字符串包含结点信息
*
* @return void
*
* @details 作用很大
*/
void forwardMB(char* str);
/**
*
*
* @brief 改变现有结构,在Box中摘除需要删除的元素。
* @param[in/out] Head 输入Box头表的头指针
* @param[in/out] Tail 输入Box头表的尾指针
* @param[in] deleteN 输入要从Box中摘除的结点
* @return void
*
* @details 这个函数需要改变Box主表的结构,同时如果这个deleteN也作为孩子链结点存在于某个结点的孩子链中,
* 那么也要改变孩子链的结构。这个change函数是这个项目的灵魂,同时每次使用这个函数的结点都是要删除的结点,
* 所以需要把这个deleteN加入到que删除队列中。
*/
void change(Box_Node** Head, Box_Node** Tail, Box_Node* deleteN);
/**
*
* @brief 主要用来遍历孩子链
*
* @param[in] root 输入要删除结点的海子链的第一个结点
*
* @return void
*
*
* @details 采用层次遍历的方式遍历这个海子链,虽说这是一个复杂的机构,但是如果只看某个孩子链的话这就是一个二叉树同时在遍历的同时使用change函数把要删除的结点加入到que缓存队列中,这个过程中没有新建任何结点。辅助这个函数的是someque队列
*
*/
void LeverOrder(Box_Node* root);
/**
*
* @brief 删除微博,根据str
*
* @param[in] str 输入要删除结点的信息
*
* @return void
*
*
*
* @details 这个函数调用了change和LeverOrder函数,一起构成了删除微博的函数。
*/
void deleteMB(char* str);
/*读文件*/
int readFile();
void test2();
#endif
#include"ls_2.h"
#include<stdio.h>
/*存储列表*/
List Box = {NULL,NULL};
/*缓存队列,保存想要删除的结点*/
List que = {NULL,NULL};
/*二叉树队列临时,二叉树层次遍历时候用到(虽然是一个图结构,但是某一部分符合二叉树)*/
List someque = { NULL,NULL };
/*释放资源*/
void freeList() {
while (Box.ListHead) {
Box_Node* t = Box.ListHead->next;
free(Box.ListHead);
Box.ListHead = t;
}
Box.ListHead = NULL;
Box.ListTail = NULL;
}
/*写文件*/
void writeFile() {
FILE* fw = fopen("C:\\Users\\13726\\Desktop\\cmlearn\\指针\\microblog_back.txt", "w");
if (fw == NULL) {
printf("文件写入失败\n");
return;
}
Box_Node* tmp = Box.ListHead;
while (tmp) {
fprintf(fw, "ID:%d title:%s\n", tmp->userID, tmp->title);
tmp = tmp->next;
}
fclose(fw);
}
/*放到列表或者队列尾部,多功能*/
/*改变队列或者链表的指针所以用双指针*/
void push(Box_Node** Head, Box_Node** Tail, Box_Node* newNode) {
if (*Head == NULL) {
*Head = newNode;
}
else {
(*Tail)->next = newNode;
newNode->pre = *Tail;
}
*Tail = newNode;
}
Box_Node* pop(Box_Node** Head, Box_Node** Tail) {
Box_Node* front = *Head;
*Head = (*Head)->next;
if (*Head == NULL) {
*Tail = NULL;
}
return front;
}
int isempty(List vec) {
if (vec.ListHead == NULL && vec.ListTail == NULL && vec.ListHead == vec.ListTail) {
return 1;
}
return 0;
}
/*创建微博*/
void createMB(char* str) {
//申请一个MB结构
Box_Node* mb = (Box_Node*)malloc(sizeof(Box_Node));
mb->pre = NULL;
mb->next = NULL;
mb->childNext = NULL;
mb->child = NULL;
mb->childPre = NULL;
mb->fatherID = -1;
while (*str > '9' || *str < '0') {
str++;//过滤非数字字符
}
mb->userID = atoi(str);
while (*str != '"') {
str++;
}
++str;
int i = 0;
while (*str != '"') {
mb->title[i++] = *str++; //不包括引号
}
mb->title[i] = '\0';
//放到列表里
push(&Box.ListHead, &Box.ListTail, mb);
}
/*转发微博*/
void forwardMB(char* str) {
//申请一个MB结构
Box_Node* mb = (Box_Node*)malloc(sizeof(Box_Node));
mb->pre = NULL;
mb->next = NULL;
mb->childNext = NULL;
mb->child = NULL;
mb->childPre = NULL;
while (*str > '9' || *str < '0') {
str++;//过滤非数字字符
}
mb->userID = atoi(str);
while (*str != '@') {
str++;
}
str++;
mb->fatherID = atoi(str);
//寻找父亲的ID
Box_Node* tmp = Box.ListHead;
while (tmp) {
if (tmp->userID == mb->fatherID) {
break;
}
tmp = tmp->next;
}
strcpy(mb->title, tmp->title);
//加入到孩子链中,如果没有转发过别人,就不会作为childNext存在
Box_Node* t = tmp->child;
if (t) {
while (t&&t->childNext) {
t = t->childNext;
}
t->childNext = mb;
t->childNext->childPre = t;
}
else {
tmp->child = mb;
tmp->child->childPre = tmp;
}
//存储到链表里
push(&Box.ListHead, &Box.ListTail,mb);
}
//改变现有结构,在Box中摘除需要删除的元素。
void change(Box_Node** Head, Box_Node** Tail, Box_Node* deleteN) {
//Box_Node* tmp = *Head;
//从原来的队列中删除
if (*Head == deleteN) {
*Head = deleteN->next;
if (deleteN->next) {
deleteN->next->pre = NULL;
}
}
else if (deleteN == *Tail) {
*Tail = deleteN->pre;
//前向
deleteN->pre->next = NULL;
}
else {
deleteN->next->pre = deleteN->pre;
//前向
deleteN->pre->next = deleteN->next;
}
//更改孩子链,从box中摘除也同时需要摘除孩子链中的该结点,防止查询时出错。
if (deleteN->childNext&&deleteN->childPre) {
deleteN->childNext->childPre = deleteN->childPre;
deleteN->childPre->childNext = deleteN->childNext;
}
else if (deleteN->childPre&&deleteN->childPre->child == deleteN) {
deleteN->childPre->child = NULL;
}else if (deleteN->childNext == NULL && deleteN->childPre) {
deleteN->childPre->childNext = NULL;
}
push(&que.ListHead, &que.ListTail, deleteN);
//前面已经摘除结点所以next和pre没用了,后面废物利用作为删除队列的链接指针。
deleteN->next = NULL;
deleteN->pre = NULL;
}
//有点类似层次遍历,但由于没有必要访问左节点故不需要入队左结点。
//森林转换为二叉树过于复杂
void LeverOrder(Box_Node* root) {
if (root == NULL) {
return;
}
change(&Box.ListHead, &Box.ListTail, root);
push(&someque.ListHead, &someque.ListTail, root);
while (!isempty(someque)) {
Box_Node* nowcur = pop(&someque.ListHead,&someque.ListTail);//出队
if (nowcur->child) {
change(&Box.ListHead, &Box.ListTail, nowcur->child);
//如果push了就会改变next和pre,为了维护这俩指针,需要先拆后压。
push(&someque.ListHead, &someque.ListTail, nowcur->child);
}
if (nowcur->childNext) {
change(&Box.ListHead, &Box.ListTail, nowcur->childNext);
push(&someque.ListHead, &someque.ListTail, nowcur->childNext);
}
}
}
/*返回一个需要删除的链*/
void deleteNode(Box_Node** Head, Box_Node** Tail,int deleteID) {
//寻找父亲的title
Box_Node* tmp = *Head;
Box_Node* result = NULL;
while (tmp) {
if (tmp->userID == deleteID) {
break;
}
tmp = tmp->next;
}
change(Head, Tail, tmp);
LeverOrder(tmp->child);
return;
}
/*删除微博*/
void deleteMB(char* str) {
while (*str > '9' || *str < '0') {
str++;
}
int deleteID = atoi(str);
deleteNode(&Box.ListHead,&Box.ListTail,deleteID);
while (que.ListHead) {
Box_Node* t = que.ListHead->next;
free(que.ListHead);
que.ListHead = t;
}
que.ListHead = NULL;
que.ListTail = NULL;
}
/*处理字符串*/
void parseString(char* str) {
if (strncmp(str, "create", 6) == 0) {
createMB(str);
}
else if (strncmp(str, "forward:", 7) == 0) {
forwardMB(str);
}
else if (strncmp(str, "delete:", 6) == 0) {
deleteMB(str);
}
}
/*读文件*/
int readFile() {
FILE* fr = fopen("C:\\Users\\13726\\Desktop\\cmlearn\\指针\\microblog.txt", "r");
if (fr == NULL) {
return FILE_READ_FAIL;
}
char buffer[BUFFER_SIZE_MAX] = "";
while (fgets(buffer, BUFFER_SIZE_MAX, fr)) {
parseString(buffer);
}
fclose(fr);
return FILE_READ_SUCCESS;
}
void test2() {
if (readFile() != FILE_READ_SUCCESS) {
printf("文件打开失败\n");
}
writeFile();
freeList();
}