数据结构->第一章->顺序表->链表

这篇文章的主要内容:了解链表的基本结构、链表的基本操作。

一、链表的基本结构

typedef int SLTDataType;
//定义链表数据域数据类型(递归定义中的递定义,因为没有归......)
typedef struct SListNode {
	SLTDataType data;//数据域
	struct SListNode* next;//指针域
}SLTNode;
//单链表节点数据域存放数据,指针域存放后继结点的指针!!!
//其实也就是一节一节的火车

这里说两点:

        1.指针域存放的是后继节点的唯一指针。在后续的操作中,要牢记这一点。(顺序问题,直接修改指针,还是先把该指针存起来)

        2.每个节点都是一个指针结构体,而不再是具体的数据类型了(有利于后续代码理解 数据类型)

二、基本操作

1.先看头文件(用来存放一些类似于‘目录’的东西)

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

typedef int SLTDataType;
//定义链表数据域数据类型
typedef struct SListNode {
	SLTDataType data;
	struct SListNode* next;
}SLTNode;
//单链表节点数据域存放数据,指针域存放后继结点的指针

void SLTPrint(SLTNode* phead);

//链表的尾插和头插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);

//链表的头删、尾删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);

//销毁链表
void SListDesTroy(SLTNode** pphead);

这里重点说明一下二级指针的用处

1.首先,我们有一个链表(物理结构),如图:

2.我们用指针变量 plist 指向头节点

通过遍历plist,我们可以找到链表的每一个节点。要修改结构体(节点),我们就用结构体指针。

本质上来说,phead就是个变量,指针变量;头节点是结构体变量。

3.如果我们要通过函数来修改链表,将 plist 的传址给函数。函数调用完之后,plist发生了什么变化?

传值呢?又有什么区别?

代码参考:

#include <stdlib.h>
typedef struct SLNode {
	int val;
	struct SLnode* next;
}SLNode;
void SLPrint(SLNode* phead) {
	SLNode* tail = phead;
	while (tail !=NULL) {
		printf("%d->", tail->val);
		tail = tail->next;
	}
	printf("NULL");
}
void SLPushFront(SLNode** phead,int x) {
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL) {
		perror("malloc fail");
		return;
	}
	newnode->val = x;
	newnode->next = NULL;
	if (*phead == NULL) {
		*phead = newnode;
		return;
	}
	newnode->next = *phead;
	*phead = newnode;
}
void SLPushFront1(SLNode* phead ) {
	phead->val = 100;
}
int main() {
	SLNode* plist=NULL;
	SLPushFront(&plist, 4);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 1);

	//SLPushFront1(plist, 100);

	SLPrint(plist);
	return 0;
}

要修改指针变量plist,我们就用plist的指针(二级指针)。

要修改结构体,我们就用结构体指针(一级指针)。


    我们再来一张图片进行加深理解

2.1具体实现对应函数的源文件

#include "SList.h"


