链表及链表的基本操作(C语言实现)

目录

什么是链表?链表的分类?

什么是链表

链表的分类

单链表

双向带头循环链表

单链表的实现及操作

完整代码

尾插

 为什么传二级指针?

 尾插时需要考虑的情况?

打印

尾删

头插

头删

查找

pos之后插入、删除

pos之前插入

删除pos

单链表的销毁

双向带头循环链表的实现及操作

完整代码

初始化

尾插

打印

尾删

头插

头删

查找

pos之前插入

删除pos

清理链表

销毁链表

链表和顺序表的对比

链表

顺序表


什么是链表?链表的分类?

什么是链表

链表是一种线性的数据结构,通过指针将一块块零散的内存块链接起来,链表的每个内存块称为结点(节点),内存块的地址空间是非连续的,所以链表在物理结构上是非连续的,逻辑结构上是连续的。(物理结构:链表的本质结构,逻辑结构:人想象出来的 )

链表的分类

链表分为8种结构:

这里我主要实现单链表和双向带头循环链表(包含了其它结构),这两种链表使用最多,为什么使用最多,原因有两点:
1,无头单向非循环链表(单链表):
LeetCode上单链表好出题 、基本不会单独作用链表使用(尾插需要找尾时间复杂O(N))、作为其它数据结构的一部分
2,带头双向循环链表作为链表使用,非循环肯定比循环结构简单一点,不带头肯定比带头结构简单一点,带头结点的链表也可以叫做带哨兵位的头结点

单链表

1,链表有一个头指针开始指向空每一个结构体有两个成员,一个用来存放数据,一个用来存放指针,通过指针来找到下一个结构体
2,链表的最后一个结点指向NULL
3,将这些无序的结构体连接起来,在逻辑上结构上单链表就是连续的

双向带头循环链表

我们在能够实现单链表的基础上再来实现双向带头循环链表会比较轻松,其结构和单链表有所不同,它每个结点多了一个指针既可以指向前面的结点也可以指向后面的结点,链表的最后一个节点不指向空,指向头结点(带哨兵位的头结点),头结点不存储有效数据,头结点又指向尾结点。

单链表的实现及操作

我们取单链表名字尾SList,single和List组成,简称单链表。

包含三个文件SList.h,SList.c,Test.c,分别作为声明、定义、测试文件。

完整代码

SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SListDataType;

typedef struct SListNode
{
	SListDataType data;
	struct SListNode* next;
}SListNode;

//尾插
void SListPushBack(SListNode** pphead, SListDataType x);
//打印
void SListPrint(SListNode* phead);
//尾删
void SListPopBack(SListNode** pphead);
//头插
void SListPushFront(SListNode** pphead, SListDataType x);
//头删
void SListPopFront(SListNode** pphead);
// 查找,并返回改结构体的地址
SListNode* SListFind(SListNode* phead, SListDataType x);
//pos之后插入
void SListInsertAfter(SListNode* phead, SListDataType x);
//pos之后删除
void SListEraseAfter(SListNode* phead);
//pos之前插入
void SListInsertBefore(SListNode** pphead, SListNode* pos, SListDataType x);
//删除pos
void SListErase(SListNode** pphead, SListNode* pos);

//单链表的销毁
void SListDestroy(SListNode** pphead);

SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

