链表的详解

目录

一、不带头单链表

       初始准备

       链表节点创建与销毁

       尾插尾删

       头插头删

       随机位置插入与删除

       缺陷

       全部代码

二、链表带环问题

三、带头双链表

       初始准备

       链表节点创建与销毁

       尾插尾删

       头插头删

       随机位置插入与删除

       全部代码

四、对比顺序表与链表


一、不带头单链表

初始准备

不带头单链表,首先创建了一个结构体,而该结构体有2个成员,一个用来存储数据(data),另一个用来存储下一个数据的地址(next*),从而使得两个结构体变量连接起来,如下图

为了书写简洁,用typedef将其结构体类型名修改

 

又为了能方便存储不同类型的数据,不至于把程序写的太呆板,采用typedef将其类型名修改,要存储其他类型的数据时,只要修改即可。如下图,存储浮点型数据时,只要int改为float或double

打印链表

每打印一个数据,指针就向后移动一步

 注意:打印因为没有改变链表,所以只需传一级指针即可

           上图也可以不用创建cur,直接用phead

指针传参

无头单链表中,只有打印和查找数据时,不用传二级指针,其余时候都要传二级,因为要改变链表

上述两种都是传递地址,在C语言中要改变实参时,必须传地址才行 

链表节点创建与销毁

先用malloc开辟一块空间,大小是结构体的大小,然后再做判断,最后再对成员进行初始化即可

链表销毁时,要采用两个指针,一个在前,一个在后,要不然释放空间后,就成了野指针,找不到后面的数据了,把最后一个数据销毁后,再将其置空即可

 

尾插尾删

首先判断链表是否为空(没有数据),没有就直接添加数据即可,有就找尾,最后一个数据的next指针为空,就表示找到了,直接连接数据紧接其后即可

要分两种情况,一种是只有一个节点时,另一种是多个节点时

多个节点时,尾删也需要两个指针,否则在删除最后一个数据后,要把前一个数据的next*置空,否则next*就是野指针了,下一次删除就找不到尾了

一个节点时,直接释放,置空即可

 

头插头删

头插时不需要判断链表是否为空,只要创建新节点,让它指向头节点,再把新节点的地址赋值给头节点的指针,作为新的头节点指针

 头删时要判断链表是否为空,毕竟空链表不能删除

先用一个指针变量存储头节点的下一个节点的地址,再将头节点销毁,再将其前面创建的指针的值赋给头节点的指针 

随机位置插入与删除

查找随机数,返回其地址,找不到就返回空指针,同样不需要传二级指针

在随机数的后面插入数据,找不到数据时,pos为空,所以断言判断一下。如下图,如果不多创建一个指针变量时,就需要先连线2,再连线1,因为先练线1时,就找不到cur2,指向的那个节点了;多创建一个变量保存节点地址,1、2那个先连都可以

 

在随机数的前面插入数据时,要分两种情况

第一种,随机数就是头节点的数据时,直接头插即可

第二种,找到pos的前一个节点,再创建节点,3个节点连接即可

在前面和后面插入两种方法对比,在后面插入明显更简单,效率也更高,因为不需要找前一个节点,这也是单链表的缺陷,双链表可以弥补这一缺陷

删除随机数后面的一节点的数据时,要作2次断言,第一个是随机数存不存在,第二个是随机数的下一个节点存不存在

 

删除随机数时,首先判断是不是随机数存不存在,同样有两种情况

第一种,随机数是头节点的数据时,直接头删即可

第二种,找到随机数的前一个节点,再连接,释放随机数的节点即可

缺陷:

每存一个数据,都要存一个指针去链接后面数据节点

不支持随机访问(用下标去访问第i个)

全部代码

//头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int DataType;

typedef struct SList
{
	DataType data;
	struct SList* next;
}SLT;

//创建节点
SLT* SListNode(DataType x);
//打印链表
void SListPrint(SLT* phead);
//销毁链表
void SListDestroy(SLT** pphead);
//尾插
void SListPushBack(SLT** pphead, DataType x);
//尾删
void SListPopBack(SLT** pphead);
//头插
void SListPushFront(SLT** pphead, DataType x);
//头删
void SListPopFront(SLT** pphead);
//查找随机数
SLT* SListFind(SLT* pphead, DataType pos);
//随机位置向后插入
void SListInsert(SLT* pos, DataType x);
//随机位置的数的删除
void SListDeleteAfter(SLT* pos);
//随机位置向前插入
void SListInsertPrev(SLT** pphead, SLT* pos, DataType x);
//随机位置数据的删除
void SListDelete(SLT** pphead, SLT* pos);

