【数据结构】链表的增删查改

一、链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。

 注意:

1.链式结构在逻辑上是连续的,但在物理上不一定连续

2.现实中的结点一般都是从堆上申请出来的

3.从堆上申请的空间,是按照一定的策略来分配,两次申请的空间,可能连续,也可能不连续。

二、链表的分类

以下情况组合起来就有8种链表结构:

1.单向 or 双向

2.带头or不带头

3.循环or非循环

常用两种结构:无头单向非循环链表带头双向循环链表

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结 构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了。

三、单链表的实现

需要实现的接口:

// SList.h
#include<stdio.h>
#include<assert.h>
#include <stdlib.h>

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

typedef int SLNDataType;
typedef struct SLNode
{
	SLNDataType data;
	struct SLNode* next;
}SLNode;

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);

// 在pos的前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);
// 删除pos位置
void SLTErase(SLNode** pphead, SLNode* pos);
// 销毁
void SLTDestroy(SLNode** pphead);

具体实现:

//SList.c
#include"SList.h"
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode != NULL)
	{
		newnode->data = x;
		newnode->next = NULL;
		return newnode;
	}
	else
		exit(-1);
}

void SListPrint(SListNode* plist)
{
	SListNode* cur = plist;
	//错误写法
	//while(cur->next)
	//while(cur->data)
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

void SListPushBack(SListNode** pplist, SLTDateType x)
{
	assert(pplist);
	//创建一个新节点
	SListNode* newnode = BuySListNode(x);
	//链表为空的情况
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		//链表不为空的情况
		//找尾
		SListNode* tail = *pplist;
		while (tail->next)
		{
			tail = tail->next;
		}
		//尾指针指向新的节点
		tail->next = newnode;
	}
}

void SListPushFront(SListNode** pplist, SLTDateType x)
{
	assert(pplist);
	//创建一个新节点
	SListNode* newnode = BuySListNode(x);
	
	//让newnode指向下一节点
	newnode->next = *pplist;

	//让plist指向newnode
	*pplist = newnode;
}


void SListPopBack(SListNode** pphead)
{
	assert(pphead);
	//先检查链表是否删空了若删空了,则不再删
	//温柔的检查
	//if ((*pphead)->next == NULL)
	//	return;
	
	//暴力检查
	assert(*pphead);

	//分情况讨论
	//若链表有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//若链表有多个节点
	else
	{
		//第一种写法
			//找尾tail  找尾的前一个prev
		SListNode* tail = *pphead;
		SListNode* prev = NULL;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		//尾部前一个置空 尾部释放空间 
		free(tail);
		prev->next = NULL;
		
		第二种写法
		//SListNode* tail = *pphead;
		//while (tail->next->next != NULL)
		//{
		//	tail = tail->next;
		//}
		//free(tail->next);//注意:释放的是指针指向的节点的内存空间,指针变量依然存在
		//tail->next = NULL;
	}	
}

void SListPopFront(SListNode** pphead)
{
	assert(pphead);
	//空链表 不删除
	assert(*pphead);

	//多个结点 一个结点 都适用

	
	//一种写法
	//先保存下一个结点的地址
	SListNode* tmp = (*pphead)->next;
	//旧的头指针置空
	free(*pphead);
	//头指针指向新的头结点
	*pphead = tmp;

	另一种写法
	//SListNode* tmp = *pphead;
	先让plist指向下一个节点
	//*pphead = (*pphead)->next;
	再释放 旧的头节点
	//free(tmp);
}

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	
	//遍历链表
	SListNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	//创建一个新节点
	SListNode* newnode = BuySListNode(x);
	//新节点指向pos之后的下一节点
	newnode->next = pos->next;
	//pos指向新的节点
	pos->next = newnode;
}

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

// 在pos的前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{
	//两种断言方式
	//1.严格规定pos一定是链表里面的一个有效节点
	assert(pphead);
	assert(pos);
	assert(*pphead);

	//2.要么都为空、要么都不为空
	//assert(*pphead && pos || !(*pphead) && !pos);
	
	//判断第一个节点是否是pos
	if (*pphead==pos)
	{
		//头插
		SListPushFront(*pphead, x);
	}
	else
	{
		SLNode* prev = *pphead;
		//prev找pos的前一个结点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//创建新节点
		SLNode* newnode= BuySListNode(x);
		//prev指向新的节点
		prev->next = newnode;
		//newnode指向下一个节点pos
		newnode->next = pos;
	}
}