//尾插
SListNode* CreatNewNode(SListDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		printf("申请节点失败\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//尾插
void SListPushBack(SListNode** pphead, SListDataType x)
{
	if (*pphead == NULL)
	{
		SListNode* newNode = CreatNewNode(x);
		*pphead = newNode;
	}
	else
	{
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		SListNode* newNode = CreatNewNode(x);
		tail->next = newNode;
	}
}
//打印
void SListPrint(SListNode* phead)
{
	SListNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

//尾删
// 1,链表为NULL,2,一个节点,3,一个以上节点
void SListPopBack(SListNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* prev = NULL;
		SListNode* tail = *pphead;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

//头插
void SListPushFront(SListNode** pphead, SListDataType x)
{
	SListNode* newNode = CreatNewNode(x);
	newNode->next = *pphead;
	*pphead = newNode;

}
//头删
void SListPopFront(SListNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
	}
}

//查找
SListNode* SListFind(SListNode* phead, SListDataType x)
{
	SListNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//pos位置之后插入、删除
void SListInsertAfter(SListNode* pos, SListDataType x)
{
	assert(pos);
	SListNode* newNode = CreatNewNode(x);
	SListNode* next = pos->next;
	pos->next = newNode;
}

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	if (pos->next)
	{
		SListNode* nextnext = pos->next->next;
		free(pos->next);
		pos->next = nextnext;
	}
}

//pos之前插入
//需要传头指针是因为需要通过头指针遍历链表
void SListInsertBefore(SListNode** pphead, SListNode* pos, SListDataType x)
{

	SListNode* newNode = CreatNewNode(x);
	if (pos == *pphead)
	{
		newNode->next = *pphead;
		*pphead = newNode;
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newNode->next = pos;
		prev->next = newNode;
	}
}
//删除pos
void SListErase(SListNode** pphead, SListNode* pos)
{
	if (*pphead == pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}


//单链表的销毁
void SListDestroy(SListNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* prev = *pphead;
		SListNode* cur = prev->next;
		while (cur)
		{
			free(prev);
			prev = cur;
			cur = cur->next;
		}
		free(prev);
		prev = NULL;
	}
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void SListTest1()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
	SListPrint(pList);

	//尾删
	SListPopBack(&pList);
	SListPopBack(&pList);
	SListPopBack(&pList);
	SListPopBack(&pList);
	SListPopBack(&pList);
	SListPopBack(&pList);
	SListPrint(pList);

	//头插
	SListPushFront(&pList, -1);
	SListPushFront(&pList, -2);
	SListPushFront(&pList, -3);
	SListPushFront(&pList, -4);
	SListPushFront(&pList, -5);
	SListPushFront(&pList, -6);
	SListPrint(pList);
	//头删
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPrint(pList);
}

void SListTest2()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
	SListPrint(pList);

	//查找
	SListNode* pos = SListFind(pList, 4);
	if (pos == NULL)
	{
		printf("不存在该值\n");
		return;
	}
	else
	{
		//找到了可以修改
		printf("找到了\n");
		pos->data = 20;
	}
	SListPrint(pList);
	//pos之后插入
	SListInsertAfter(pos, 10);
	SListInsertAfter(pos, 30);
	SListPrint(pList);
	//pos之后删除
	SListEraseAfter(pos);
	SListPrint(pList);

	//pos之前插入, pos之前删除
	SListNode* pos1 = SListFind(pList, 2);
	if (pos1 == NULL)
	{
		printf("不存在该值\n");
		return;
	}
	SListInsertBefore(&pList, pos1, 80);
	SListInsertBefore(&pList, pos1, 70);
	SListPrint(pList);

	//删除pos的位置
	SListNode* pos2 = SListFind(pList, 80);
	if (pos2 == NULL)
	{
		printf("不存在该值\n");
		return;
	}
	SListErase(&pList, pos2);
	SListPrint(pList);

	//单链表的销毁
	SListDestroy(&pList);
}
int main()
{
	//SListTest1();
	SListTest2();
	return 0;
}

尾插

SList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SListDataType;

typedef struct SListNode
{
	SListDataType data;
	struct SListNode* next;
}SListNode;

//尾插
void SListPushBack(SListNode** pphead, SListDataType x);

由链表的定义可知,定义一个结构体,有两个成员变量一个用来存放有效数据,一个用来存放指针,该指针指向下一个结点,最后一个节点的指针指向NULL。

SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

//尾插
SListNode* CreatNewNode(SListDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		printf("申请节点失败\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void SListPushBack(SListNode** pphead, SListDataType x)
{
	if (*pphead == NULL)
	{
		SListNode* newNode = CreatNewNode(x);
		*pphead = newNode;
	}
	else
	{
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		SListNode* newNode = CreatNewNode(x);
		tail->next = newNode;
	}
}

节点定义好之后我们可以在Test.c中初始化一个 pList 指针指向 NULL(指针指向NULL可以不初始化), pList 作链表的头,如果链表为空,表示没有数据,头开始指向NULL,如果有一个数据插入,就要创建新的结点,我们写成一个函数CreatNewNode,就相当于把这个节点的地址作为新的头,也就是意味着改变了原来头的地址,数据发生改变的话,外面传过去的是pList指针的值,而函数体里面的改变不会影响外面,pList,所以需要传二级指针。

 为什么传二级指针?

 尾插时需要考虑的情况?

1,当链表为空

2,当链表只有一个结点

3,当链表有多个结点

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void SListTest1()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
}

int main()
{
	SListTest1();
	return 0;
}

 为了方便观察结果我们把打印实现一下

打印

SList.h

//打印
void SListPrint(SListNode* phead);

SList.c

//打印
void SListPrint(SListNode* phead)
{
	SListNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

 如果链表为空不进循环,直接打印 NULL,如果链表不为空

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void SListTest1()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
    SListPrint(pList);
}

int main()
{
	SListTest1();
	return 0;
}

运行结果如下:

尾删

SList.h

//尾删
void SListPopBack(SListNode** pphead);

SList.c

//尾删
// 1,链表为NULL,2,一个节点,3,一个以上节点
void SListPopBack(SListNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* prev = NULL;
		SListNode* tail = *pphead;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

尾删的时候需要考虑三种情况:
①链表为空

②链表只有一个结点
③链表有多个结点

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void SListTest1()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
    SListPrint(pList);

    //尾删
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPrint(pList);
}

int main()
{
	SListTest1();
	return 0;
}

测试结果如下:

头插

SList.h

//头插
void SListPushFront(SListNode** pphead, SListDataType x);

SList.c

//头插
void SListPushFront(SListNode** pphead, SListDataType x)
{
	SListNode* newNode = CreatNewNode(x);
	newNode->next = *pphead;
	*pphead = newNode;
}

 图解如下:

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void SListTest1()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
    SListPrint(pList);

    //尾删
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPrint(pList);
    
    //头插
	SListPushFront(&pList, -1);
	SListPushFront(&pList, -2);
	SListPushFront(&pList, -3);
	SListPushFront(&pList, -4);
	SListPushFront(&pList, -5);
	SListPushFront(&pList, -6);
	SListPrint(pList);
}

int main()
{
	SListTest1();
	return 0;
}

测试结果如下:

头删

SList.h

//头删
void SListPopFront(SListNode** pphead);

SList.c

//头删
void SListPopFront(SListNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
	}
}

 头删有三种情况需要考虑:

①链表为空

②链表只有一个结点

③链表有多个结点

 头删情况分析:

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void SListTest1()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
    SListPrint(pList);

    //尾删
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPopBack(&pList);
    SListPrint(pList);
    
    //头插
	SListPushFront(&pList, -1);
	SListPushFront(&pList, -2);
	SListPushFront(&pList, -3);
	SListPushFront(&pList, -4);
	SListPushFront(&pList, -5);
	SListPushFront(&pList, -6);
	SListPrint(pList);

    	//头删
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPrint(pList);
}

