数据结构-----链表

目录

1.顺序表经典算法

(1)移除元素

(2)合并数组

2.链表的创建

(1)准备工作

(2)建结构体

(3)链表打印

(4)尾插数据

(5)头插数据

(6)尾删数据

(7)头部删除

(8)查找数据

(9)在指定位置之前插入数据

(10)在指定位置之后插入数据

(11)删除指定位置的节点

(12)删除指定位置之后的节点


1.顺序表经典算法

(1)移除元素

我们这里使用双指针的移动解决这个问题:

(1)指针dest和src, 例如3 2 2 3,我们的val是3,当开始的时候,src和dest都指向3,正好是我们想要删除的元素,我们的dest不变,src++,src此时指向的2不是我们想要删除的元素,就把2挪到前一位,覆盖掉3,这个顺序表里面的头删很相似,按照这个,当src执行最后一个元素的时候等于val,再次执行加加就结束了,此时我们的dest正好指在第二个2的位置,恰好是我们想要的返回值,直接返回就可以了。

(2)合并数组

我们选择的是使用l1,l2,l3,分别指向第一个数组的末尾,第二个数组的末尾,第一个数组的最后,我们的第一个数组的长度是两个数组的长度之和,l3指向的是没有数字的空位置,让两个数组里面的元素进行比较,大的就从第一个数组的后面开始布置,知道一个数组遍历完成,当第一个数组遍历完成但是第二个数组没有遍历完成的时候,就说明第一个数组里面的元素全部大于第二个数组,我们就需要使用循环直接把第二个数组里面的元素挪到第一个数组里面,如果第二个数组遍历完成,但是第一个数组没有遍历完成,这个时候数组的顺序就是按照大小进行排列的,我们不需要进行任何处理(读者可自行尝试);

2.链表的创建

中间头部插入数据效率低下,增容时浪费空间,效率低下,链表可以解决这个问题:

(1)准备工作

创建三个文件,一个头文件----slist.h文件,两个源文件------slist.c------test.c文件,头文件还是主要负责相关的函数的声明以及结构体的创建,一个源文件slist.c是链表的相关函数的实现,另外的一个test.c就是进行函数的功能的测试:

(2)建结构体

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int sldatatype;
typedef struct slistnode
{
	sldatatype data;
	struct slistnode* next;
}slnode;

创建类型是struct slistnode的结构体,并且重新命名为slnode类型,这样会方便我们后续变量的创建,结构体里面有data就是存放的数据,另外的一个就是我们的next指针,因为我们的链表不是连续的,所以既需要变量存放数据,也需要定义一个next指针找到下一个数据;

(3)链表打印

