从0开始的(c语言)数据结构学习 2:线性表-链表

注:本文以造轮子为主,属于相对理论性、教学性的东西

理解:什么是链表?

在一个区域有大量的浮空岛,每个浮空岛上有着一块小空间用来储存物品(数据域),还有一扇传送门(指针),用于通向下一个浮空岛(指针域)。
在这些所有浮空岛的最前面,有一个浮空岛头:它只有一扇门,用来通往第一个浮空岛。

对于这一堆浮空岛,我们有着以下几个基础功能:
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表示的是每一个结点内的情况,它由两部分组成:

  1. 数据域:可以是一个变量(int,char),也可以是结构,在一些面向对象语言里也可以是对象。
    player data;
  2. 指针域:用来指向下一个结点
    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

思路:
  1. 找到链表中的第i个位置,判断范围
	int j=0;
	while(p && j<i-1)
	{
		p = p->next;
		j++;
	}
	if(!p || j>i-1) return ERROR;

由于查找的目的是我们思想中的第i个,所以实际上是i-1。

  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个结点,将其删除,并将前后连接起来

思路:
  1. 删除一个结点更像是之前的插入结点反过来操作,首先是查找到目标点(j表示当前查找到第几个结点),判断范围
	while ((p->next) && (j < i - 1))
	{
		p = p->next;
		++j;
	}
	if (!(p->next) || (j > i - 1))return ERROR;

如果p->存在,且当前结点为第i-1个结点(我们现实中是从1开始数数,编程中是从0开始),结束循环。

  1. 将结点删除,前后结点连接。
	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;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值