int main()
{
	SListTest1();
	return 0;
}

运行结果如下:

查找

SList.h

// 查找,并返回改结构体的地址
SListNode* SListFind(SListNode* phead, SListDataType x);

SList.c

//查找
SListNode* SListFind(SListNode* phead, SListDataType x)
{
	SListNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

查找和打印类型,都是遍历链表

Test.c

void SListTest2()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
	SListPrint(pList);

	//查找
	SListNode* pos = SListFind(pList, 4);
	if (pos == NULL)
	{
		printf("不存在该值\n");
		return;
	}
	else
	{
		//找到了可以修改
		printf("找到了\n");
		pos->data = 20;
	}

}

int main()
{
	SListTest2();
	return 0;
}

 运行结果如下:

pos之后插入、删除

在查找的基础上进行插入

SList.h

//pos之后插入
void SListInsertAfter(SListNode* phead, SListDataType x);
//pos之后删除
void SListEraseAfter(SListNode* phead);

SList.c

//pos位置之后插入、删除
void SListInsertAfter(SListNode* pos, SListDataType x)
{
	assert(pos);
	SListNode* newNode = CreatNewNode(x);
	SListNode* next = pos->next;
	pos->next = newNode;
}

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	if (pos->next)
	{
		SListNode* nextnext = pos->next->next;
		free(pos->next);
		pos->next = nextnext;
	}
}

 pos所在位置如图所示:

Test.c

void SListTest2()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
	SListPrint(pList);

	//查找
	SListNode* pos = SListFind(pList, 4);
	if (pos == NULL)
	{
		printf("不存在该值\n");
		return;
	}
	else
	{
		//找到了可以修改
		printf("找到了\n");
		pos->data = 20;
	}
	SListPrint(pList);
	//pos之后插入
	SListInsertAfter(pos, 30);
	SListInsertAfter(pos, 10);
	SListPrint(pList);
   

   //pos之后删除
   //SListEraseAfter(pos);
   //SListPrint(pList);
}

int main()
{
	//SListTest1();
	SListTest2();
	return 0;
}

插入运行结果:

删除运行结果:

pos之前插入

SList.h

//pos之前插入
void SListInsertBefore(SListNode** pphead, SListNode* pos, SListDataType x);

SList.c

//pos之前插入
//需要传头指针是因为需要通过头指针遍历链表
void SListInsertBefore(SListNode** pphead, SListNode* pos, SListDataType x)
{

	SListNode* newNode = CreatNewNode(x);
	if (pos == *pphead)
	{
		newNode->next = *pphead;
		*pphead = newNode;
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newNode->next = pos;
		prev->next = newNode;
	}
}

如图所示:

Test.c

void SListTest2()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
	SListPrint(pList);
   //pos之前插入, pos之前删除
   SListNode* pos1 = SListFind(pList, 2);
   if (pos1 == NULL)
   {
	  printf("不存在该值\n");
	  return;
   }
    SListInsertBefore(&pList, pos1, 80);
    SListInsertBefore(&pList, pos1, 70);
    SListPrint(pList);
}
int main()
{
	SListTest2();
	return 0;
}

 运行结果如下:

删除pos

SList.h

//删除pos
void SListErase(SListNode** pphead, SListNode* pos);

SList.c

//删除pos
void SListErase(SListNode** pphead, SListNode* pos)
{
	if (*pphead == pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

①如果pos在头结点,直接可以调用头删函数
②其它正常情况
找到pos的前一个和pos的后一个,把两个结点连接起来释放掉pos

Test.c

void SListTest2()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
	SListPrint(pList);
   //pos之前插入, pos之前删除
   SListNode* pos1 = SListFind(pList, 2);
   if (pos1 == NULL)
   {
	  printf("不存在该值\n");
	  return;
   }
    SListInsertBefore(&pList, pos1, 80);
    SListInsertBefore(&pList, pos1, 70);
    SListPrint(pList);
    //删除pos的位置
    SListNode* pos2 = SListFind(pList, 80);
    if (pos2 == NULL)
    {
	    printf("不存在该值\n");
	    return;
    }
    SListErase(&pList, pos2);
    SListPrint(pList);
}
int main()
{
	SListTest2();
	return 0;
}

运行结果如下:

单链表的销毁

SList.h

//单链表的销毁
void SListDestroy(SListNode** pphead);

SList.c

//单链表的销毁
void SListDestroy(SListNode** pphead)
{
	if (*pphead == NULL)
	{
		return;
	}
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* prev = *pphead;
		SListNode* cur = prev->next;
		while (cur)
		{
			free(prev);
			prev = cur;
			cur = cur->next;
		}
		free(prev);
		prev = NULL;
	}
}

 释放单链表需要考虑三种情形:

①单链表没有节点,直接返回

②单链表只有一个结点,直接释放

③单链表情况正常,有多个结点,需要遍历,通过prev不断保存前一个,然后释放,

让cur往后走

Test.c

void SListTest2()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);
	SListPushBack(&pList, 5);
	SListPushBack(&pList, 6);
	SListPrint(pList);
   //pos之前插入, pos之前删除
   SListNode* pos1 = SListFind(pList, 2);
   if (pos1 == NULL)
   {
	  printf("不存在该值\n");
	  return;
   }
    SListInsertBefore(&pList, pos1, 80);
    SListInsertBefore(&pList, pos1, 70);
    SListPrint(pList);
    //删除pos的位置
    SListNode* pos2 = SListFind(pList, 80);
    if (pos2 == NULL)
    {
	    printf("不存在该值\n");
	    return;
    }
    SListErase(&pList, pos2);
    SListPrint(pList);

    //单链表的销毁
    SListDestroy(&pList);
}
int main()
{
	SListTest2();
	return 0;
}

