数据结构-链表

1 篇文章 0 订阅
1 篇文章 0 订阅

目录

单向链表

插入链表数据

删除链表数据

单链表打印

单链表查找

双向链表

双向链表插入数据

双向链表删除数据

双向链表打印/查找

双向链表销毁

单双链表整体代码

不带头单向链表

带头双向循环链表


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

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构

单向/双向带头链表:在链表的前段带一个头结点,不用于存储数据,方便进行头部删除和插入数据时不需要更改头节点的位置;

单向/双向不带头链表:链表的第一个元素作为头节点,在往头部插入或删除节点时,需要调整头节点的位置;

单向/双向循环链表:链表的最后一个节点指向头结点

单向/双向非循环链表:链表最后一个节点指向NULL


单向链表

例如下图:

单向链表,链表的上半存储数据,下半存储指向下一个节点的地址,链表最后一个节点的下半存储空指针;

typedef struct ListNode
{
	int data;
	struct ListNode* next;
}ListNode;

这里就是一个单向链表,data存储数据,next存储下一个节点的地址,


插入链表数据

需要给链表存储数据时,则创建一块内存,数据存入data,next连接下一个链表节点,如果没有则设为空指针,例如下列代码:

//开辟新内存空间用于存储节点
SListNode* BuySListNode(SLTDateType x)//创建节点
{
	SListNode* lsjg = (SListNode*)malloc(sizeof(SListNode));
	assert(lsjg);
	lsjg->data = x;
	lsjg->next = NULL;//初始化时默认下一个节点指向空
    return lsjg;
}

(从链表尾部插入节点)



void SListPushBack(SListNode** pplist, SLTDateType x)//尾插数据
{
	SListNode* ls =  BuySListNode(x);//开辟链表节点
	if (*pplist == NULL)//如果pplist中没有链表节点,则将这次的节点设为头节点
	{
		*pplist = ls;
	}
	else
	{
		SListNode* lsbr = *pplist;
		while (lsbr->next)//将链表循环至最后一个节点的位置
		{
			lsbr = lsbr->next;
		}
		lsbr->next = ls;//插入新节点
	}
}

(从链表头部插入节点)

void SListPushFront(SListNode** pplist, SLTDateType x)
{
	SListNode* ls = BuySListNode(x);//开辟链表节点
	ls->next = *pplist;//将新节点的next指向头节点
	*pplist = ls;//将新节点设为头节点
}

(单链表在pos位置之后插入x)

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	if (pos->next)
	{
		SListNode* ls = pos->next;//记录pos后的一个节点
        
        //插入新节点并连接ls
		pos->next = BuySListNode(x);
		pos->next->next = ls;
	}
	else//pos为最后一个节点则调用尾部插入函数即可
	{
		SListPushBack(&pos,x);
	}
}

删除链表数据

删除数据时,将需要删除的链表节点释放掉,再恢复原链表走向;

(从链表尾部删除节点)

void SListPopBack(SListNode** pplist)
{
	assert(*pplist);
	SListNode* ls = *pplist;
	SListNode* ls1 = NULL;
	while (ls->next)//将链表循环至最后一个节点的位置
	{
		ls1 = ls;//保存前一个节点的位置
		ls = ls->next;
	}
	free(ls);//释放最后的节点
	if (ls1)
		ls1->next = NULL;//链表中还有数据,则将前一个当做最后节点next指向NULL,
	else
		*pplist = NULL;//链表中没有数据,则将链表设为空
}

(从链表头部删除节点)

void SListPopFront(SListNode** pplist)
{
	assert(*pplist);
	SListNode* ls = *pplist;
	if (ls->next)//判断链表中是否只剩最后一个节点
	{
        //释放原头节点,将头节点设为下一个节点
		ls = ls->next;
		free(*pplist);
		*pplist = ls;
	}
	else
	{
		free(*pplist);
		*pplist = NULL;
	}
}

(单链表删除pos位置之后的值)

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	if (pos->next)
	{
		SListNode* ls = pos->next;//记录需要删除的数据
		pos->next = ls->next;//先将pos连向后一个节点
		free(ls);//再释放需要删除的节点
	}
}

单链表打印

void SListPrint(SListNode* plist)
{
	if (plist)
	{
		while (plist->next)
		{
			printf("%d->", plist->data);
			plist = plist->next;
		}
		printf("%d->", plist->data);
	}
	printf("\n");
}

单链表查找

//找链表中的x值
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	assert(plist);
	while (plist->next&& plist->data != x)
	{
		plist = plist->next;
	}
	if (plist->data == x)
		return plist;
	return NULL;
}

