目录
写链表之前的感想:
-
写链表的意义:
- 因为后期学习到c++之类的都有自己写好的容器(包含链表),比自己写的好太多了,因此觉得没必要自己花时间熟练链表的编写。但是逐渐了解了自己想法的错误。
- 第一个意义,觉得是自己当前写链表的主要原因,因为身边同学大四找工作,公司招人的时候大多问的是数据结构方面的,虽然不一定是链表,但是让我了解到数据结构的重要性,因此也让我重新整理自己数据结构的知识点。
- 第二:写了链表可以更好的让自己了解到已有的容器结构等,更好的使用这些工具(私下认为并没多大帮助,要更好的使用容器应该是看容器函数的结构,当然不乏一些大佬可以触类旁通)。
- 第三:大学教学需要,当别人不会时你可以教他们很有成就感,装B满满的。
- 如果有其他意义可以提出,因为对于初学者来说一个对他们来说有意义的事情才能让他们坚持学下去
- 因为后期学习到c++之类的都有自己写好的容器(包含链表),比自己写的好太多了,因此觉得没必要自己花时间熟练链表的编写。但是逐渐了解了自己想法的错误。
- 之前写了很多次的链表,但是过一段时间就会忘记了。虽然有些是因为记性不好的原因,但是更多的是因为没有经过自己的总结。
- 链表其实的实现方法有很多种,不同的书的写法可能不同,但是都是大同小异,只要掌握了链表的根本和链表编写的一条逻辑线就能顺畅的写出链表
总结是什么:
- 之前看到一句话说的很好,失败不是成功之母,总结失败才是成功之母。
- 在我高中时期,老师一直说我只是做题没有总结。我总是心里暗暗说我一直有总结啊,我做题目看完答案解析,感觉自己会了就是这么做的,但是下次还是不会。到我大学的时候看了很多视频才明白别人的知识点永远都是别人的,即使你记住了也不是你的。因为你只是跟着别人的那条路,那条路虽然可以直通大道,但是不是你自己熟悉的,当你自己独自回忆的时候往往不能完全别人的路走下去,往往是走到一半就忘了怎么走了(这里排除记忆力超常的天才之类的人)。
- 因此要想真正掌握知识点,要么就是不断练习这条路,要么就是参考别人的总结,根据自己的思考圈圈绕绕,然后开辟一条自己熟悉的路,也许这条路并不简单但是是自己最熟悉的路,记忆也最深(这点跟记忆宫殿有点像,都是依靠自己熟悉的事物逻辑进行记忆)。
- 这个是我大三时期才明白的道理,说晚也不晚,只是有点惋惜很多知识点都是不断练习掌握的,很多都忘了,所以写下该总结感想希望对其他人有所帮助。
怎么写单向链表:
- 这个博客主要是想给新手一些帮助的,让新手了解链表应该怎么编写。链表的命名并不规范,链表的功能如果有什么差错请大家多多指点。
- 前面说了,链表的写法各种各样,但是都是大同小异。
-
链表的根本
- 不用多说,了解过的人都知道单向链表其实类似数组的一种结构,但是他是通过指针连接起来的,可以随时添加结点,相当于一个个环连接在一起。首先要清楚一个单向链表是由数据域和指针域组成的。数据域就是存放数据的结构,可以是类可以是结构体,也可以直接在结点里面写属性。
- 因此我做了三个类,用来表示单向链表,数据域类、结点类、链表类,这些类先写了他们的属性、构造函数和析构函数,其他函数在后面边写边添加
- 数据域的类,如下所示
//链表的数据域 class Date { private: int date; //可以自己添加想要的属性 public: Date(); ~Date(); //下面这两个函数可以先不看 void setDate(int num); int getDate(); };
- 数据域和指针域是存放在结点里的,就相当于一个环,因此写了个结点类
//链表的结点 class Node { private: Date* date; //结点里面的数据域 Node* pNext; //指针域 public: Node(); ~Node(); //下面函数先不需要看,后面会讲解 void node_setDate(Date* date); Date* node_getDate(); Node* getNextNode(); void setNextNode(Node* node); };
- 有了结点就可以定义一个头结点了,这个结点是为了确定链表的地址,通过这个地址就能找到所有链表结点,因此他必须是固定存在的,你可以做全局变量之类的只要能让这个头结点存在就行了。而我是做了一个链表类,里面属性有头结点,因此一个链表对象就是一个链表
//List表示一个链表对象,因此要存放一个链表需要的元素 //链表的头结点、长度 class List { private: Node* head; //链表的头结点,不变 int listLength; //链表的长度 public: List(); ~List(); //可以先不看 void list_nodeAdd(Date* date); Node* list_getNode(int pos); Node* list_getNode(Date* date); void listInsert(int pos, Date* date); void listDeleteNode(int pos); Date* listFindNode(Date* date); Node* listFindNode(int pos); void listPrint(); };
-
函数的实现
- 有了这三个部分整个单向链表的实现已经实现了一大半了,剩下的就是这三个类里面的函数实现了。
-
因此逻辑思路就是通过链表的增删改查操作来一点点完善代码,这个需要对链表比较熟悉,有一定的逻辑思路才能比较顺利的实现
- 数据的操作一般就是增删改查、我是需要什么函数再去写这个函数,因为实在记不下来。
- 注意:如果不熟悉链表编写,需要做完一个功能就验证这个功能,这样才不容易出错。因此这边建议可以先写链表的一个功能,然后就写数据的打印,通过数据的打印查看是否成功。
-
单向链表的逻辑思路
- 下面举数据添加然后进行检查作为实例,我是先写类的属性,然后写链表的增删改查的函数,因此类的函数都是在链表类需要的时候添加上去的:
- 我先写的是数据的添加
void List::list_nodeAdd(Date* date) { Node* ptemp = head; Node* node = new Node; node->node_setDate(date); //找到最后的一个结点 while (ptemp->getNextNode() != NULL) { ptemp = ptemp->getNextNode(); } ptemp->setNextNode(node); this->listLength++; //每添加一个结点就++,因此第一个结点的数值就为1 }
- 然后写数据的打印
void List::listPrint() { Node* node = this->head; while (node->getNextNode() != NULL) { node = node->getNextNode(); cout << node->node_getDate()->getDate() << endl; } cout << "长度:" << this->listLength<<endl; }
- 写函数时的步骤逻辑:
- 上面的数据检测中的链表结点添加,写的时候发现结点需要数据,因此就去结点类里面添加设置结点数据函数
void Node::setNextNode(Node* node) { this->pNext = node; }
- 发现链表循环遍历的时候需要获取下一个结点,因此就写了获取下一个结点的函数
Node* Node::getNextNode() { return this->pNext; }
- 添加结点进入链表的时候需要将指针指向下一个结点,因此需要设置结点下一结点的函数
void Node::setNextNode(Node* node) { this->pNext = node; }
- 上面的数据检测中的链表结点添加,写的时候发现结点需要数据,因此就去结点类里面添加设置结点数据函数
- 函数的测试
- 这里发现还要数据域的数据添加,可以在类的构造函数里面直接传参添加,也可以写一个添加数据的函数
void Date::setDate(int num) { this->date = num; }
- 测试代码:
List* list = new List; for (int i = 0; i < 5; i++) { Date* date = new Date; date->setDate(i); list->list_nodeAdd(date); } list->listPrint();
- 这里发现还要数据域的数据添加,可以在类的构造函数里面直接传参添加,也可以写一个添加数据的函数
源代码
- 我将数据域的类、结点类和链表类全放在一个文件里面,这种写法不推荐,因为当文件多起来有可能发生文件的包含,一般是为完成一个功能才将这些类放在一个文件内
- 其他的数据操作大概也是这样的逻辑思路,需要的时候再去添加,函数不是死的,可以有多种实现方式。因此下面也不进行多余赘述,直接放源代码:
- main.cpp
#include <iostream> #include "list.h" using namespace std; int main() { List* list = new List; for (int i = 0; i < 5; i++) { Date* date = new Date; date->setDate(i); list->list_nodeAdd(date); } list->listPrint(); Date* date = new Date; date->setDate(12); list->listInsert(1, date); list->listPrint(); list->listDeleteNode(6); list->listPrint(); Node* ptemp = list->listFindNode(1); if (ptemp != NULL) { cout << ptemp->node_getDate()->getDate(); } system("pause"); return 0; }
- list.cpp
#include "list.h" #include <iostream> #include <string> using namespace std; Date::Date() { this->date = 0; } void Date::setDate(int num) { this->date = num; } int Date::getDate() { return this->date; } Date::~Date() {} Node::Node() { this->date = NULL; this->pNext = NULL; } void Node::node_setDate(Date* date) { this->date = date; } Date* Node::node_getDate() { return this->date; } Node* Node::getNextNode() { return this->pNext; } void Node::setNextNode(Node* node) { this->pNext = node; } Node::~Node() { if (this->date != NULL) { delete this->date; this->date = NULL; } if (this->pNext != NULL) { delete this->pNext; this->pNext = NULL; } } List::List() { this->head = new Node; this->listLength = 0; //head头结点不记录为结点,为空 } void List::list_nodeAdd(Date* date) { Node* ptemp = head; Node* node = new Node; node->node_setDate(date); //找到最后的一个结点 while (ptemp->getNextNode() != NULL) { ptemp = ptemp->getNextNode(); } ptemp->setNextNode(node); this->listLength++; //每添加一个结点就++,因此第一个结点的数值就为1 } //通过数字获取结点 Node* List::list_getNode(int pos) { Node* node = this->head; //函数是从1开始的,没有0 if (pos == 0) { cout << "从1开始计算结点" << endl; node = NULL; } if (pos == 1) { node = head->getNextNode(); } if (pos > this->listLength || pos < 0) { cout << "不存在该结点" << endl; node = NULL; } if (pos > 1 && pos <= this->listLength) { for (int i = 0; i < pos; i++) { node = node->getNextNode(); } } return node; } //通过数据域获取结点 Node* List::list_getNode(Date* date) { Node* node = head; while (node->getNextNode() != NULL) { node = node->getNextNode(); if (node->node_getDate() == date) { break; } else { node = NULL; } } return node; } void List::listInsert(int pos, Date* date) { Node* ptemp = this->head; Node* node = new Node; if (pos > 0 && pos <= this->listLength) { //因为是从1开始的,所以pos-1 for (int i = 0; i < pos-1; i++) { ptemp = ptemp->getNextNode(); } //当位置和长度相同的时候单独考虑 if (pos == this->listLength) { node->node_setDate(date); ptemp->getNextNode()->setNextNode(node); this->listLength++; return; } node->node_setDate(date); node->setNextNode(ptemp->getNextNode()); ptemp->setNextNode(node); this->listLength++; } else { cout << "不存在该结点" << endl; } } void List::listDeleteNode(int pos) { Node* ptemp = this->head; Node* deleteNode = new Node; //删除的时候是找到要删除的前一个结点,因为如果找到的是要删除的结点就需要 //前一个结点,而单向链表不能随时找到前一个结点 if (pos > 0 && pos <= this->listLength) { for (int i = 0; i < pos - 1; i++) { ptemp = ptemp->getNextNode(); } deleteNode = ptemp->getNextNode(); ptemp->setNextNode(ptemp->getNextNode()->getNextNode()); deleteNode->setNextNode(NULL); delete deleteNode; this->listLength--; } else { cout << "不存在该结点"<<endl; } } Date* List::listFindNode(Date* date) { Node* ptemp = this->head; while (ptemp->getNextNode() != NULL) { ptemp = ptemp->getNextNode(); if (ptemp->node_getDate() == date) { return ptemp->node_getDate(); } } return NULL; } Node* List::listFindNode(int pos) { Node* ptemp = this->head; if (pos > 0 && pos <= this->listLength) { for (int i = 0; i < pos; i++) { ptemp = ptemp->getNextNode(); } return ptemp; } else { cout << "不存在该结点" << endl; } return NULL; } void List::listPrint() { Node* node = this->head; while (node->getNextNode() != NULL) { node = node->getNextNode(); cout << node->node_getDate()->getDate() << endl; } cout << "长度:" << this->listLength<<endl; } List::~List() { if (this->head != NULL) { delete this->head; this->head = NULL; } }
- list.h
#pragma once //链表的数据域 class Date { private: int date; //可以自己添加属性 public: Date(); ~Date(); void setDate(int num); int getDate(); }; //链表的结点 class Node { private: Date* date; //结点里面的数据域 Node* pNext; //指针域 public: Node(); ~Node(); void node_setDate(Date* date); Date* node_getDate(); Node* getNextNode(); void setNextNode(Node* node); }; //List表示一个链表对象,因此要存放一个链表需要的元素 //链表的头结点、长度 class List { private: Node* head; //链表的头结点,不变 int listLength; //链表的长度 public: List(); ~List(); void list_nodeAdd(Date* date); Node* list_getNode(int pos); Node* list_getNode(Date* date); void listInsert(int pos, Date* date); void listDeleteNode(int pos); Date* listFindNode(Date* date); Node* listFindNode(int pos); void listPrint(); };