不带头结点的单链表的实现

不带头结点的单链表的实现

  • 为什么会有单链表这种数据结构的出现,是因为顺序表本身其实是存在有一些问题的,就比如顺序表在中间或者头部的插入和删除的操作的时间复杂度是O(N);还比如顺序表的空间是动态开辟的,所以需要申请空间,拷贝元素,释放旧空间,整体的消耗也是比较大的,同时扩容的时候存在一定浪费空间的可能性,所以综上所述,顺序表这种数据结构存在一定的缺陷
链表的概念
  • 概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
  • 下图可以看出逻辑上是一定连续的,但是物理上不一定是连续的,现实中的结点一般都是从堆上申请出来的,从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
    在这里插入图片描述
    在这里插入图片描述
  • 我们主要看这两种链表:
  1. 不带头结点的单链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等
  2. 带头结点的双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了
    在这里插入图片描述
写法一,不适用二级指针
  • 下面的这种对于单链表的实现,一个好处就是不用写成二级指针的写法
SList.h
//不带头结点的单链表   
//不带头结点的话,那么链表表中的第一个节点一顶存储的是有效元素

#pragma once  //保证头文件不被重复包含


#include<assert.h>
#include<stdlib.h>
#include<stdio.h>
//如果想要对链表进行操作,那么首先需要一个结点的结构体

typedef int SDataType;

typedef struct SListNode
{
	SDataType data;
	struct SListNode* _pNext;
}Node;

//为了操作简单,在把链表的结构给出来
//如果想要找到一个链表,那么只需要知道链表的头节点就可以了
typedef struct SList
{
	Node* _pHead;
}SList;

//初始化
void SListInit(SList* pl);

//尾插
void SListPushBack(SList* pl,SDataType data);

//尾删
void SListPopBack(SList* pl);

//销毁
void SListDestroy(SList* pl);

//打印
void PrintSList(SList* pl);

//测试
void TestList();

//头插
void SListPushFront(SList* pl, SDataType data);

//头删
void SListPopFront(SList* pl);

//任意位置的插入
void SListInsert(Node* pos, SDataType data);

//任意位置的删除
void SListErase(SList* pl,Node* pos);   //需要遍历去找到pos的前一个位置

//查找
Node* SListFind(SList* pl, SDataType data);

//个数
int SListSize(SList* pl);

//是否为空
int SListEmpty(SList* pl);

//返回第一个结点
Node* SListFront(SList* pl);

//返回最后一个结点
Node* SListBack(SList* pl);

SList.c
#include"单链表.h"

void SListInit(SList* pl)
{
	assert(pl);
	//初始化的时候,链表中一个结点都没有
	//所以让头节点指向空就可以了
	pl->_pHead = NULL;
}

Node* BuySListNode(SDataType data)
{
	Node* pNode = (Node*)malloc(sizeof(Node));
	//判断是否申请空间成功
	if (NULL == pNode)
	{
		assert(0);
		return NULL;
	}
	pNode->data = data;
	pNode->_pNext = NULL;
	return pNode;
}

void SListPushBack(SList* pl, SDataType data)
{
	//用来遍历的结点
	Node* pCur = NULL;
	//先需要构造出来一个结点
	//链表中的结点是从堆上new出来的
	Node* pNewNode = NULL;    //先将新的结点的赋值成空值
	//紧接着,判断链表是否存在,如果存在的话,我们给出结点
	assert(pl);    //保证链表是存在的 
	//给出结点
	pNewNode = BuySListNode(data);
	pCur = pl->_pHead;
	//空链表
	if (NULL == pl->_pHead)
		pl->_pHead = pNewNode;
	else
	{
		while (pCur->_pNext)
		{
			pCur = pCur->_pNext;
		}
		pCur->_pNext = pNewNode;
		pNewNode->_pNext = NULL;
	}
}

void PrintSList(SList* pl)
{
	Node* pCur = NULL;
	assert(pl);
	pCur = pl->_pHead;
	while (pCur)
	{
		printf("%d--->", pCur->data);
		pCur = pCur->_pNext;
	}
	printf("NULL\n");
}

