最近在学c++,但是网上基于c++的数据结构和算法教程很少,毕竟c++有stl,想用什么数据结构都直接调用了,可是找工作的话,面试官不会让你调库啊,所以自己尝试着用c++来实现各种数据结构和算法。好在网上关于数据结构的资料不少,只是语言有区别而已,还是有很多可以借鉴的地方。
关于单向链表,原理这部分我觉得没什么可说的了,就是每个存数据的结点分为两部分,一部分是数据,一部分是指针,通过指针指向下一个结点,由此所有数据就一个接一个的连起来了。并且还得有个头结点,这个结点不存数据,只存指向第0个数据结点的指针,帮助我们找到这串数据。
下面开始敲代码:
1.首先就是结点,我们需要先声明和定义结点。它包含两种类型的数据,那肯定就得用结构体,或者类了,我这里就用类了,反正c++里这两者也没啥大区别。并且,为了提高泛用性,我用了模板,就是加了一行“template<class T>” 罢了,用T去代替什么int,float之类的数据类型。
template<class T>
class Node {
public:
T value;
Node* next;
Node(T v,Node* n) {
value = v;
next = n;
}
};
结点这个类里就有两个成员变量:T类型的value和Node类型的指针next。顾名思义,value就是存的数据,next就是指向下一个结点的指针。还有一个有参构造函数,这个不写,后面就会稍微麻烦一点。
2.接下来就是单向链表这个类了,我在这个类里肯定就要我之前说的“头结点”,不然怎么在内存中找到这些数据呢,还要有一个int类型的变量指示当前链表中存数据的结点个数。并且,我还在里面写了一些有功能的函数。还有要注意的是,由于结点用了模板<T>,那这个类也要用模板。
template<class T>
class Link_list {
public:
Node<T>* head; //头结点,指向第一个存储的数据
int N; //记录链表的长度
//初始化头结点和链表长度
Link_list() {
head = new Node<T>(NULL, NULL); //在堆区开辟一块空间,这里的<T>不能省略
N = 0;
}
//清空链表
//只要让头结点不指向第一个数据结点就行了,这样就不能访问后面的数据了。
//很简单,但缺点就是可能造成内存泄漏
void clear_list() {
head = NULL;
N = 0;
}
//获得链表的长度
int get_length() {
return N;
}
//判断链表是否为空
bool is_empty() {
return N == 0;
}
//获得第i处的值
T get_value(int i) {
//要从头结点开始
Node<T>* n = head;
//这肯定要加个越界判断嘛
if (i > N) {
cout << "索引过大!" << endl;
return NULL; //函数返回值类型是T,所以用NULL。
}
//用for循环让n变成第i个数据结点(当h=i-1时,也就是最后一次循环,n就变成第i个数据节点了)
for (int h = 0; h < i; h++) {
n = n->next;
}
return n->value;
}
//尾插
void insert(T v) {
Node<T>* n = head;
//for循环让n变成最后一个结点
for (int h = 0; h < N; h++) {
n = n->next;
}
//生成一个存有数据v的新结点ni
Node<T>* ni = new Node<T>(v, NULL);
//让原本链表的最后一个结点指向ni
n->next = ni;
//别忘了链表长度加一
N++;
}
//在i处插入
void insert(T v, int i) {
Node<T>* n = head;
//这里是让n成为第i-1个数据结点
for (int h = 0; h < i; h++) {
n = n->next;
}
Node<T>* ni = new Node<T>(v, NULL);
//让新结点指向原本第i处的结点
//让第i-1处结点指向新结点
ni->next = n->next;
n->next = ni;
N++;
}
//移除第i处的结点
void remove(int i) {
Node<T>* n = head;
//让n变成第i-1处的结点
for (int h = 0; h < i; h++) {
n = n->next;
}
//把第i处结点(i-1的next)存的指针,也就是指向第i+1处的指针先存起来
Node<T>* ntemp = n->next->next;
//删掉第i处的结点(所以前面要先存下指针,不然就删掉了)
delete n->next;
//让第i-1处的结点指向原本第i+1处的结点
n->next = ntemp;
//删掉了结点,长度要减一
N--;
}
//遍历打印链表的函数
void printT() {
Node<T>* n = head;
for (int i = 0; i < N; i++) {
cout << n->next->value << endl;
n = n->next;
}
}
};
值得注意的是,尾插和指定i处插入这两个函数的for循环,分别在N-1处和i-1处最后一次循环,但N是从1开始数的(三个数据,那N就是3),而i是从0开始数的(三个数据,最后一个数据在位置2)。
3.来测试一下
int main() {
//用int类型,所以<int>
Link_list<int> list1;
cout << list1.is_empty() << endl;
//尾插法插五个数进去
list1.insert(0);
list1.insert(1);
list1.insert(2);
list1.insert(3);
list1.insert(4);
cout <<"长度为:"<< list1.get_length() << endl;
cout << list1.is_empty() << endl;
cout << "-----------" << endl;
list1.printT();
cout << "-----------" << endl;
cout << "第0个:" << list1.get_value(0) << endl;
//在位置3处插一个值
list1.insert(31, 3);
cout << "长度为:" << list1.get_length() << endl;
cout << "-----------" << endl;
list1.printT();
cout << "-----------" << endl;
//移除位置0处的结点
list1.remove(0);
list1.printT();
system("pause");
return 0;
}
运行结果:
应该没什么问题,最后,欢迎大家评论区批评指正!
完整代码:
#include<iostream>
using namespace std;
template<class T>
class Node {
public:
T value;
Node* next;
Node(T v,Node* n) {
value = v;
next = n;
}
};
template<class T>
class Link_list {
public:
Node<T>* head; //头结点,指向第一个存储的数据
int N; //记录链表的长度
//初始化头结点和链表长度
Link_list() {
head = new Node<T>(NULL, NULL); //在堆区开辟一块空间,这里的<T>不能省略
N = 0;
}
//清空链表
//只要让头结点不指向第一个数据结点就行了,这样就不能访问后面的数据了。
//很简单,但缺点就是可能造成内存泄漏
void clear_list() {
head = NULL;
N = 0;
}
//获得链表的长度
int get_length() {
return N;
}
//判断链表是否为空
bool is_empty() {
return N == 0;
}
//获得第i处的值
T get_value(int i) {
//要从头结点开始
Node<T>* n = head;
//这肯定要加个越界判断嘛
if (i > N) {
cout << "索引过大!" << endl;
return NULL; //函数返回值类型是T,所以用NULL。
}
//用for循环让n变成第i个数据结点(当h=i-1时,也就是最后一次循环,n就变成第i个数据节点了)
for (int h = 0; h < i; h++) {
n = n->next;
}
return n->value;
}
//尾插
void insert(T v) {
Node<T>* n = head;
//for循环让n变成最后一个结点
for (int h = 0; h < N; h++) {
n = n->next;
}
//生成一个存有数据v的新结点ni
Node<T>* ni = new Node<T>(v, NULL);
//让原本链表的最后一个结点指向ni
n->next = ni;
//别忘了链表长度加一
N++;
}
//在i处插入
void insert(T v, int i) {
Node<T>* n = head;
//这里是让n成为第i-1个数据结点
for (int h = 0; h < i; h++) {
n = n->next;
}
Node<T>* ni = new Node<T>(v, NULL);
//让新结点指向原本第i处的结点
//让第i-1处结点指向新结点
ni->next = n->next;
n->next = ni;
N++;
}
//移除第i处的结点
void remove(int i) {
Node<T>* n = head;
//让n变成第i-1处的结点
for (int h = 0; h < i; h++) {
n = n->next;
}
//把第i处结点(i-1的next)存的指针,也就是指向第i+1处的指针先存起来
Node<T>* ntemp = n->next->next;
//删掉第i处的结点(所以前面要先存下指针,不然就删掉了)
delete n->next;
//让第i-1处的结点指向原本第i+1处的结点
n->next = ntemp;
//删掉了结点,长度要减一
N--;
}
//遍历打印链表的函数
void printT() {
Node<T>* n = head;
for (int i = 0; i < N; i++) {
cout << n->next->value << endl;
n = n->next;
}
}
};
int main() {
//用int类型,所以<int>
Link_list<int> list1;
cout << list1.is_empty() << endl;
//尾插法插五个数进去
list1.insert(0);
list1.insert(1);
list1.insert(2);
list1.insert(3);
list1.insert(4);
cout <<"长度为:"<< list1.get_length() << endl;
cout << list1.is_empty() << endl;
cout << "-----------" << endl;
list1.printT();
cout << "-----------" << endl;
cout << "第0个:" << list1.get_value(0) << endl;
//在位置3处插一个值
list1.insert(31, 3);
cout << "长度为:" << list1.get_length() << endl;
cout << "-----------" << endl;
list1.printT();
cout << "-----------" << endl;
//移除位置0处的结点
list1.remove(0);
list1.printT();
system("pause");
return 0;
}