void slnodeprint(slnode* phead)
{
	slnode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

这个是链表的源文件里面的一个链表的打印函数的实现,因为链表不是连续的,我们无法利用之前的那种加加减减操作依次打印,我们需要移动指针,逐个进行打印;结合下面的测试代码,我们定义了一个plist指针指向我们的第一个节点,plist作为实际参数,phead作为形式参数接受,这个时候phead也是指向了第一个节点,重新定义了一个指针pcur指向第一个节点,通过pcur=pcur->next使得pcur不断地指向下一个结点,循环进行打印,失恋了链表的遍历。

(4)尾插数据

先找到尾部的节点,让尾部的节点和我们的新的节点连接起来:我们首先要新建一个节点,因为进行头插函数设计的时候也会涉及到这个节点的新建,因此我们把定义新的节点,封装成为一个函数,我们进行头插尾插的时候直接对函数进行调用就可以了;

尾部插入数据,首先要判断我们这个一直的链表是否有数据,没有就直接指向新建的节点;有的话进行循环找到尾部的节点,实现链接;因此尾插的时候需要划分为两种情况,否则空的话解引用就会报错,下面的是实参的三种形式;

在我们的测试函数里面,我们首先定义了一个空的链表,plist是指向第一个节点的指针,我们置为空指针,说明初始的情况下这个链表里面是没有元素的,我们如果在形参里使用一级指针进行接收,就会发现,我们给phead传进去的最后一个数字是4,我们的phead的数据始终在变化,但是并没有同步到plist里面去,这个就可以说明我们的传递的是值,虽然plist就是一个指针,但是我们应该传递这个一级指针plist的地址,这样才可以让形参的变化同步到实参;

我们传递了一级指针plist的地址,在形参部分就应该使用二级指针进行接收,跟就实参的三种形式, 我们对于尾插代码里面相应的部分进行修改;

当我们使用的二级指针进行接收的时候,我们就可以发现,相残和实参的变化就会同步了:phead修改的时候,plist改变量也会被同步进行。

(5)头插数据

和尾部插入有些许类似,我们首先要新建一个节点,我们要想实现关联,我们首先就要让这个新建的节点只想我们的头节点,然后再让我们这个新建的节点成为头节点;这里只需要修改第一个指针的指向,就可以完成链表的头插数据,不需要让剩余的节点向后移动;

(6)尾删数据

尾删数据,我们首先就要考虑先找到最后的一个数据节点,让后把这个空间给释放掉,然后设置为空指针,前提是我们的节点不能是空的,否则我们就没有可以删除的数据了;之前我们的插入*pphead(指向第一个节点的指针)可以是空的,我们只需要对二级指针**pphead进行断言就可以了,因为就算没有数据,我们自己也是可以插入数据的,但是这个地方我们必须同时对于一级指针和二级指针进行断言,因为如果没有数据,我们可以进行插入,但是无法进行删除;

这个时候我们比如有1 2 3 4这四个元素,4被删除之后,我们的倒数第二个元素原本是指向的4,现在这个4被我们给释放掉了,这个节点处的指针就变成了野指针,因此我们也要把这个倒数第二个节点的next指针指向空才可以;

但是想象一下一种极端的情况,如果本来就是空的话,我们释放掉之后,这块空间就消失了,就不会存在pre前一个指针变成野指针的情况了;

(7)头部删除

首先我们要考虑一个问题:如果是先释放掉,然后把第二个节点变成头节点,这个时候我们必须要意识到,当我们把空间释放掉之后,我们第一个结点的next指针里面的是第二个节点的地址,我们把它释放掉之后,我们是无法找到第二个元素的,我们因此先要把第二个元素的地址给存起来,让后进行释放空间,最后再把我们的第二个结点的地址赋值给我们的*pphead(指向第一个节点的指针);

(8)查找数据

我们这里在一般的情况下都会定义一个pcur=phead,两者的效果是一样的,相当于pcur就是phead的一个复制品,我们为社么要这么进行定义呢?因为如果我们没有定义pcur这个指针,而是直接使用phead指针,随着指针的移动,当我们遍历完成之后,如果我们想要再次进行处理的时候,就不能找到头部节点的位置了,但是如果我们定义了一个pcur指针,无论我们的pcur怎样进行移动,我们的phead始终是位置不变的,我们始终是能够找到这个链表的头位置的,对于这个思想,我们应该逐步地理解并且学会运用;

我们可以给一个返回值方便我们进行判断:if语句是判断是否是我们想要的数据,如果不是的话就会继续向下走,如此进行下去,除了while循环之后还是没有找到的话,就说明我们想要查找的数据在链表里面不存在,我们返回的是空指针;找到的话我们直接返回pcur就可以了,因为我们的pcur是一个slnode*类型的指针,那么用来进行接收返回值的find就应该是相同的类型;

(9)在指定位置之前插入数据

这个时候,我们就需要三个参数,我们的指针(二级指针),我们的插入的位置pos,以及我们想要插入的数据(sldatatype x),先要进行断言,我们的pos是一定存在的,所以*pphead不可以是空指针;同样,我们使用pre同样表示的指向第一个节点的指针,如果我们的pos就是第一个结点的话,我们的循环条件是pre->next!=pos,指针他就会不停的向后面进行移动,我们因此要进行判断pos(我们想要进行查找的位置)是否和*pphead相等,如果相等的话,就说明首个节点的位置就是我们的pos位置,我们要在这个位置之前插入数据,就是进行的头部插入,我们直接进行调用前面定义的头插函数就可以了;否则的话就说明pos位置不是我们的第一个加点位置,我们逐个进行遍历,直到找到我们的pos位置即可;找到这个pos位置之后,我们直接让pre(指定位置的前面的一个节点)指向我们的新的节点,让我们的新的节点指向pre->next这个位置的节点,这样就可以在我们的指定位置前面把新的节点插入进去了;

(10)在指定位置之后插入数据

下面就是一个简单的只有4个数据的链表,我们只是为了说明问题:我们在指定的位置的后面插入数据,可有2种方案,一种就是先1后2,还有就是先2后1,先2后1才是正确的,先1后2是错误的(如果是先1后2的话,我们的pos->next指针就已经指向了新的节点,然后是newnode->next指向我们的pose->next,这个时候的pos->next就已经变化了,不是我们想要的了,这个不就是自己指向自己嘛),所以只能是先2后1;

我们这个地方只需要两个参数,不需要头节点,我们前面的指定位置前面插入数据,我们找到了指定的位置,但是我们没有办法知道前面的一个,因此我们定义了一个副本pre进行从头进行遍历,但是这个地方我们在指定位置的后面插入数据,我们的pos找到了以后,我们的pos->next就直接找到了,不需要用到头节点,因此我们只用涉及两个参数就可以了;

(11)删除指定位置的节点

只有一个节点,直接进行头删函数的调用就可以了;否则让指定位置前面的节点链接指定位置后面的节点;最后释放指定位置节点,置为空指针;

(12)删除指定位置之后的节点

pos和pos后面的一个节点必须要有数据,进行断言,使用del作为副本存储指定的位置,链接pos->next和del->next;释放之后置空;

(13)销毁链表

一个一个地进行销毁节点,最后把头节点销毁,我们在销毁单个节点之前要存下一个节点的位置

3.完整代码

(1)slist.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int sldatatype;
typedef struct slistnode
{
	sldatatype data;
	struct slistnode* next;
}slnode;

void slnodeprint(slnode* phead);

void slpushback(slnode** pphead, sldatatype x);

void slpushfront(slnode** pphead, sldatatype x);

void slpopback(slnode** pphead);

void slpopfront(slnode** pphead);

slnode* slfind(slnode* phead, sldatatype x);

slnode* slinsert(slnode** pphead, slnode* pos, sldatatype x);

slnode* slinsertafter(slnode* pos, sldatatype x);

void slerase(slnode** pphead, slnode* pos);

void sleraseafter(slnode* pos);

void destory(slnode** pphead);

(2)slist.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"slist.h"
void slnodeprint(slnode* phead)
{
	slnode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
slnode* buynode(sldatatype x)
{
	slnode* newnode = (slnode*)malloc(sizeof(slnode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
尾部插入数据
//void slpushback(slnode** pphead, sldatatype x)
//{
//	assert(pphead);
//	slnode* newnode = buynode(x);
//	if (*pphead == NULL)
//	{
//		*pphead = newnode;
//	}
//	else
//	{
//		//找到尾部的节点,使之只想我们插入的元素
//		slnode* ptail = *pphead;
//		while (ptail->next)
//		{
//			ptail = ptail->next;
//		}
//		//跳出循环之后ptail指向的就是尾部节点
//		ptail->next = newnode;
//	}
//}


//链表的尾插数据
void slpushback(slnode** pphead, sldatatype x)
{
	//assert(pphead);//对二级指针进行断言
	slnode* newnode = buynode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		slnode* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}
//头插数据
void slpushfront(slnode** pphead, sldatatype x)
{
	assert(pphead);
	slnode* newnode = buynode(x);
	//新的节点的指针原本指向空,现在指向头节点;
	newnode->next = *pphead;
	*pphead = newnode;
}
//尾部删除数据
void slpopback(slnode** pphead)
{
	assert(pphead && *pphead);
	slnode* pre = *pphead;
	slnode* ptail = *pphead;
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		while (ptail->next)
		{
			pre = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		pre->next = NULL;
	}
}
//头部删除数据
void slpopfront(slnode** pphead)
{
	assert(*pphead && pphead);
	slnode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}
//查找数据
slnode* slfind(slnode* phead, sldatatype x)
{
	slnode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;//没有找到
}
//指定的位置之前插入数据
slnode* slinsert(slnode** pphead, slnode* pos, sldatatype x)
{
	assert(pphead && *pphead);
	assert(pos);
	slnode* newnode = buynode(x);
	if (pos == *pphead)//pos就是链表的第一个节点
	{
		slpushfront(pphead, x);
	}
	else
	{
		slnode* pre = *pphead;
		while (pre->next != pos)
		{
			pre = pre->next;
		}
		newnode->next = pos;
		pre->next = newnode;
	}
}
//在指定的位置之后插入数据
slnode* slinsertafter(slnode* pos, sldatatype x)
{
	assert(pos);
	slnode* newnode = buynode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//删除某个节点
void slerase(slnode** pphead, slnode* pos)
{
	assert(pphead&&*pphead);
	assert(pos);
	slnode* pre = *pphead;
	if (pos == *pphead)
	{
		slpopfront(pphead);
	}
	else
	{
		slnode* pre = *pphead;
		while (pre->next != pos)
		{
			pre = pre->next;
		}
		pre->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
//删除指定位置后面节点
void sleraseafter(slnode* pos)
{
	assert(pos && pos->next);
	slnode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}
//销毁
void destory(slnode** pphead)
{
	assert(pphead && *pphead);
	slnode* pcur = *pphead;
	while (pcur)
	{
		slnode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

(3)test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"slist.h"
void slnodetest01()
{
	slnode* node1 = (slnode*)malloc(sizeof(slnode));
	node1->data = 1;

	slnode* node2 = (slnode*)malloc(sizeof(slnode));
	node2->data = 2;

	slnode* node3 = (slnode*)malloc(sizeof(slnode));
	node3->data = 3;

	slnode* node4 = (slnode*)malloc(sizeof(slnode));
	node4->data = 4;

	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	slnode* plist = node1;
	slnodeprint(plist);
}
void test2()
{
	slnode* plist = NULL;
	slpushback(&plist, 1);
	slnodeprint(plist);
	slpushback(&plist, 2);
	slpushback(&plist, 9);
	slpushback(&plist, 4);
	slnodeprint(plist);

	slpushfront(&plist, 6);
	slnodeprint(plist);
	//6 1 2 9 4;

	/*slpopback(&plist);
	slnodeprint(plist);

	slpopfront(&plist);
	slnodeprint(plist);*/

	slnode* find = slfind(plist, 9);
	/*if (find == NULL)
	{
		printf("没有找到");
	}
	else
	{
		printf("找到了");
	}*/

	//slerase(&plist, find);
	//slnodeprint(plist);
	//注意这个里面的55,56不能和58,60同时执行,必须要注释一个
	sleraseafter(find);
	//slinsert(&plist, find, 10);
	slnodeprint(plist);

	destory(&plist);
	slnodeprint(plist);
	  

	
}
int main()
{
	//slnodetest01();
	test2();
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值