学习一下单链表

  听说,今天是公务员考试,ss祝莘莘学子可以凯旋。关关难过,关关过,再坚持坚持,这句话送给我们大家,这是我高中语文老师给我们说的一句话,陪ss过了一关又一关,好了废话不多说。

1.首先了解一下什么是链表:

链表是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。其实最形象的就是火车,火车上一节的火车的挂钩挂着下一节的头部,而在我们单链表中呢,就是上一节存储着下一节的地址,我们在程序中就可以通过地址找到它,而通过存储地址的方式,我们就像或者一样一节节的链住了,单链表形如火车,准确的来说是没有火车头的火车,ss下期会给大家双向链表,它有一个俗称哨兵位的头,它更像是火车头。
2.单链表我们要用它实现顺序表的内容,为什么使用呢,顺序表每次扩容都是成倍的扩容,更容易浪费空间,而我们单链表呢,每次使用,每次再开辟需要的空间,在进行链接,空间上不容易造成浪费。
单链表的优点:<1>任何位置下可以插入删除数 <2>按需申请空间
              缺点:<1>cpu命中率低 <2>不支持下标访问数据,访问相对较难
好了,让我们进行代码实操吧。
3.第一步,创建一个单链表
typedef int innt;//我们把int 类型换个名字,方便以后改变类型使用单链表
typedef struct seqlist
{
	innt x;
	struct seqlist* next;//建立一个结构体的指针,用来存放下一个一模一样的“车厢”

}list;

4.单链表的功能实现

<1>尾插

为什么单链表不像顺序表那样需要初始化了,因为单链表再需要的时候会要创建空间,我们在实现功能的时候,就申请空间,所以不需要明明确确的初始化。尾插,我们先把这个数存进一个我们申请好的空间,然后用做好的链表的尾巴链接住此时新数据的头,也就是旧尾巴存上新空间的地址

list* chuang(innt x)
{
	list* newnode = (list*)malloc(sizeof(list));
	newnode->x = x;
	newnode->next = NULL;
	return newnode;
}
void pushback(list** head,innt x)
{
	list* newnode = chuang(x);
	if (*head == NULL)
	{
		*head = newnode;
		return;
	}
	list* ps = *head;
	while (ps->next)
	{
		ps = ps->next;
	}
	ps->next = newnode;
}

1*.值得一提的时,为什么使用的时list** head,我们要知道,使用函数传参时,形参就是实参的一个临时拷贝,对形参改变完全不会影响实参,我们尾插的时候,要改变尾巴的next的指向从NULL指向的改编成指向一个新的空间地址,我们如果只传一级指针,但是我们要改变这个指针的指向,虽然传了一个指针,但是我们无法改变它自身,所以我们要传二级指针,保证可以改变它的指向和它自身。

2*.在pushback函数中我们可以看到,ss先进行了一下判断,*head是不是为空指针,为什么呢,如果这是第一个数据,我们的头还没有建立,那它就是头节点。

<2>打印

我们实现了尾插数据,当然得看看有没有写成功,可以进行调试,但是为了更加直观和方便,也是进一步学习遍历链表,我们写一下打印的函数,打印不需要改变里面的任何指针的指向,所以我们只需要一级指针就可以,如何遍历单链表呢?

我们的尾结点都和一个头结点相连接(头尾除外),所以我们只需要将头节点变成下一个的头节点,而下一个的头节点不就是我们刚才的尾结点存储的那个地址嘛,所以定义一个指针,指向头部,用它去遍历,不要改变定义好的头结点

void print(list* head)
{
	assert(head);

	list* ps = head;
	while (ps)
	{
		printf("%d->", ps->x);
		ps = ps->next;
	}
}

<3>头插,我们要知道,单链表单的意思是,单向的,是一个方向循环的,就是只能从头的方向向尾巴循环遍历,那我们怎么实现头部增加,并且把新数据连在旧数据的前面呢?我们可以创建一个头指针,相对来说他就是不动的,他就做头,我们每次头插新数据时,把新数组的地址当作头,然后再进行链接,思路明了,代码实操。

void pushfront(list** head,innt x)
{
	list* ps=chuang(x);
	assert(ps);
	if ((*head) == NULL)
	{
		*head = ps;
		return;
	}
	ps->next = *head;
	*head = ps;
}

<4>尾巴删除

因为单向循环,所以我们遍历一下,尾巴的标志是什么,就是它的next指针指向的是NULL,所以我们要以它作为条件,进行循环遍历,但是我们还需要尾巴的上一个结点,因为它成了新的尾结点,所以我们有两种思路,第一种就是定义两个指针,两者一前一后,紧挨着进行遍历,最后释放尾结点即可,第二种思路,就是用指针的next->next进行判断为空,值得注意的就是,如果只有一个数据,此时就是尾结点,-next=NULL,不能将NULL再指向next,会报错,所以我们先写一个只有一个数据的时候。思路明了,代码实操。

void popback(sltnode** phead)//第一种
{
	assert(phead);
	assert(*phead);// 链表不为空
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
		return;
	}
	sltnode* petail = *phead;
	sltnode* prev = NULL;
	while (petail->next)
	{
		prev = petail;
		petail = petail->next;
	}
	prev->next = NULL;
	free(petail);
	petail = NULL;
}
//第二种
void popback(list** head)
{
	assert(*head);
	assert(head);
	list* ps = *head;
	if (ps->next == NULL)
	{

		free(ps->next);
		free(ps);
		ps = NULL;
		return;
	}
	while (ps->next->next != NULL)
	{
		ps = ps->next;
	}
	free(ps->next);
	ps->next = NULL;
}

<5>头删

这个有了以往的经验,我们想象,只需要更改头结点,让新的头结点是刚才的头结点的next指向的那块。