双向链表

例如下图:

(双向链表)

(双向循环链表) 

 在单向链表中我们只能读取到链表往后的节点,无法往前读取节点,双向链表则弥补了这个缺陷,既可以向前读取也可以像后读取;

data为链表存储的数据,next为下一个节点的地址,prev为上一个节点的地址;

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}ListNode;

这里示范为带头双向循环链表

(初始化创建链表的头结点)

ListNode* ListCreate()
{
	ListNode* xtsr = (ListNode*)malloc(sizeof(ListNode));
	if (xtsr == NULL)
		return;
	xtsr->_next = xtsr;
	xtsr->_prev = xtsr;
	return xtsr;
}

此时链表头节点一个元素,所以next和prev都指向自身;


双向链表插入数据

(双向链表在pos的前面进行插入)

ListNode* ccskfar(int x)//开辟内存给链表
{
	ListNode* krsc = (ListNode*)malloc(sizeof(ListNode));
	if (krsc == NULL)
		return;
	krsc->_data = x;
	return krsc;
}

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* nextcr =  ccskfar(x);
	ListNode* prevp = pos->_prev;//记录链表前一个内存的地址

    //将prevp与新节点之间互相连接
	prevp->_next = nextcr;
	nextcr->_prev = prevp;

    //将新节点与pos之间互相连接
	nextcr->_next = pos;
	pos->_prev = nextcr;
}

(双向链表尾插/双向链表头插)尾插与头插直接调用上面函数即可,

void ListPushBack(ListNode* pHead, LTDataType x)/// 双向链表尾插
{
	assert(pHead);
	ListInsert(pHead, x);

    //实现尾插
	//ListNode* pisds = pHead->_prev;
	//ListNode* crds = ccskfar(x);
	//pisds->_next = crds;
	//crds->_prev = pisds;
	//crds->_next = pHead;
	//pHead->_prev = crds;
}

void ListPushFront(ListNode* pHead, LTDataType x)// 双向链表头插
{
	assert(pHead);
	ListInsert(pHead->_next, x);

    //实现头插
	//ListNode* nedxign = ccskfar(x);
	//ListNode* nedes = pHead->_next;
	//pHead->_next = nedxign;
	//nedxign->_prev = pHead;
	//nedxign->_next = nedes;
	//nedes->_prev = nedxign;
}

双向链表删除数据

(双向链表删除pos位置的节点)

void ListErase(ListNode* pos)
{
	assert(pos);
    //记录pos前后两个节点
	ListNode* posprev = pos->_prev;
	ListNode* posnext = pos->_next;

    //释放pos后重新连接前后节点
	free(pos);
	posprev->_next = posnext;
	posnext->_prev = posprev;
}

(双向链表尾删/双向链表头删)尾删头删调用上面函数即可

void ListPopBack(ListNode* pHead)// 双向链表尾删
{
	assert(pHead && pHead->_next != pHead);
	ListErase(pHead->_prev);

    
	//ListNode* wei = pHead->_prev->_prev;
	//free(wei->_next);
	//wei->_next = pHead;
	//pHead->_prev = wei;
}

void ListPopFront(ListNode* pHead)// 双向链表头删
{
	assert(pHead);
	ListErase(pHead->_next);

    
	//ListNode* nesdx = pHead->_next->_next;
	//free(pHead->_next);
	//pHead->_next = nesdx;
	//nesdx->_prev = pHead;
}

双向链表打印/查找

void ListPrint(ListNode* pHead)// 双向链表打印
{
	assert(pHead);
	ListNode* tstls = pHead->_next;
	while (tstls!=pHead)//因为是循环链表,所以结束条件为==头节点地址
	{
		printf("%d ", tstls->_data);
		tstls = tstls->_next;
	}
	printf("\n");
}

ListNode* ListFind(ListNode* pHead, LTDataType x)// 双向链表查找
{
	assert(pHead);
	ListNode* ndex = pHead->_next;
	while (ndex!= pHead)
	{
		if (ndex->_data == x)
			return ndex;
		ndex = ndex->_next;
	}
	return NULL;
}

双向链表销毁

void ListDestory(ListNode* pHead)// 双向链表销毁
{
	assert(pHead);
	ListNode* Des = pHead->_next;

	while (Des != pHead)//依次释放前一个节点的内存,直到只剩头结点
	{
		Des = Des->_next;
		free(Des->_prev);
	}

    //重新初始化头节点
	pHead->_data = pHead;
	pHead->_prev = pHead;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值