void SLTPrint(SLTNode* phead) {
	SLTNode* pcur = phead;
	while (pcur)//等同于pcur!= NULL
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//链表的尾插和头插
void SLTPushBack(SLTNode** pphead, SLTDataType x) {
	assert(pphead);	//pphead指向不能为空
	SLTNode* newnode = SLTBuyNode(x);
	SLTNode* ptail = *pphead;

	//链表可不可以为空?
	//如果是空链表,那么尾插的结点即为头结点
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	//如果链表不是空链表,那么找到尾结点,进行尾插
	while (ptail->next != NULL)
	{
		ptail = ptail->next;
	}
	//循环完之后,ptail此时就是尾结点了。可以进行尾插了。
	//newnode->next = NULL;
	ptail->next = newnode;	
}
void SLTPushFront(SLTNode** pphead, SLTDataType x) {
	assert(pphead);

	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

//链表的尾删、头删
void SLTPopBack(SLTNode** pphead) {

	assert(pphead);//头指针地址不能为空
	//空指针,也是有地址的,并且地址不为空。
	
	assert(*pphead);//头指针不能为空

	//先找到尾结点,在找到尾结点的前驱节点。
	SLTNode* ptail = *pphead;//尾结点
	SLTNode* prev = NULL;//尾结点的前驱节点	
	//注:当单链表只有一个节点的时候,是没有前驱节点的,需要特殊处理
	
	//链表只有一个节点时:
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}

	//链表有多个节点(2个及以上)时
	while (ptail->next != NULL) {
		prev = ptail;
		ptail = ptail->next;
	}
	prev->next = NULL;
	//释放尾结点,并将尾结点的指针域置为空
	free(ptail);
	ptail= NULL;

}
void SLTPopFront(SLTNode** pphead) {
	assert(pphead);
	assert(*pphead);
	SLTNode* newnode = (*pphead)->next;
	free(*pphead);
	*pphead = newnode;
}

//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x) {
	assert(pphead);

	SLTNode* pcur = *pphead;
	while (pcur->next!= NULL)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
	assert(pphead);
	assert(*pphead);
	assert(pos);

	//如果pos是头结点,它没有前驱节点,直接头插
	if (pos == *pphead)
	{
		SLTPushFront(pphead,x);
		return;
	}

	//pos不是头节点
	SLTNode* prev = *pphead;
	while (prev->next != pos) {
		prev = prev->next;
	}
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = prev->next;
	prev->next = newnode;

}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);

	newnode->next = pos->next;
	pos->next = newnode;
}

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos) {
	assert(pos);
	assert(pphead);
	assert(*pphead);
	//如果只有一个头节点,没有前驱节点,直接头删
	if (pos == *pphead) {
		SLTPopFront(pphead);
		return;
	}
	//有2个及以上的节点,就需要找pos的前驱节点了
	SLTNode* prev = *pphead;//pos的前驱节点
	while (prev->next != pos) {
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;


}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos) {
	assert(pos);
	assert(pos->next);
	//pos pos->next pos->next->next
	//pos pos->next->next
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	//free(pos->next),为什么不行?
	free(del);
	del = NULL;

}

//销毁链表
void SListDesTroy(SLTNode** pphead) {
	assert(pphead);
	assert(*pphead);
	SLTNode* del = *pphead;
	SLTNode* delAfter = NULL;
	while (del)
	{
		delAfter = del->next;
		free(del);
		del = delAfter;
	}
	*pphead = NULL;
}

 2.2重要点:

        要考虑执行当前操作时,单链表是否为空(头插第一个节点时;在指定位置之前插入节点......)也就是说,当我们需要修改节点的前驱节点指针域时,该前驱节点是否存在?如果没有前驱节点,那么问题就比较简单;如果有前驱节点,那么需要注意顺序问题。

        什么是顺序问题?就如文章开头所说:每个节点的指针域,存放的都是后继节点的唯一指针,在修改指针域时,是否会使之前的指向丢失?

SLTNode* del = pos->next;
	pos->next = pos->next->next;
	//free(pos->next),为什么不行?
	free(del);
	del = NULL;

举个例子,以上代码,为什么需要新节点del?为什么不能直接free?

3.用于测试的源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void test0()
{
	SLTNode* plist = NULL;
	SLTPrint(plist);
	//尾插测试
	SLTPushBack(&plist, 1);
	SLTPrint(plist);
	SLTPushBack(&plist, 2);
	SLTPrint(plist);
	SLTPushBack(&plist, 3);
	SLTPrint(plist);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
	printf("\n");

	//头插测试
	//SLTPushFront(&plist, 200);
	//SLTPrint(plist);
	//SLTPushFront(&plist, 0);
	//SLTPrint(plist);
	
	//尾删
	//SLTPopBack(&plist);
	//SLTPrint(plist);
	//SLTPopBack(&plist);
	//SLTPrint(plist);	

	//头删
	/*SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);*/

}
void test1()
{
	SLTNode* plist = NULL;
	SLTPrint(plist);
	//尾插测试
	SLTPushBack(&plist, 1);
	SLTPrint(plist);
	SLTPushBack(&plist, 2);
	SLTPrint(plist);
	SLTPushBack(&plist, 3);
	SLTPrint(plist);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
	printf("\n");

	SLTNode* FindRet = SLTFind(&plist, 2);
	if (FindRet) {
		printf("找到了\n");
	}
	else {
		printf("未找到\n");
	}
	SLTInsert(&plist, FindRet, 100);
	SLTPrint(plist);
	SLTInsertAfter(FindRet, 000);
	SLTPrint(plist);

	SLTEraseAfter(FindRet);
	SLTPrint(plist);
	SLTErase(&plist, FindRet);
	SLTPrint(plist);
	//SListDesTroy(&plist);
	//SLTPrint(plist);

}
int main()
{
	//test0();
	test1();
	return 0;
}

三、总结:

1.单链表最重要的是指针域的修改。

2.要修改什么,就用什么的地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值