线性表之链表的创建以及基本操作

12 篇文章 0 订阅
8 篇文章 0 订阅

        在线性表中,使用最多的莫过于链表了,链表有多个种类,而且使用方便,接下来我们来慢慢了解链表的创建以及基本操作;

链表的类型

 1:单向链表

 2:双向链表

3:带头链表 

4:不带头链表

 5:循环链表

 6:非循环链表

以上是一些链表的基本图示,而我们最常用的是单向无头非循环链表和双向带头循环链表;

单向无头非循环链表

双向带头循环链表

当然,从结构的难易程度上来说,我们先来体会下单向无头非循环链表的创建以及基本操作;

单向无头非循环链表的创建

typedef int DataType;

typedef struct STD {
	DataType data;
	struct STD* next;
}STD;

         所谓链表,顾名思义,就是通过某些东西链接起来的表,在c语言中,链接两个变量最好的东西就是指针,而链表就是通过一个自引用的指针变量next来链接每一个节点;

        在明白了链表的基本变量后我们来完成增删查改的基本操作;

        创建节点

STD* BuyNewNode(DataType x)
{
	STD* ret = (STD*)malloc(sizeof(STD));
	if (ret == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	ret->data = x;
	ret->next = NULL;
	return ret;
}

        头插

void STDPushFront(STD** head, DataType x)
{
	assert(head);
	STD* tmp = BuyNewNode(x);
	tmp->next = *head;
	*head = tmp;
}

        尾插

void STDPushBack(STD** head, DataType x)
{
	assert(head);
	STD* tmp = BuyNewNode(x);
	if (*head == NULL)//第一次插入数据
	{
		(*(head)) = tmp;
	}
	else
	{
		STD* tail = *head;
		while (tail->next != NULL)//找到尾部节点
		{
			tail = tail->next;
		}
		tail->next = tmp;
	}

}

主函数中的测试函数 

void test1()
{
	STD* head = NULL;
	STDPushBack(&head, 1);
	STDPushBack(&head, 2);
	STDPushBack(&head, 3);
	STDPushBack(&head, 4);
	STDprint(head);

	STDPushFront(&head, 5);
	STDPushFront(&head, 6);
	STDPushFront(&head, 7);
	STDPushFront(&head, 8);
	STDprint(head);
}

        看到这里大家应该会有一些疑惑,比如为什么头节点是指针类型,为什么传参需要传指针的地址之类的,这里一一为大家解答;

为什么链表的头节点是指针类型

链表的每一个节点都是由malloc之类的动态内存开辟函数开辟出来的,而这种函数开辟出来的地址都必须用指针类型接受,因此我们的头节点也必须是一样的指针类型;

为什么传参需要传指针地址

我们首先看头插函数,该函数每次都会开辟一个新的节点,然后将节点的next指向head,再改变head的内容,这里我用几张图来方便理解;

 首先如图,main函数中创建了一个*head变量,指向NULL,而在头插函数内部,创建了一个*tmp并且为它开辟了一个空间,而头插就是需要将*tmp插在*head的之前,而我们要在*head之前插入一个变量很简单,就只需要将*tmp的*next指向*head就可以了;

 

但是这样仅仅只是将*tmp插入在*head之前, 但我们的头指针还没有改变,若是不改变*head那么就相当于没有头插,而我们若是想改变*head,根据函数传址传值的规则,我们必须传*head的地址,才能改变*head本身,这就是为什么我们需要传二级指针才行;

         

 

        再看尾插,我们发现,尾插函数中只有当*head为NULL时,也就是第一次尾插才需要解引用变量,而不是第一次尾插则是需要找到尾节点然后改变其*next指针所指向的内容就行了;

现在我们就明白了,当我们要改变头节点本身时就需要传二级指针或者进行解引用操作,而当我们只要改变节点的next指针域的时候,就不要传二级指针或者解引用操作;

        那么有没有一种办法让我们什么时候都不要传二级指针呢?当然有,那就是创建带头的链表;当一个节点有了首元节点时,我们不管是尾插还是头插,不管是第一次插入还是多次插入,都不用传二级指针,因为每次都是改变的*head内部的*next所指向的内容,而非*head本身;

        当我们明白为什么传二级指针之后,接下来的操作都十分简单了;

头删        

void STDDealFront(STD** head)
{
	assert(head);
	if (*head == NULL)
	{
		return;
	}
	STD* Del = *head;
	*head = (*head)->next;
	free(Del);
}

当我们进行头删的时候,我们需要让*head指向下一个节点,再free原本的节点,这就改变了*head本身,于是我们要传二级指针;

 

就像这样,del指向原本head的位置,head往后走,最后free(del); 

 

void STDDealBack(STD** head)
{
	assert(head);
	if (*head == NULL)
	{
		return;
	}
	STD* tail = *head;
	if ((*head)->next == NULL)
	{
		free(*head);
		*head = NULL;
	}
	else
	{
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

而尾删也是差不多的,只不过需要分两种情况,一种是只有一个节点了,一种是还有一个以上的节点,这里也画图来简单理解下;

首先是有一个以上节点的链表进行尾删

 

我们需要找到该链表的尾节点才能进行尾删操作,于是我们用*tail指向*head,然后循环知道找到最后一个节点 ,但我们还是需要将链表的最后一个节点的next指向NULL,否则链表会出错,因此我们这样寻找到倒数第二个节点;

找到倒数第二个节点后再free(tail->next);

这样我们就成功的尾删了,并且我们能够成功的将tail的next置为NULL;

 

然后是只有一个节点的时候,我们只用直接free(*head),然后将*head置空就ok了;

之后就是查找,修改等函数了;

 查找某个节点,有则返回地址,无则返回NULL

STD* STDFind(STD** head, DataType x)
{
	assert(head);
	STD* pos = *head;
	while (pos)
	{
		if (pos->data == x)
		{
			return pos;
		}
		pos = pos->next;
	}
	return NULL;

}

某位后插入节点

void STDInsertAfter(STD*pos, DataType x)
{
	assert(pos);
	STD* tmp = BuyNewNode(x);
	tmp->next = pos->next;
	pos->next = tmp;
}

某位前插入节点


void STDInsertFront(STD** head, STD* pos, DataType x)
{
	assert(head);
	assert(pos);
	STD* tmp = BuyNewNode(x);
	STD* prev = *head;
	if (pos == *head)//就在头节点
	{
		STDPushFront(head, x);
	}
	else
	{
		while (prev->next != pos)
		{
			prev = prev->next;
			assert(prev);//pos为空的可能
		}
		prev->next = tmp;
		tmp->next = pos;
	}
}

        大家发现某位前插入节点和某位后插入节点不一样,这是因为单向无头非循环链表的性质所决定的,因为若是要在某位前插入节点,不仅需要改变该位置的节点,还需要改变该位置之前的节点;

这里也简单的画几个图方便各位理解;

 

这里,我们成功找到了pos以及pos前一个节点pre,然后我们需要将tmp插入在pos之前,并且改变pre的next指针,就变成了这样;

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值