void SListDestroy(SList* pl)
{
	Node* pCur = NULL;
	assert(pl);
	pCur = pl->_pHead;
	while (pCur)
	{
		pl->_pHead = pCur->_pNext;
		free(pCur);
		pCur = pl->_pHead;
	}
	pl->_pHead = NULL;
}

void SListPopBack(SList* pl)
{
	Node* pCur = NULL;
	//找链表中的倒数第二个结点    链表至少有两个结点
	assert(pl);
	if (NULL == pl->_pHead)
	{
		return;
	}
	else if (NULL == pl->_pHead->_pNext)
	{
		free(pl->_pHead);
		pl->_pHead = NULL;
	}
	else
	{
		//至少有两个节点
		Node* pTailNode = pl->_pHead;
		while (pTailNode->_pNext->_pNext)
		{
			pTailNode = pTailNode->_pNext;
		}
		free(pTailNode->_pNext);
		pTailNode->_pNext = NULL;
	}
}

void SListPushFront(SList* pl, SDataType data)
{
	Node* pNewNode = NULL;
	assert(pl);
	pNewNode = BuySListNode(data);
	pNewNode->_pNext = pl->_pHead;
	pl->_pHead = pNewNode;
}

void SListPopFront(SList* pl)
{
	assert(pl);
	if (NULL == pl->_pHead)
		return;
	else
	{
		Node* pDelNode = pl->_pHead;
		pl->_pHead = pDelNode->_pNext;
		free(pDelNode);
	}
}

void SListInsert(Node* pos, SDataType data)
{
	Node* pNewNode = NULL;
	//只能忘这个位置的后面插入
	if (NULL == pos)
		return;
	pNewNode = BuySListNode(data);
	pNewNode->_pNext = pos->_pNext;
	pos->_pNext = pNewNode;
}

void SListErase(SList* pl,Node* pos)
{
	Node* pPrePos = NULL;
	assert(pl);
	if (NULL == pl->_pHead || NULL == pos)
		return;
	if (pos == pl->_pHead)
	{
		pl->_pHead = pos->_pNext;
		free(pos);
		return;
	}
	pPrePos = pl->_pHead;
	while (pPrePos->_pNext != pos)
	{
		pPrePos = pPrePos->_pNext;
	}
	pPrePos->_pNext = pos->_pNext;
	free(pos);
}

Node* SListFind(SList* pl, SDataType data)
{
	Node* pCur = pl->_pHead;
	assert(pl);
	while (pCur)
	{
		if (pCur->data == data)
			return pCur;
		pCur = pCur->_pNext;
	}
	return NULL;
}

int SListSize(SList* pl)
{
	int count = 0;
	Node* pCur = NULL;
	assert(pl);
	pCur = pl->_pHead;
	while (pCur)
	{
		count++;
		pCur = pCur->_pNext;
	}
	return count;
}

int SListEmpty(SList* pl)
{
	assert(pl);
	retrun NULL == pl->_pHead;
}

Node* SListFront(SList* pl)
{
	assert(pl);
	return pl->_pHead;
}

//必须保证有结点才可以
Node* SListBack(SList* pl)
{
	Node* pCur = NULL;
	assert(pl);
	pCur = pl->_pHead;
	while (pCur->_pNext)
	{
		pCur = pCur->_pNext;
	}
	return pCur;
}
main.c
#include"单链表.h"

void TestList1()
{
	SList s;  //创建一个对象

	SListInit(&s);

	SListPushBack(&s, 1);
	SListPushBack(&s, 2);
	SListPushBack(&s, 3);
	SListPushBack(&s, 4);
	SListPushBack(&s, 5);

	PrintSList(&s);

	SListPopBack(&s);

	PrintSList(&s);

	SListPushFront(&s, 10);
	SListPushFront(&s, 20);
	SListPushFront(&s, 30);
	SListPushFront(&s, 40);
	SListPushFront(&s, 50);

	PrintSList(&s);

	SListPopFront(&s);
	SListPopFront(&s);

	PrintSList(&s);

	SListInsert(SListFind(&s, 2),66);

	PrintSList(&s);

	SListErase(&s, SListFind(&s, 66));

	PrintSList(&s);

	SListDestroy(&s);
}

