单链表的基本操作

学过数据结构都知道,链表是最基础的一个数据结构。作为一个合格的程序员都应该清楚链表的操作。

一、什么是链表呢?

我的理解是,链表是有每个结点的组成的一种线性表,每个结点的物理存储单元是不连续的,但是这些结点能通过指针找到下一个结点的位置。

结点是什么呢?我来画一个图解释一下。

每个结点都是由数据域和指针域组成的,数据域中存放个的是这个结点的一些信息,指针域存放的是下一个结点的地址,这个结点也就是通过指针域中的地址找到下一个结点。

上图就表示了结点是怎么通过指针域找到下一个结点的。

 

二、链表的基本操作

单链表有两种形式,一种是带头结点的,另一种是不带头结点的,本篇博客所用的就是带头结点的链表,头结点数据域中不保存任何数据,但是有时候可以存储这个链表的长度。

链表的创建

1、首先定义一个结构体,结构体中包含一个数据域和一个指针域。

struct Node 
{
	int data;
	struct Node* next;
};

2、创建一个链表,并且给这个链表初始化

struct Node* createList()
{
	struct Node* list = (struct Node*)malloc(sizeof(struct Node));
	list->next = NULL;
	return list;
}

3、创建一个结点,并且给这个结点初始化

struct Node* createNode(int data)
{
	struct Node * node = (struct Node*)malloc(sizeof(struct Node));
	node->next = NULL;
	node->data = data;
	return node;
}

往链表中插入结点

1、头插

void headinsert(struct Node *list, int data)
{
	struct Node* node = createNode(data); //需要插入的结点
	node->next = list->next;
	list->next = node;

}

这里我在用一个图来解释一下

头插法就是每个新插入的结点都是头结点的下一个结点。

①首先让新结点的指针域的指针指向原来头结点的下一个结点。

②让头结点的指针域的指针指向新结点

这样就在头结点和原来的第一个结点之间插入了一个新结点。

2、尾插

尾插有点麻烦,首先得先找到这个链表的尾部结点。

void backinsert(struct Node* list, int data)
{
	struct Node* newnode = createNode(data);
	struct Node* temp = list;//定义一个移动指针,用来找到链表尾部
	while (temp->next != NULL)//找到尾部的结点
	{
		temp = temp->next;
	}
	newnode->next = NULL; //将新结点的next置为null
	temp->next = newnode;//将新结点设置为尾部结点。
}

这个while循环是为了找到这个链表的尾部结点,找到之后直接把尾部结点的指针域中的指针指向新结点就行了。

3、在指定位置插入结点

下面的代码是在指定位置之前插入新的结点。

void insertlocation(struct Node* list, int locat, int newdata)
{
	struct Node* newnode = createNode(newdata);
	struct Node* temp = list;
	struct Node* q = list->next;
	while (q->data != locat)
	{
		q = q->next;
		temp = temp->next;
		if (q == NULL)
		{
			printf("没有找到指定位置\n");
			return;
		}
	}
	newnode->next = temp->next;
	temp->next = newnode;
}

因为需要插入到指定位置的前面,所以需要知道这个指定的结点和它前面一个结点,temp就是用来表示指定结点的前一个结点,q就是用来找到指定结点的位置。如果p的数据域和指定的数据不同,那么两个指针都同时向后移。找到之后就插入,这里的插入和头插的方法相同。

删除指定的结点

删除的操作和在指定的位置插入类似,首先得遍历链表,找到需要删除的位置,同时得找到这个结点的前一个结点。

void deletlocations(struct Node* list, int deldata)
{
	struct Node* temp = list;
	struct Node* p = list->next;

	while (p->data != deldata)
	{
		p = p->next;
		temp = temp->next;
		if (p == NULL)
		{
			printf("没有找到指定位置\n");
			return;
		}
	}
	temp->next = p->next;
	free(p);
	p = NULL;  //将这个指针置为NULL防止出现野指针
}

temp是用来保存指定位置的前一个结点,p是用来找指定的结点。

找到这个结点之后,将它前一个结点的指针域指针指向指定结点的下一个结点,然后free释放这个结点p的内存空间。为了防止出现野指针,将这个指针置为NULL。

单链表的遍历

遍历其实操作就很简单多了,就不多说了。直接看代码

void print(struct Node* list)
{
	struct Node * p = list->next;
	while (p)
	{
		printf("%d\t", p->data);
		p = p->next;
	}
	printf("\n");

}

单链表的逆序

单链表的逆序其实是一个比较复杂的操作,以为是单链表,所以只有一个指针域,它的指针是指向下一个结点的地址。

逆序就是将尾结点变为第一个结点,然后依次改变原有的指向顺序。用一个图来说明

蓝色的是原先的链表,红色的是逆序之后的。逆序之后是将头结点指向最后一个结点,然后从最后开始,依次指向它原来的前一个结点,原来的第一个结点指向NULL。

通过上面这个图我们可以看出,需要在逆序之前保留第一个结点的位置。同样的因为需要指向它前一个结点,所以需要保留它前面节点的位置,还有它后面的结点的位置,以及当前结点的位置。

注意:如果这个链表是一个空链表,后者这个链表只有一个结点(头结点不算),都是不需要逆序的。

//单链表的逆序
//因为是单链表,所以需要保留当前结点、前一个结点的、还有下一个节点的位置
//头结点不动,最后将第一个(头结点的后一个)结点指向NULL,因此还要保留第二个结点的位置

void nixu(struct Node* list)
{
	struct Node *last = list->next;//保留第1个结点的位置
	struct Node *pre = list;       //保留上一个结点的位置
	struct Node *cur = list->next; //保留当前结点的位置
	struct Node *next = NULL;      //保留下一个结点的位置

	//如果这个链表是一个空链表就不要逆序
	if (list->next == NULL)
	{
		printf("这是一个空链表\n");
		return;
	}

	//如果这个链表只有一个结点,也不需要逆序
	if (list->next->next == NULL)
	{
		printf("这个链表只有一个元素,不需要逆序\n");
		return;
	}

	while (cur)
	{
		next = cur->next; //保留下原来下一个结点的位置
		cur->next = pre;  //把当前结点的下一个位置指向前一个结点
		pre = cur;        //把当前结点的next指向前一个结点后,将pre指向当前结点,用于下一个结点的逆序。
		cur = next;       //当前结点后移,移动到原来结点的下一个结点,就是next所指向的结点。
	}

	//除了最后一个结点,其他的都逆序完毕,这个时候把头结点的下一个结点指向最后一个结点
	list->next = pre;
	last->next = NULL;
}

以上就是单链表的一些基础操作。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值