注:本文以造轮子为主,属于相对理论性、教学性的东西
理解:什么是链表?
在一个区域有大量的浮空岛,每个浮空岛上有着一块小空间用来储存物品(数据域),还有一扇传送门(指针),用于通向下一个浮空岛(指针域)。
在这些所有浮空岛的最前面,有一个浮空岛头:它只有一扇门,用来通往第一个浮空岛。
对于这一堆浮空岛,我们有着以下几个基础功能:
1,初始化(创造一个浮空岛头)
2,插入一个值(为浮空岛在某个岛后加入一个新的浮空岛。)
3,获取第i个值(找到第i个浮空岛并获得值)
4,找到第一个值等于e的结点
5,删除第i个结点
(当然,除了这些功能之外,你还要在看完之后试着自己写出在浮空岛最后加入一个值,以及相关的一些其他功能)
具体代码
注:本代码没有包含main函数(我在其中加入了一些很具体的注释来帮助理解,如果喜欢读代码的朋友可以直接看)
注2:如果你不想纯粹读代码,该代码所有的具体解释请下移
#include<iostream>
#include<string>
#include<iomanip>
#include<fstream>
using namespace std;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
typedef int ElemType;
struct player
{
string ip;
string name;
};
typedef struct LNode{//用于储存内容的“域”
player data;//用于储存数据的区域,我们通常称之为数据域
struct LNode *next;//用于储存下一个域地址的域,我们通常称之为指针域
}LNode, *LinkList;//linklist,指针,指向lnode(类似于char数组中,指向单个char的指针)
int length;
Status InitList_L(LinkList &L)//初始化链表
{
L = new LNode;//创建一个新的域,作为开头,并分配空间
L -> next = NULL;//这个头域的下一个域暂且为空
return OK;
}
Status ListInsert_L(LinkList &L,int i,player &pl)//插入一个值
//单链表L中第i个位置插入值为pl的新结点
{
LinkList p,s;//p用于储存我们输入的链表,s为新表
p=L;
int j=0;
while(p && j<i-1)
{
p = p->next;
j++;
}//查找节点直到第i个,用p指向该节点
if(!p || j>i-1) return ERROR;//判断范围
s=new LNode;//生成新节点s
s->data = pl;//将数据放入s的数据域中
s->next = p->next;//让s的下一个成为原本p的下一个
p->next = s;//p的下一个变为s
length++;//长度+1
return OK;
}
Status GetElem_L(LinkList &L,int i,player &pl)//获取第i个值(在L中获取第i个并给予pl)
{
int j=1;//用于表示目前查找到第几个
LinkList p;
p = L -> next;
while(p && j<i)//查找直到p指向第i个或者p为空(超范围)
{
p = p->next;
j++;
}
if(!p || j>i)return ERROR;//范围
pl = p->data;
return OK;
}
LNode *LocateElem_L(LinkList L, string e) { //按值查找第一个id值为e的
LinkList p;
p = L->next;
while (p && p->player.id != e)//顺链域向后扫描,直到p为空或p所指结点的数据域等于e(找到或者超范围)
p = p->next;
return p; //查找成功返回值为e的结点地址p,查找失败p为NULL
}
Status ListDelete_L(LinkList &L, int i) { //删除第i个值
LinkList p, q;
int j;
p = L;
j = 0;
while ((p->next) && (j < i - 1)) //查找第i个结点,p指向该结点
{
p = p->next;
++j;
}
if (!(p->next) || (j > i - 1))return ERROR; //判断范围
q = p->next; //保存将要被删除节点,目的是为了释放
p->next = q->next; //被删除节点左右连接起来
delete q; //释放删除结点的空间
--length;//长度-1
return OK;
}
int main()
{
length =0;
return 0;
}
对于代码的具体解释
1,前设_宏定义
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
typedef int ElemType;
宏定义:define和typedef,和之前顺序表一样,都是进行一些相关的定义。由于之前顺序表已经介绍过,这里不再赘述。
2,前设_结构
struct player
{
string ip;
string name;
};
typedef struct LNode{
player data;
struct LNode *next;
}LNode, *LinkList;
int length;
依然由两部分组成,第一个结构是我们的数据,第二个结构则是我们的LinkList(也就是上面的浮空岛头)。
LNode表示的是每一个结点内的情况,它由两部分组成:
- 数据域:可以是一个变量(int,char),也可以是结构,在一些面向对象语言里也可以是对象。
player data;
- 指针域:用来指向下一个结点
struct LNode *next;
最后用一个int来记录长度。
3,函数_初始化
Status InitList_L(LinkList &L)//初始化链表
{
L = new LNode;
L -> next = NULL;
return OK;
}
和顺序表不同的是,链表最初只需要创建一个结点的大小就好了。原因是它是不连续的的,在任何时候,只要还有空间就依然可以创造新的结点。
4,函数_插入
Status ListInsert_L(LinkList &L,int i,player &pl)
{
LinkList p,s;
p=L;
int j=0;
while(p && j<i-1)
{
p = p->next;
j++;
}
if(!p || j>i-1) return ERROR;
s=new LNode;
s->data = pl;
s->next = p->next;
p->next = s;
length++;
return OK;
}
【作用解释】
在链表L的第i个位置插入一个结点,结点的值为pl
思路:
- 找到链表中的第i个位置,判断范围
int j=0;
while(p && j<i-1)
{
p = p->next;
j++;
}
if(!p || j>i-1) return ERROR;
由于查找的目的是我们思想中的第i个,所以实际上是i-1。
- 新建一个结点,将数据存入结点内。紧接着将原本上一个结点的next设定为该节点,然后将原本下一个结点设为当前结点的下一个。
s=new LNode;
s->data = pl;
s->next = p->next;
p->next = s;
思路如图
3. 长度+1
5,函数_取值
Status GetElem_L(LinkList &L,int i,player &pl)
{
int j=1;
LinkList p;
p = L -> next;
while(p && j<i)
{
p = p->next;
j++;
}
if(!p || j>i)return ERROR;
pl = p->data;
return OK;
}
【作用解释】
找到L的第i个结点,将其数据域存入pl
思路:
本质上和顺序表取值差不多,找到第i个结点,然后把他赋给&pl就好了
查找代码区:
while(p && j<i)
{
p = p->next;
j++;
}
if(!p || j>i)return ERROR;
6,函数_查找
LNode *LocateElem_L(LinkList L, string e) {
LinkList p;
p = L->next;
while (p && p->player.id != e)
p = p->next;
return p;
}
【作用解释】
找到L的第i个结点,将其数据域存入pl
思路:
和取值大同小异,一个一个往后找,直到找到第一个值和我们所找的e相同的,返回该结点
while (p && p->player.id != e)
p = p->next;
如果p不存在,或者没有任何值等于e,那么返回NULL
7,函数_删除
Status ListDelete_L(LinkList &L, int i) {
LinkList p, q;
int j;
p = L;
j = 0;
while ((p->next) && (j < i - 1))
{
p = p->next;
++j;
}
if (!(p->next) || (j > i - 1))return ERROR;
q = p->next;
p->next = q->next;
delete q;
--length;
return OK;
}
【作用解释】
找到L的第i个结点,将其删除,并将前后连接起来
思路:
- 删除一个结点更像是之前的插入结点反过来操作,首先是查找到目标点(j表示当前查找到第几个结点),判断范围
while ((p->next) && (j < i - 1))
{
p = p->next;
++j;
}
if (!(p->next) || (j > i - 1))return ERROR;
如果p->存在,且当前结点为第i-1个结点(我们现实中是从1开始数数,编程中是从0开始),结束循环。
- 将结点删除,前后结点连接。
q = p->next;
p->next = q->next;
delete q;
这里q的作用是储存我们准备删除的结点的值,以便我们可以调用delete
删除它。
关于delete:释放new分配的单个对象指针指向的内存,防止溢出
3. 长度-1
如果你是初学者:
我建议你再尝试自己实现以下几个功能:
- 循环链表(将链表的头尾相连)
- 双向链表(链表的每个结点加入一个新的方向,让我们可以倒序查找)
- 逆位序输入n个元素的值,建立到头结点的单链表L(前插法创建单链表)
- 正位序输入n个元素的值,建立带表头结点的单链表L (后插法创建单链表)
(后两个功能酌情尝试)
如果你想要没有注释的代码:
拿走,请。
#include<iostream>
#include<string>
#include<iomanip>
#include<fstream>
using namespace std;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;
typedef int ElemType;
struct player
{
string ip;
string name;
};
typedef struct LNode{
player data;
struct LNode *next;
}LNode, *LinkList;
int length;
Status InitList_L(LinkList &L)
{
L = new LNode;
L -> next = NULL;
return OK;
}
Status ListInsert_L(LinkList &L,int i,player &pl)
{
LinkList p,s;
p=L;
int j=0;
while(p && j<i-1)
{
p = p->next;
j++;
}
if(!p || j>i-1) return ERROR;
s=new LNode;
s->data = pl;
s->next = p->next;
p->next = s;
length++;
return OK;
}
Status GetElem_L(LinkList &L,int i,player &pl)
{
int j=1;
LinkList p;
p = L -> next;
while(p && j<i)
{
p = p->next;
j++;
}
if(!p || j>i)return ERROR;
pl = p->data;
return OK;
}
LNode *LocateElem_L(LinkList L, string e) {
LinkList p;
p = L->next;
while (p && p->player.id != e)
p = p->next;
return p;
}
Status ListDelete_L(LinkList &L, int i) {
LinkList p, q;
int j;
p = L;
j = 0;
while ((p->next) && (j < i - 1))
{
p = p->next;
++j;
}
if (!(p->next) || (j > i - 1))return ERROR;
q = p->next;
p->next = q->next;
delete q;
--length;
return OK;
}
int main()
{
length =0;
return 0;
}