void TestList()
{
	TestList1();
}

int main()
{
	TestList();
	return 0;
}
写法二:使用二级指针
  • 这里为什么要使用二级指针呢,因为对于单链表来说,在链表进行插入删除的操作的时候,在很大的可能上是会修改链表头元素的,这个时候,如果只是用一级指针的话,那么就无法达到通过形参修改实际参数的目的了,所以说这个时候我们就需要使用二级指针了,代码如下所示
List.h
#pragma once


#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>


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


void ListDestroy(Node** head);

void ListPrint(Node* head);

void ListPushFront(Node** head,int data);

void ListPushBack(Node** head, int data);

void ListPopFront(Node** head);

void ListPopBack(Node** head);

Node* ListFind(Node* head, int data);
List.c
#include"List.h"


void ListDestroy(Node** head)
{
	Node* cur = *head;
	while (cur)
	{
		*head = cur->next;
		free(cur);
		cur = *head;
	}
	*head = NULL;
}

//打印
//这里也不需要断言,因为空链表其实也是可以进行打印操作的
void ListPrint(Node* head)
{
	Node* cur = head;
	while (cur)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

Node* BuyListNode(int data)
{
	Node* pNewNode = (Node*)malloc(sizeof(Node));
	if (NULL == pNewNode)
	{
		return NULL;
	}
	pNewNode->data = data;
	pNewNode->next = NULL;
	return pNewNode;
}

//头插
void ListPushFront(Node** head, int data)
{
	//head这里不用断言,因为head其实永远都不可能是空的
	assert(head);//链表为空head也不为空,因为head是头指针的地址,所以是需要断言的
	//*head不需要断言,因为链表为空的时候也是可以进行数据插入的
	Node* pNewNode = BuyListNode(data);
	/*if (NULL == *head)
	{
		*head = pNewNode;
		pNewNode->next = NULL;
	}
	else
	{
		pNewNode->next = *head;
		*head= pNewNode;
	}*/
	pNewNode->next = *head;
	*head = pNewNode;
}

//尾插,在链表为空的前提下进行尾插,就一定要使用二级指针,因为在链表为空的情况下
//进行尾插的时候,这个时候一定是会改变头指针的指向的,所以我们需要使用二级指针
void ListPushBack(Node** head, int data)
{
	Node* pNewNode = BuyListNode(data);
	if (NULL == *head)
	{
		*head = pNewNode;
	}
	else
	{
		Node* cur = *head;
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = pNewNode;
	}
}

//头删,和上面的思路一样,在进行头删的时候也需要传递二级指针
void ListPopFront(Node** head)
{
	assert(head);
	assert(*head);//当链表为空的时候,不能头删,所以要断言
	if (NULL == *head)
	{
		assert(*head);
	}
	else if (NULL == (*head)->next)
	{
		free(*head);
		*head = NULL;
	}
	else
	{
		Node* cur = *head;
		*head = (*head)->next;
		free(cur);
		cur = *head;
	}
}

//尾删
void ListPopBack(Node** head)
{
	if (NULL == *head)
	{
		assert(*head);
	}
	else if (NULL == (*head)->next)
	{
		free(*head);
		*head = NULL;
	}
	else
	{
		Node* cur = *head;
		while (cur->next->next)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}

Node* ListFind(Node* head, int data)
{
	//这里不用断言,因为空的链表也是可以进行查找的,所以是否为空其实是不影响的
	Node* cur = head;
	while (cur)
	{
		if (cur->data == data)
			return cur;
		cur = cur->next;
	}
	return NULL;
}
main.c
#include"List.h"


int main()
{
	Node* p = NULL;

	ListPushFront(&p, 10);
	ListPushFront(&p, 20);
	ListPushFront(&p, 30);
	ListPushFront(&p, 40);
	ListPushFront(&p, 50);

	ListPrint(p);

	ListPushBack(&p, 1);
	ListPushBack(&p, 2);
	ListPushBack(&p, 3);

	ListPrint(p);

	ListPopBack(&p);
	ListPopBack(&p);

	ListPrint(p);

	ListPopFront(&p);
	ListPopFront(&p);

	ListPrint(p);


	//ListDestroy(&p);
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值