双向带头循环链表的实现及操作

双向带头循环链表是使用最多的,是在单链表的基础上增加了一个头结点也可以叫做带哨兵位的头结点,该结点不存储有效数据。

还增加了一个指针,指向链表的前面,尾结点不再指向NULL,而是指向头结点。

在这个链表实现中不需要传二级指针,因为没有改变该链表的头结点。

 链表有多个结点的情况:

完整代码

List.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int ListDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	ListDataType data;
}ListNode;

//创建新结点
ListNode* BuyListNode(ListDataType x);
//初始化
//void ListInit(ListNode** pphead);
ListNode* ListInit();
//尾插
void ListPushBack(ListNode* phead, ListDataType x);
//打印
void ListPrint(ListNode* phead);
// 尾删
void ListPopBack(ListNode* phead);
//头插
void ListPushFront(ListNode* phead, ListDataType x);
//头删
void ListPopFront(ListNode* phead);
// 查找
ListNode* ListFind(ListNode* phead, ListDataType x);
//pos之前插入
void ListInsert(ListNode* pos, ListDataType x);
//删除pos
void ListErase(ListNode* pos);
//清理链表
void ListClear(ListNode* phead);
//销毁链表
void ListDestory(ListNode** pphead);

List.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
//创建新结点
ListNode* BuyListNode(ListDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		printf("申请节点失败\n");
		exit(-1);
	}
	//创建的新节点初始化,不初始化是随机值,可能带了一些问题
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;
	return newnode;
}

//初始化头结点
//void ListInit(ListNode** pphead) 
//{
//	*pphead = BuyListNode(0);
//	(*pphead)->next = NULL;
//	(*pphead)->prev = NULL;
//}

ListNode* ListInit()
{
	ListNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

//没有改变头指针,利用头指针进行操作
void ListPushBack(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* tail = phead->prev;
	ListNode* newnode = BuyListNode(x);
	//处理三者  phead   tail   newndoe
	tail->next = newnode;
	newnode->prev = tail;

	newnode->next = phead;
	phead->prev = newnode;
	// phead 前一个插入就是尾
	//ListInsert(phead, x);
}
//打印
void ListPrint(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf(" %d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

// 尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* tail = phead->prev;
	ListNode* tailPrev = tail->prev;
	phead->prev = tailPrev;
	tailPrev->next = phead;
	free(tail);
	//不置NULL也可以,tail的生命周期在函数内部,外面无法使用tail
	tail = NULL;

	头的前一个就是尾
	//ListErase(phead->prev);
}

//头插
void ListPushFront(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* first = phead->next;
	ListNode* newnode = BuyListNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;

	//ListInsert(phead->next, x);
}
// 头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* first = phead->next;
	ListNode* second = first->next;
	// phead first  second
	phead->next = second;
	second->prev = phead;
	free(first);
	first = NULL;

	头的下一个
	//ListErase(phead->next);
}


//查找 = 修改
ListNode* ListFind(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

//在pos前面插入x
void ListInsert(ListNode* pos, ListDataType x)
{
	assert(pos);
	ListNode* posPrev = pos->prev;
	ListNode* newnode = BuyListNode(x);
	//posPrev  newnode  pos
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}
void ListErase(ListNode* pos)
{
	assert(pos);
	//assert(pos != phead);
	ListNode* posPrev = pos->prev;
	ListNode* posNext = pos->next;
	free(pos);
	//置空pos外面不改变,因为传的是一级指针,内部的改变不会影响外面
	/*pos = NULL;*/
	posPrev->next = posNext;
	posNext->prev = posPrev;
}


//清理链表
//清理所有的数据节点,保留头结点可继续使用
void ListClear(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	//ListNode* prev = NULL;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	//野指针
	//prev = cur;
	//free(prev);
	//cur = cur->next;
	}
	phead->prev = phead;
	phead->next = phead;
}
//销毁链表 , 从head->next 开始删除,留着head判结束
void ListDestory(ListNode** pphead)
{
	assert(*pphead);
	ListClear(*pphead);
	free(*pphead);
	*pphead = NULL;
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
void TestList1()
{
	//初始化
	/*ListNode* phead = NULL;
	ListInit(&phead);*/
	ListNode* phead = ListInit();
	//尾插
	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushBack(phead, 3);
	ListPushBack(phead, 4);
	ListPrint(phead);
	//尾删
	ListPopBack(phead);
	ListPopBack(phead);
	ListPopBack(phead);
	ListPopBack(phead);
	ListPrint(phead);

	//头插
	ListPushFront(phead, 1);
	ListPushFront(phead, 2);
	ListPushFront(phead, 3);
	ListPushFront(phead, 4);
	ListPushFront(phead, 5);
	ListPrint(phead);
	//头删
	ListPopFront(phead);
	ListPopFront(phead);
	ListPopFront(phead);
	ListPopFront(phead);
	ListPrint(phead);
	//销毁
	ListDestory(&phead);
}

void TestList2()
{
	ListNode* phead = ListInit();
	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushBack(phead, 3);
	ListPushBack(phead, 4);
	ListPrint(phead);
	//查找
	ListNode* pos = ListFind(phead, 4);
	if (pos == NULL)
	{
		return;
	}
	//pos之前插入
	ListInsert(pos, 40);
	ListPrint(phead);
	//删除pos
	ListErase(pos);
	ListPrint(phead);
	//销毁
	ListDestory(&phead);
}
int main()
{
	TestList1();
	//TestList2();
	return 0;
}

初始化

List.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int ListDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	ListDataType data;
}ListNode;

//创建新结点
ListNode* BuyListNode(ListDataType x);
//初始化
//void ListInit(ListNode** pphead);
ListNode* ListInit();

List.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
//创建新结点
ListNode* BuyListNode(ListDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		printf("申请节点失败\n");
		exit(-1);
	}
	//创建的新节点初始化,不初始化是随机值,可能带了一些问题
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;
	return newnode;
}