void popfront(list** head)
{
	assert(*head);
	assert(head);
	list* ps = *head;
	*head = ps->next;
	free(ps);
	ps = NULL;
}

<6>在指定的数字前面插入新的数据

首先我们需要找到这个指定的数字,然后在它前面插数据,则需要改变这个数字上个结点的尾结点指向,并使新数据的尾结点指向这个指定的数字。

void insert(list** head, int pos, innt x)
{
	if ((*head)->x == pos)
	{
		pushfront(head,x);
		return;
	}
	list* ps = chuang(x);
	list* ret = find(head, pos);
	if (ret == NULL)
	{
		printf("查无此数");
		return;
	}
	ps->next = ret->next;
	ret->next = ps;
	

}

观察代码,其实和刚才头插有些许相同,因为我需要指定数的上一个结点,所以先判断一下他是不是头结点,如果是,则直接头插就行,不是的话,我们用ps->next->x,来找到那个指定数的上一个结点。

<7>在指定的数字后面插入新的数据

这个更好做了,直接找数,把尾结点更改即可。

void insertafter(list** head, int pos, innt x)
{
	assert(head);
	assert(*head);
	list* ret = find(head, pos);
	list* ps = chuang(x);
	ret->next = ps;
	ps->next = NULL;

}

<8>指定数字的删除

void eraser(list** head, int pos)
{
	assert(head);
	assert(*head);
	if ((*head)->x == pos)
	{
		popfront(head);
		return;
	}
	list* ret = find(head, pos);
	list* ps = ret->next;
	ret->next = ps->next;
	
	free(ps);
	ps = NULL;

}

还是和刚才一样,先判断是不是头结点,不是的话,我们找一下,指定的数的上一个结点,然后更改它的尾结点,再进行释放指定的数的空间即可;

<9>销毁

void destory(list** head)
{
	assert(*head);
	assert(head);
	list* ps = *head;
	while (ps)
	{
		list*ret = ps->next;
		free(ps);
		ps = ret;
	}
	*head = NULL;
	
}

销毁的话,我们就需要两个指针,一个负责被销毁,一个负责遍历链表,所以第二个要在第一个的前面,进行遍历,最后把头结点置为空。

最后我们还是代码分享

//list.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int innt;
typedef struct seqlist
{
	innt x;
	struct seqlist* next;

}list;
void pushback(list** ps,innt x);
void pushfront(list** ps,innt x);
void print(list* ps);
void popback(list** ps);
void popfront(list** ps);
void insert(list** ps, int pos, innt x);
void insertafter(list** ps, int pos, innt x);
void eraser(list** head, int pos);
void destory(list** head);
//list.c
#include"seqlist.h"
list* chuang(innt x)
{
	list* newnode = (list*)malloc(sizeof(list));
	newnode->x = x;
	newnode->next = NULL;
	return newnode;
}
void pushback(list** head,innt x)
{
	list* newnode = chuang(x);
	if (*head == NULL)
	{
		*head = newnode;
		return;
	}
	list* ps = *head;
	while (ps->next)
	{
		ps = ps->next;
	}
	ps->next = newnode;
}
void print(list* head)
{
	assert(head);

	list* ps = head;
	while (ps)
	{
		printf("%d->", ps->x);
		ps = ps->next;
	}
	printf("\n");
}

void pushfront(list** head,innt x)
{
	list* ps=chuang(x);
	assert(ps);
	if ((*head) == NULL)
	{
		*head = ps;
		return;
	}
	ps->next = *head;
	*head = ps;
}
void popback(list** head)
{
	assert(*head);
	assert(head);
	list* ps = *head;
	if (ps->next == NULL)
	{

		free(ps->next);
		free(ps);
		ps = NULL;
		return;
	}
	while (ps->next->next != NULL)
	{
		ps = ps->next;
	}
	free(ps->next);
	ps->next = NULL;
}
void popfront(list** head)
{
	assert(*head);
	assert(head);
	list* ps = *head;
	*head = ps->next;
	free(ps);
	ps = NULL;
}
list* find(list** head, int pos)
{
	list* ps = *head;
	
	while (ps)
	{
		if (ps->next->x == pos)
		{
			return ps;
		}
		ps = ps->next;
	}
	return NULL;
}
void insert(list** head, int pos, innt x)
{
	if ((*head)->x == pos)
	{
		pushfront(head,x);
		return;
	}
	list* ps = chuang(x);
	list* ret = find(head, pos);
	if (ret == NULL)
	{
		printf("查无此数");
		return;
	}
	ps->next = ret->next;
	ret->next = ps;
	

}
void insertafter(list** head, int pos, innt x)
{
	assert(head);
	assert(*head);
	list* ret = find(head, pos);
	list* ps = chuang(x);
	ret->next = ps;
	ps->next = NULL;

}
void eraser(list** head, int pos)
{
	assert(head);
	assert(*head);
	if ((*head)->x == pos)
	{
		popfront(head);
		return;
	}
	list* ret = find(head, pos);
	list* ps = ret->next;
	ret->next = ps->next;
	
	free(ps);
	ps = NULL;

}
void destory(list** head)
{
	assert(*head);
	assert(head);
	list* ps = *head;
	while (ps)
	{
		list*ret = ps->next;
		free(ps);
		ps = ret;
	}
	*head = NULL;
	
}
//test.c
#include"seqlist.h"
void test1()
{
	list* ps=NULL;
	pushback(&ps,1);
	pushback(&ps,2);
	pushback(&ps,3);
	pushfront(&ps, 100);
	eraser(&ps, 1);
	print(ps);
	
}
int main()
{
	test1();
	return 0;
}

欢迎大佬指出错误,谢谢观看

  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值