//思考:若只给pos位置指针,想在pos前面插入,如何做?
//再pos后面插入, 交换节点中的值

// 删除pos位置
void SLTErase(SLNode** pphead, SLNode* pos)
{
	//空链表不删
	assert(*pphead);
	//要删的不能为空
	assert(pos);
	//防止乱传
	assert(pphead);

	//分情况讨论 
	//头节点就是Pos
	if (*pphead == pos)
	{
		//头删
		SListPopFront(pphead);
	}
	//头节点不是pos的情况
	else
	{
		//利用prev找pos
		SLNode* prev = *pphead;
		//找pos上一节点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//pos前一节点指向pos下一节点
		prev->next = pos->next;
		//删除pos位置
		free(pos);
		pos == NULL;
	}
}


void SLTDestroy(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLNode* cur = *pphead;
	while (cur)
	{
		//先保存下一个节点的地址
		SLNode* tmp = cur->next;
		//再释放当前节点
		free(cur);
		//再让cur指向下一节点
		cur = tmp;
	}
	*pphead = NULL;
}

测试代码:

//test.c
#include"SList.h"
test1()
{
	SListNode* plist = NULL;
	SListPrint(plist);
	SListPushBack(&plist, 1);
	SListPrint(plist);
	SListPushBack(&plist, 2);
	SListPrint(plist);
	SListPushBack(&plist, 3);
	SListPrint(plist);
	SListPushBack(&plist, 4);
	SListPrint(plist);
}
test2()
{
	SListNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);
	SListPushFront(&plist, 10);
	SListPushFront(&plist, 9);
	SListPrint(plist);
}
test3()
{
	SListNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);
	SListNode* ret = SListFind(plist, 4);
	printf("%d", ret->data);
}
test4()
{
	SListNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);
	SListNode* pos = SListFind(plist, 2);
	SListInsertAfter(pos, 20);
	SListPrint(plist);
}
test5()
{
	SListNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);
	SListNode* pos = SListFind(plist, 2);
	SListEraseAfter(pos);
	SListPrint(plist);
}
test6()
{
	SListNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);
	//测试删除pos 
	SListNode* pos = SListFind(plist, 1);
	SLTErase(&plist, pos);
	SListPrint(plist);
	//测试删除pos 
	pos = SListFind(plist, 3);
	SLTErase(&plist, pos);
	SListPrint(plist);
}
int main()
{
	测试尾插
	//test1();
	测试头插
	//test2();
	测试找结点
	//test3();
	测试单链表在pos位置之后插入x
	//test4();
	删除pos位置之后的值
	//test5();
	//测试删除pos
	test6();
	return 0;
}

四、双向链表的实现

需要实现的接口:

//DList.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}ListNode;
//初始化函数
ListNode* ListInit();
// 创建返回链表的头结点.
ListNode* ListCreate(LTDataType x);
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

具体实现:

//DList.c
#include"DList.h"
ListNode* ListCreate(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->_data = x;
	newnode->_next = NULL;
	newnode->_prev = NULL;
	return newnode;
}
ListNode* ListInit()
{
	ListNode* pHead = ListCreate(-1);
	pHead->_next = pHead;
	pHead->_prev = pHead;
	return pHead;
}