//初始化头结点
//void ListInit(ListNode** pphead) 
//{
//	*pphead = BuyListNode(0);
//	(*pphead)->next = NULL;
//	(*pphead)->prev = NULL;
//}

ListNode* ListInit()
{
	ListNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

 初始化头结点可以传二级指针,函数里面的改变会影响外面,或者返回头结点地址,

 这里我们使用返回头结点这种方法。

 在创建新结点的时候我们直接把创建新节点写成一个函数,可以直接调用。

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
void TestList1()
{
	//初始化
	/*ListNode* phead = NULL;
	ListInit(&phead);*/
	ListNode* phead = ListInit();
}

int main()
{
	TestList1();
	return 0;
}

尾插

初始化完成之后,我们就可以在头结点后面进行尾插。

List.h

//尾插
void ListPushBack(ListNode* phead, ListDataType x);

List.c

void ListPushBack(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* tail = phead->prev;
	ListNode* newnode = BuyListNode(x);
	//处理三者  phead   tail   newndoe
	tail->next = newnode;
	newnode->prev = tail;

	newnode->next = phead;
	phead->prev = newnode;
	// phead 前一个插入就是尾
	//ListInsert(phead, x);
}

尾插就是找尾,而由双向带头循环链表的定义可知,刚刚也初始化了,如果只有一个头结点就指向自己,如果有有效数据,就是指向尾结点。

插入时候考虑两种情况:

链表只有头结点:头结点即为尾结点,直接在后面插入,然后改变指针指向关系。

链表有多个结点:头结点指向prev即为尾节点 tail, 然后改变指向关系

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
void TestList1()
{
	//初始化
	/*ListNode* phead = NULL;
	ListInit(&phead);*/
	ListNode* phead = ListInit();

    //尾插
    ListPushBack(phead, 1);
    ListPushBack(phead, 2);
    ListPushBack(phead, 3);
    ListPushBack(phead, 4);
    ListPrint(phead);
}

int main()
{
	TestList1();
	return 0;
}

 尾插结束为了更好的看到效果,我们先要实现打印

打印

List.h

//打印
void ListPrint(ListNode* phead);

List.c

//打印
void ListPrint(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf(" %d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

 如图所示:

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
void TestList1()
{
	//初始化
	/*ListNode* phead = NULL;
	ListInit(&phead);*/
	ListNode* phead = ListInit();

    //尾插
    ListPushBack(phead, 1);
    ListPushBack(phead, 2);
    ListPushBack(phead, 3);
    ListPushBack(phead, 4);
    ListPrint(phead);
}

int main()
{
	TestList1();
	return 0;
}

 打印结果如下:

尾删

List.h

// 尾删
void ListPopBack(ListNode* phead);

List.c

// 尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* tail = phead->prev;
	ListNode* tailPrev = tail->prev;
	phead->prev = tailPrev;
	tailPrev->next = phead;
	free(tail);
	//不置NULL也可以,tail的生命周期在函数内部,外面无法使用tail
	tail = NULL;

	头的前一个就是尾
	//ListErase(phead->prev);
}

 尾删就是找尾

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
void TestList1()
{
	//初始化
	/*ListNode* phead = NULL;
	ListInit(&phead);*/
	ListNode* phead = ListInit();

    //尾插
    ListPushBack(phead, 1);
    ListPushBack(phead, 2);
    ListPushBack(phead, 3);
    ListPushBack(phead, 4);
    ListPrint(phead);
    //尾删
    ListPopBack(phead);
    ListPopBack(phead);
    ListPopBack(phead);
    ListPrint(phead);
}

int main()
{
	TestList1();
	return 0;
}

测试结果如下:

头插

List.h

//头插
void ListPushFront(ListNode* phead, ListDataType x);

List.c

//头插
void ListPushFront(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* first = phead->next;
	ListNode* newnode = BuyListNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;

	//ListInsert(phead->next, x);
}

 头插就是找真正的头结点,真正存储有数据的头在头结点的后面

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
void TestList1()
{
	//初始化
	/*ListNode* phead = NULL;
	ListInit(&phead);*/
	ListNode* phead = ListInit();

    //尾插
    ListPushBack(phead, 1);
    ListPushBack(phead, 2);
    ListPushBack(phead, 3);
    ListPushBack(phead, 4);
    ListPrint(phead);
    //尾删
    ListPopBack(phead);
    ListPopBack(phead);
    ListPopBack(phead);
    ListPrint(phead);
    //头插
    ListPushFront(phead, 1);
    ListPushFront(phead, 2);
    ListPushFront(phead, 3);
    ListPushFront(phead, 4);
    ListPushFront(phead, 5);
    ListPrint(phead);
}

int main()
{
	TestList1();
	return 0;
}

测试结果如下:

头删

List.h

//头删
void ListPopFront(ListNode* phead);

List.c

// 头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* first = phead->next;
	ListNode* second = first->next;
	// phead first  second
	phead->next = second;
	second->prev = phead;
	free(first);
	first = NULL;

	头的下一个
	//ListErase(phead->next);
}

链表不只有头结点不头删
我们删除的时候,先保存真正的头结点的下一个结点
然后处理连接关系

 只有一个头结点和有有效数据的情况:

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
void TestList1()
{
	//初始化
	/*ListNode* phead = NULL;
	ListInit(&phead);*/
	ListNode* phead = ListInit();

    //尾插
    ListPushBack(phead, 1);
    ListPushBack(phead, 2);
    ListPushBack(phead, 3);
    ListPushBack(phead, 4);
    ListPrint(phead);
    //尾删
    ListPopBack(phead);
    ListPopBack(phead);
    ListPopBack(phead);
    ListPrint(phead);
    //头插
    ListPushFront(phead, 1);
    ListPushFront(phead, 2);
    ListPushFront(phead, 3);
    ListPushFront(phead, 4);
    ListPushFront(phead, 5);
    ListPrint(phead);

    //头删
    ListPopFront(phead);
    ListPopFront(phead);
    ListPopFront(phead);
    ListPopFront(phead);
    ListPrint(phead);
}

int main()
{
	TestList1();
	return 0;
}

测试结果如下:

查找

List.h

// 查找
ListNode* ListFind(ListNode* phead, ListDataType x);

List.c

//查找 = 修改
ListNode* ListFind(ListNode* phead, ListDataType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

 和打印同理都是遍历链表,找到就返回结点地址

Test.c

void TestList2()
{
	ListNode* phead = ListInit();
	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushBack(phead, 3);
	ListPushBack(phead, 4);
	ListPrint(phead);
	//查找
	ListNode* pos = ListFind(phead, 4);
	if (pos == NULL)
	{
		return;
	}


}
int main()
{
	TestList2();
	return 0;
}

pos之前插入

List.h

//pos之前插入
void ListInsert(ListNode* pos, ListDataType x);

List.c

//在pos前面插入x
void ListInsert(ListNode* pos, ListDataType x)
{
	assert(pos);
	ListNode* posPrev = pos->prev;
	ListNode* newnode = BuyListNode(x);
	//posPrev  newnode  pos
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

找到pos,保存pos结点的前一个posPrev,连接 posPrev、newnode、pos三者的关系。

Test.c

void TestList2()
{
	ListNode* phead = ListInit();
	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushBack(phead, 3);
	ListPushBack(phead, 4);
	ListPrint(phead);
	//查找
	ListNode* pos = ListFind(phead, 4);
	if (pos == NULL)
	{
		return;
	}
   //pos之前插入
    ListInsert(pos, 40);
    ListPrint(phead);

}
int main()
{
	TestList2();
	return 0;
}

测试结果如下:

删除pos

List.h

//删除pos
void ListErase(ListNode* pos);

List.c

void ListErase(ListNode* pos)
{
	assert(pos);
	//assert(pos != phead);
	ListNode* posPrev = pos->prev;
	ListNode* posNext = pos->next;
	free(pos);
	//置空pos外面不改变,因为传的是一级指针,内部的改变不会影响外面
	/*pos = NULL;*/
	posPrev->next = posNext;
	posNext->prev = posPrev;
}

1,删除pos我们需要保存pos的前一个和pos的后一个,如果只有头结点不做删除操作。 ,2,如果除了头结点,还有一个真正的头,保存前一个和后一个都是保存头结点,释放pos,最后处理后头结点自己指向自己。

3,如果有多个有效数据,找到pos的前一个后一个保存,然后处理连接关系。

Test.c

void TestList2()
{
	ListNode* phead = ListInit();
	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushBack(phead, 3);
	ListPushBack(phead, 4);
	ListPrint(phead);
	//查找
	ListNode* pos = ListFind(phead, 4);
	if (pos == NULL)
	{
		return;
	}
   //pos之前插入
    ListInsert(pos, 40);
    ListPrint(phead);

    //删除pos
    ListErase(pos);
    ListPrint(phead);

}
int main()
{
	TestList2();
	return 0;
}

测试结果如下:

清理链表

为什么存在清理链表和销毁链表,就像购物车一样,你把你所有的商品都清空,就相当于清理链表,但是我还要用这个购物车,而把购物车都清理掉就相当于销毁链表。

List.h

//清理链表
void ListClear(ListNode* phead);

List.c

//清理所有的数据节点,保留头结点可继续使用
void ListClear(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	//ListNode* prev = NULL;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	//野指针
	//prev = cur;
	//free(prev);
	//cur = cur->next;
	}
	phead->prev = phead;
	phead->next = phead;
}

 保存下一个结点,然后释放当前结点cur,最后让cur不断向后走

Test.c

ListClear(phead);

销毁链表

List.h

//销毁链表
void ListDestory(ListNode** pphead);

List.c

//销毁链表 
void ListDestory(ListNode** pphead)
{
	assert(*pphead);
	ListClear(*pphead);
	free(*pphead);
	*pphead = NULL;
}

在清理的基础上,对链表的头结点释放,传入一级指针,函数内部的改变不会影响外面的phead

因此,我们需要用到二级指针将头结点改变置成NULL

Test.c

//销毁
ListDestory(&phead);

链表和顺序表的对比

链表

链表是由数组的缺陷产生的,链表的增删查改比顺序表效率高

缺陷:不能随机访问。

顺序表

顺序表就是在数组的基础上实现增删查改,并且插入时可以动态增长

顺序表的缺陷:a、可能存在一定的空间浪费
                       b、增容有一些效率损失
                       c、中间或者头部插入删除,时间复杂度O(N),因为要挪动数据
                       这些问题都由链表解决了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值