//定义文件
#include"SList.h"

SLT* SListNode(DataType x)
{
	SLT* newnode = (SLT*)malloc(sizeof(SLT));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

void SListPushBack(SLT** pphead, DataType x)
{
	if (*pphead == NULL)
	{
		*pphead = SListNode(x);
	}
	else
	{
		SLT* cur = *pphead;
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = SListNode(x);
	}
}

void SListPrint(SLT* phead)
{
	SLT* cur = phead;
	while (cur)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void SListDestroy(SLT** pphead)
{
	assert(pphead);
	SLT* cur = *pphead;
	SLT* prev = *pphead;
	while (cur)
	{
		prev = cur;
		cur = cur->next;
		free(prev);
	}
	*pphead = NULL;
}

void SListPopBack(SLT** pphead)
{
	assert(*pphead);
	SLT* cur = *pphead;
	SLT* prev = NULL;
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		while (cur->next)
		{
			prev = cur;
			cur = cur->next;
		}
		free(cur);
		cur = NULL;
		prev->next = NULL;
	}
}

void SListPushFront(SLT** pphead, DataType x)
{
	SLT* cur = *pphead;
	SLT* newnode = SListNode(x);
	newnode->next = cur;
	*pphead = newnode;
}

void SListPopFront(SLT** pphead)
{
	assert(*pphead);
	SLT* cur = (*pphead)->next;
	free(*pphead);
	*pphead = cur;
}

SLT* SListFind(SLT* phead, DataType pos)
{
	assert(phead);
	SLT* cur = phead;
	while (cur)
	{
		if (cur->data == pos)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

void SListInsert(SLT* pos, DataType x)
{
	assert(pos);
	SLT* cur2 = pos->next;
	SLT* newnode = SListNode(x);
	newnode->next = cur2;
	pos->next = newnode;
}

void SListDelete(SLT** pphead, SLT* pos)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLT* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
void SListDeleteAfter(SLT* pos)
{
	assert(pos);
	assert(pos->next);
	pos->next = pos->next->next;
	free(pos->next);
	pos->next = NULL;
}

void SListInsertPrev(SLT** pphead, SLT* pos, DataType x)
{
	SLT* newnode = SListNode(x);
	if (*pphead == pos)
	{
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else
	{
		SLT* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}

//测试文件
#include"SList.h"
void test1()
{
	SLT* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);

	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);

	SListPrint(plist);
}
void test2()
{
	SLT* plist = NULL;
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPushFront(&plist, 4);
	SListPrint(plist);

	SListPopFront(&plist);
	SListPopFront(&plist);
	SListPopFront(&plist);
	SListPopFront(&plist);
	SListPrint(plist);

}
void test3()
{
	SLT* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);

	SLT* pos = SListFind(plist, 3);
	SListInsert(pos, 8);
	
	/*SListDeleteAfter(pos);
	SListPrint(plist);*/

	SListInsertPrev(&plist, pos, 15);
	SListPrint(plist);

	SListDelete(&plist, pos);
	SListPrint(plist);


	SListDestroy(&plist);
}
int main()
{
	//test1();
	//test2();
	test3();
	return 0;
}

二、链表带环问题

结论一:

快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表 带环则一定会在环中相遇

证明如下图:

 至于fast走4步,slow走1步的情况下,读者可依照上图依葫芦画瓢分析

结论二:

fast与slow一定会在环的入口处相遇

三、带头双链表

初始准备

首先创建一个结构体,有3个成员,第一个是存储数据,2个指针,一个指向前节点,另一个指向后节点,用typedef将其结构体类型名修改,使其简洁,方便使用

用typedef将其数据类型名修改,便于存储其它数据时,不用大幅度改动

链表节点创建与销毁

首先创建一个头节点,用malloc开辟空间,不存储数据,前后指针均指向本身

 

然后用malloc创建其它节点,添加节点时,只需调用函数即可,前后指针均置为NULL

销毁双链表时,从头节点后的节点开始销毁,最后再销毁头节点

打印双链表

尾插尾删

 

尾删不能把头节点给删了,所以需断言

 

头插头删

 

头删不能把头节点给删了,所以需断言

  

随机位置插入与删除

查找

找不到返回NULL,找得到返回其地址

 

 

 

全部代码

//头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;

typedef struct List
{
	DataType data;
	struct List* prev;
	struct List* next;
}LT;

//初始化双链表
LT* LTInit();
//创建节点
LT* LTNode(DataType x);
//打印双链表
void LTPrint(LT* phead);
//尾插
void LTPushBack(LT* phead, DataType x);
//尾删
void LTPopBack(LT* phead);
//头插
void LTPushFront(LT* phead, DataType x);
//头删
void LTPopFront(LT* phead);
//查找
LT* LTFind(LT* phead, DataType pos);
//随机位置插入数据
void LTInsert(LT* pos, DataType x);
//随机位置的数据删除
void LTDelete(LT* pos);
//销毁双链表
void LTDestroy(LT* phead);

//定义文件
#include"List.h"

LT* LTInit()
{
	LT* head = (LT*)malloc(sizeof(LT));
	if (head == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	head->next = head;
	head->prev = head;
	return head;
}

LT* LTNode(DataType x)
{
	LT* newnode = (LT*)malloc(sizeof(LT));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->prev = NULL;
	newnode->next = NULL;
	return newnode;
}

void LTPrint(LT* phead)
{
	assert(phead);
	LT* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void LTPushBack(LT* phead, DataType x)
{
	assert(phead);
	LT* newnode = LTNode(x);
	LT* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

void LTPopBack(LT* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LT* tail = phead->prev;
	LT* prevtail = phead->prev->prev;
	phead->prev = prevtail;
	prevtail->next = phead;
	free(tail);
	tail = NULL;
}

void LTPushFront(LT* phead, DataType x)
{
	assert(phead);
	LT* newnode = LTNode(x);
	LT* begintail = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	begintail->prev = newnode;
	newnode->next = begintail;
}

void LTPopFront(LT* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LT* begin = phead->next;
	LT* cur = begin->next;
	phead->next = cur;
	cur->prev = phead;
	free(begin);
	begin = NULL;
}

LT* LTFind(LT* phead, DataType pos)
{
	assert(phead);
	LT* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == pos)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

void LTInsert(LT* pos, DataType x)
{
	assert(pos);
	LT* newnode = LTNode(x);
	LT* prevpos = pos->prev;
	prevpos->next = newnode;
	newnode->prev = prevpos;
	pos->prev = newnode;
	newnode->next = pos;
}

void LTDelete(LT* pos)
{
	assert(pos);
	LT* prevpos = pos->prev;
	LT* tailpos = pos->next;
	prevpos->next = tailpos;
	tailpos->prev = prevpos;
	free(pos);
	pos = NULL;
}

void LTDestroy(LT* phead)
{
	assert(phead);
	LT* cur = phead->next;
	while (cur != phead)
	{
		LT* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

//测试文件
#include"List.h"
void test1()
{
	LT* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPushBack(plist, 5);
	LTPushBack(plist, 6);
	LTPushBack(plist, 7);
	LTPrint(plist);

	LTPopBack(plist);
	LTPopBack(plist);
	LTPopBack(plist);
	LTPopBack(plist);
	//LTPopBack(plist);
	//LTPopBack(plist);
	//LTPopBack(plist);
	//LTPopBack(plist);

	LTPrint(plist);
}
void test2()
{
	LT* plist = LTInit();
	LTPushFront(plist, 1);
	LTPushFront(plist, 2);
	LTPushFront(plist, 3);
	LTPushFront(plist, 4);
	LTPushFront(plist, 5);
	LTPushFront(plist, 6);
	LTPushFront(plist, 7);
	LTPrint(plist);

	LTPopFront(plist);
	LTPopFront(plist);
	LTPopFront(plist);
	LTPopFront(plist);
	LTPopFront(plist);
	//LTPopFront(plist);
	//LTPopFront(plist);
	//LTPopFront(plist);

	LTPrint(plist);
}
void test3()
{
	LT* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);

	LT* pos = LTFind(plist, 3);
	LTInsert(pos, 8);
	LTInsert(pos, 16);

	LTDelete(pos);
	LTPrint(plist);
	LTDestroy(plist);
}
int main()
{
	test1();
	//test2();
	//test3();
	return 0;
}

四、对比顺序表与链表

顺序表

优点:

支持随机访问。需要随机访问结构支持算法可以很好的适用

缺点:

头部中部插入删除时间效率低  O(n)

连续的物理空间,空间不够了以后需要增容:

增容有一定程度消耗

为了避免频繁增容,一般我们都按倍数去增容,用不完可能存在一定的空间浪费

链表(双向带头循环)

优点:

任意位置插入删除效率高  O(1)

按需申请释放空间

缺点:

不支持随机访问(用下标访问)。意味着:一些排序、二分查找等在这种结构上不适用

链表每存储一个值,同时要存储一个指针,也有一定的消耗

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值