【数据结构】单链表

完整代码在码云仓库:

C++数据结构: 本仓库存放C++数据结构代码https://gitee.com/j-jun-jie/c-data-structure.git

1.顺序表与链表的优劣

  • 顺序表

优点:1物理空间连续,2.可以实现下标随机访问。

缺点:1.空间不够要扩容,所以有一定空间浪费和空间消耗。

2.头部或中间位置插入删除效率低。

改善方案:1.按需申请空间,需要一个空间开辟一个。

2.头部,中间插入删除不挪动数据。

——————链表

  • 链表

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

typedef int SLTDateType;

struct SListNode
{
	SLTDateType date;  //数据个数
	struct SListNode* next;   //指向下一个结点的指针
};

这是在头文件区域定义的,上面的#pragma是防止头文件重复的,下面的结构体是链表的主体。

先写四个节点

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

void TestSList1()
{
	SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n1);
	SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n2);
	SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n3);
	SLTNode* n4 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n4);

	n1->date = 1;
	n2->date = 2;
	n3->date = 3;
	n4->date = 4;

	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = NULL;

}
int main()
{
	TestSList1();

	return 0;
}

next中存的是下一个数据的地址,最后一个数据的next中存的是NULL。

//打印
void SListPrint(SLTNode* phead)
{
	//phead存首元素地址
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->date);
		cur = cur->next;   //指向下一个
	}
	printf("NULL\n");
}

 为了方便,先写一个打印函数。

2.单链表的打印

//打印
void SListPrint(SLTNode* phead)
{
	//phead存首元素地址
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->date);
		cur = cur->next;   //指向下一个
	}
	printf("NULL\n");
}

phead中存的是首元素地址,要不然一会打印不能从头开始,所以又创建个新变量cur。

中间这个->是咱自己在printf中加的。

 3.尾插实现       

//尾插
void SListPushBack(SLTNode* phead, SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->date = x;
	newnode->next = NULL;
	//找节点
	SLTNode* tail = phead;
	while (tail->next != NULL)
	{
		tail = tail->next;
	}
	tail->next = newnode;
}

 思路:新开辟一个空间,这个空间分两部分,一部分用来存值x,一部分用来存地址,但是在最后所以存NULL。接下来我要去空间中去找最后的地址,所以又创建个tail。

那如果是空指针实现尾插呢?

很明显,程序直接崩掉了,所以尾插代码还不完善。

//尾插
void SListPushBack(SLTNode* phead, SLTDateType x)
{
	
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);
	newnode->date = x;
	newnode->next = NULL;
	//先判断phead是否为空
	if (phead == NULL)
	{
		phead = newnode;
	}
	else
	{
		//找节点
	   SLTNode* tail = phead; 
	   while (tail->next != NULL)
	   {
		   tail = tail->next;
	   }
	    tail->next = newnode;

	}
	
}

 但是打印结果是NULL(不在粘贴图了),为啥呢?

3.2二级指针

形参是实参的一份临时拷贝,要改变形参,就要传实参的地址,同类,我要改变指针,那我就要传指针的地址,指针是一份地址,指针的地址又是一份地址,所以用到二级指针。

咱们要做的是把NULL改成要插入元素的地址,所以用到二级指针,而不仅仅改变数值。

//尾插
void SListPushBack(SLTNode** pphead, SLTDateType x)
{
	
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);
	newnode->date = x;
	newnode->next = NULL;
	//先判断phead是否为空
	if (*pphead == NULL)
	{
		*pphead = newnode;
		//*pphead代表的是地址,而不是解引用
	}
	else
	{
		//找节点
	   SLTNode* tail = *pphead; 
	   while (tail->next != NULL)
	   {
		   tail = tail->next;
	   }
	    tail->next = newnode;

	}
	
}

 print函数可以不传,因为它不需要改变数值。

4.头插

4.1节点函数

每次插入都要开辟节点,那不妨创建一个节点函数

//开辟空间
SLTNode* BuySListNode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);
	newnode->date = x;
	newnode->next = NULL;
	return newnode;
}

4.2头插

好好理解一下兄弟们。

5.头删

//头删
void SListPopFront(SLTNode** pphead)
{
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

 这个很好理解,我先找一个寄存器存下一个数的地址,然后free掉,我再把寄存器中的地址传给*pphead然后通过地址直接访问的是原来的一串数的第二个,因为我找的地址就是第二个数的,所以第一个数就被free了。

尾删两次。注意一定要判空。

6.尾删

//尾删
void SListPopBack(SLTNode** pphead)
{
	SLTNode* tail = *pphead;
	SLTNode* tailPrve = NULL;
	while (tail->next != NULL)//前提是tail不是空
	{
		tailPrve = tail;
		tail = tail->next;
	}
	free(tail);
	tailPrve->next = NULL;
} 

当tail访问最后一个是,就不进入while循环了,所以tailPrve是倒数第二个值。

7.某位置插入/删除

//某处添加
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pos && *pphead!=NULL);
	
	if (*pphead == pos)
	{
		SListPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		SLTNode* newnode = BuySListNode(x); //开辟节点
		while (prev->next != pos);
		{
			prev = prev->next;
		}
		prev->next = newnode;  //把newnode接在prev上,再把pos和newnode接在一起
		newnode->next = pos;
	}
}
  •  在某位置删除
//某处删除
void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos && pphead);

	if (*pphead == pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;

		while (prev->next != NULL)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

9.在pos后面添加和删除

//pos后添加
void SListInsertafter(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pos);
	SLTNode* newnode = BuySListNode(x); //开辟节点
	SLTNode* next = pos->next;
	pos->next = newnode;
	newnode->next = next; //不用考虑顺序
}

//pos后删除
void SListEraseafter(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	if (pos->next == NULL) //最后了就不需要删除了
		return;
	SLTNode* del = pos->next;    //正好把中间那个隔离
	pos->next = del->next;
	free(del);
	del = NULL;
}

由于时间有限,最后几个就不在解释了,有不太理解的兄弟们私信我。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值