void ListDestory(ListNode* pHead)
{
	assert(pHead);
	//毁哨兵位
	ListNode* cur = pHead->_next;
	while (cur!= pHead)
	{
		ListNode* next = cur->_next;
		free(cur);
		cur = next;  
	}
	free(pHead);
	//pHead = NULL;

}
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	//不打印哨兵位
	ListNode* cur = pHead->_next;
	printf("pHead<=>");
	while (cur!= pHead)
	{
		printf("%d<=>", cur->_data);
		cur = cur->_next;
	}
	printf("\n");
}
尾插写法一:
//void ListPushBack(ListNode* pHead, LTDataType x)
//{
//	assert(pHead);
//	//创建新节点
//	ListNode* newnode = ListCreate(x);
//
//	ListNode* tail = pHead->_prev;
//	tail->_next = newnode;
//	newnode->_prev = tail;
//	newnode->_next = pHead;
//	pHead->_prev = newnode;
//}
尾插写法二:
void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//创建新节点
	ListNode* newnode = ListCreate(x);
	//在pHead的前面插入就是尾插
	ListInsert(pHead, x);
	
}
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->_next);
	ListNode* del = pHead->_prev;
	ListNode* tail = del->_prev;
	tail->_next = pHead;
	pHead->_prev = tail;
	free(del);
	del = NULL;
}
头插写法一:
//void ListPushFront(ListNode* pHead, LTDataType x)
//{
//	assert(pHead);
//	//创建新节点
//	ListNode* newnode = ListCreate(x);
//
//	ListNode* next = pHead->_next;
//	newnode->_next = next;
//	next->_prev = newnode;
//
//	pHead->_next = newnode;
//	newnode->_prev = pHead;
//}
头插写法二:
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//创建新节点
	ListNode* newnode = ListCreate(x);

	//在pHead的后面插入就是头插
	ListInsert(pHead->_next, x);
}
void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(pHead->_next != pHead);
	ListNode* del = pHead->_next;
	pHead ->_next = del->_next;
	del->_next->_prev = pHead;
	free(del);
	del=NULL;
}

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	//链表为空 不查找
	//assert(pHead->_next);
	ListNode* cur = pHead->_next;
	while (cur != pHead)
	{
		if (cur->_data == x)
			return cur;
		cur = cur->_next;
	}
	return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	//创建新节点
	ListNode* newnode = ListCreate(x);

	ListNode* pre = pos->_prev;
	pre->_next = newnode;
	newnode->_prev = pre;
	
	newnode->_next = pos;
	pos->_prev = newnode;
}

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* pre = pos->_prev;
	ListNode* next = pos->_next;
	pre->_next = next;
	next->_prev = pre;
	free(pos);
	pos = NULL;
}

测试代码:

//test.c
#include"DList.h"
test1()
{
	ListNode* DList = ListInit();
	ListPushBack(DList, 1);
	ListPushBack(DList, 2);
	ListPushBack(DList, 3);
	ListPushBack(DList, 4);
	ListPushBack(DList, 5);
	ListPrint(DList);
}
test2()
{
	ListNode* DList = ListInit();
	ListPushBack(DList, 1);
	ListPushBack(DList, 2);
	ListPushBack(DList, 3);
	ListPushBack(DList, 4);
	ListPushBack(DList, 5);
	ListPrint(DList);
	ListPopBack(DList);
	ListPrint(DList);
	ListPopBack(DList);
	ListPrint(DList);
	ListPopBack(DList);
	ListPrint(DList);
	ListPopBack(DList);
	ListPrint(DList);
	ListPopBack(DList);
	ListPrint(DList);
	
}
test3()
{
	ListNode* DList = ListInit();
	ListPushBack(DList, 1);
	ListPushBack(DList, 2);
	ListPushBack(DList, 3);
	ListPushBack(DList, 4);
	ListPushBack(DList, 5);
	ListPrint(DList);
	ListPushFront(DList, 10);
	ListPushFront(DList, 9);
	ListPushFront(DList, 8);
	ListPushFront(DList, 7);
	ListPrint(DList);
}
test4()
{
	ListNode* DList = ListInit();
	ListPushBack(DList, 1);
	ListPushBack(DList, 2);
	ListPushBack(DList, 3);
	ListPushBack(DList, 4);
	ListPushBack(DList, 5);
	ListPrint(DList);
	ListPopFront(DList);
	ListPopFront(DList);
	ListPopFront(DList);
	ListPrint(DList);
}
test5()
{
	ListNode* DList = ListInit();
	ListPushBack(DList, 1);
	ListPushBack(DList, 2);
	ListPushBack(DList, 3);
	ListPushBack(DList, 4);
	ListPushBack(DList, 5);
	ListPrint(DList);
	ListNode* ret = ListFind(DList, 5);
	printf("%d ", ret->_data);
}
test6()
{
	ListNode* DList = ListInit();
	ListPushBack(DList, 1);
	ListPushBack(DList, 2);
	ListPushBack(DList, 3);
	ListPushBack(DList, 4);
	ListPushBack(DList, 5);
	ListPrint(DList);
	ListNode* pos = ListFind(DList,4);
	ListInsert(pos, 20);
	ListPrint(DList);

}
test7()
{
	ListNode* DList = ListInit();
	ListPushBack(DList, 1);
	ListPushBack(DList, 2);
	ListPushBack(DList, 3);
	ListPushBack(DList, 4);
	ListPushBack(DList, 5);
	ListPrint(DList);
	ListNode* pos = ListFind(DList, 4);
	ListErase(pos);
	ListPrint(DList);
}
int main()
{
	//测试尾插
	//test1()
	//测试尾删
	//test2();
	//测试头插
	//test3();
	//测试头删
	//test4();
	//测试查找函数
	//test5();
	//测试pos前插入
	//test6();
	//删除pos
	test7();
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值