단일 연결 리스트(데이터 구조)(C 언어)

이것은 특별히 센트리 비트가 아닌 단방향 비순환 연결 목록을 나타냅니다.

목차

배경

개념

단일 연결 리스트 구현

전망 힌트

단일 연결 리스트 구조 정의

단일 연결 리스트 출력

phead가 주장되지 않는 이유에 대해

단일 연결 리스트의 논리적 구조와 물리적 구조에 대하여

단일 연결 목록의 꼬리 삽입

보조 포인터가 사용되는 이유

테일 플러그의 성질에 대해

꼬리 찾기의 전 과정에 대한 설명

단일 연결 목록을 인쇄하기 위해 보조 포인터를 전달할 필요가 없는 이유에 대해

단일 연결 목록의 동적 애플리케이션 노드

단일 연결 목록 헤드 플러그

단일 연결 리스트의 꼬리 삭제

단일 연결 리스트의 헤드 삭제

연결된 목록 조회

단일 연결 목록은 pos 위치 앞에 x를 삽입합니다(pos 위치에 삽입하는 것으로 이해될 수도 있음).

단일 연결 리스트는 pos 위치 앞의 값을 삭제합니다(pos 위치의 값을 삭제하는 것으로 이해하기도 함).

단일 연결 목록은 pos 위치 뒤에 x를 삽입합니다. 

단일 연결 목록은 pos 위치 뒤의 값을 삭제합니다.

헤더 포인터를 전달하지 않고 pos 앞에 삽입/삭제하는 방법에 대해(독창적)

단일 연결 목록 파괴

전체 코드(결과를 직접 보고 싶다면 여기에서 볼 수 있음)


배경

지난 기사에서 시퀀스 테이블에 대해 배웠습니다.

그러나 순차 테이블은 연속적인 물리적 공간을 필요로 하기 때문에 다음과 같은 단점이 있다.

1. 중간/헤드에서 삽입 및 삭제, 시간 복잡도는 O(N)입니다.
2. 용량을 늘리려면 새로운 공간을 신청하고 데이터를 복사하고 오래된 공간을 해제해야 합니다. 많은 소비가있을 것입니다.
3. 용량의 증가는 일반적으로 2배의 증가이며 일정량의 공간 낭비가 있을 수밖에 없습니다. 예를 들어, 현재 용량은 100이고 가득 차면 200으로 늘어납니다. 계속해서 5개의 데이터를 더 삽입하고 나중에 데이터를 추가하지 않으므로 95개의 데이터 공간이 낭비됩니다.
위의 문제를 더 잘 해결하기 위해 연결 목록을 확장했습니다.

개념

연결 리스트 는 물리적 저장 구조의 비순차적 이고 비순차적인 저장 구조 이며, 데이터 요소의 논리적 순서 는 연결 리스트의 포인터 구현된다 . 연결 리스트의 데이터 한 조각은 하나의 메모리 블록에 저장됩니다. 연결된 목록은 일련의 노드(연결된 목록의 각 요소를 노드라고 함)로 구성되며 노드는 런타임에 동적으로 생성될 수 있습니다. 각 노드는 두 부분으로 구성됩니다. 하나는 데이터 요소를 저장하는 데이터 이고 다른 하나는 다음 노드의 주소를 저장하는 포인터 .
단일 연결 목록은 선형 목록에 데이터 요소를 저장하기 위해 임의의 주소가 있는 저장 장치 그룹(이 저장 장치 그룹은 연속적이거나 불연속적일 수 있음)을 사용 하는 체인 액세스 데이터 구조입니다 . 연결된 목록의 데이터는 노드로 표시 됩니다 ( 각 노드는 두 부분으로 구성됩니다. 하나는 데이터 요소를 저장하기 위한 데이터 필드이고 다른 하나는 다음 노드의 주소를 저장하기 위한 포인터 필드입니다. ), 각 노드 구성: 요소 +포인터에서 요소는 데이터를 저장하는 저장 이고 포인터는 각 노드를 연결하는 주소 .
단일 연결 리스트의 각 노드의 저장 주소는 선행 노드의 다음 필드에 저장되며 시작 노드에는 선행 노드가 없으므로 헤드 포인터 헤드는 시작 노드를 가리키도록 설정됩니다. 연결 리스트는 헤드 포인터에 의해 유일하게 , 단일 연결 리스트는 헤드 포인터의 이름으로 명명될 수 있다.종단 노드는 계승자가 없기 때문에 종단 노드의 포인터 필드는 비어 있다, 즉 NULL이다.

단일 연결 리스트 구현

전망 힌트

SList.h는   참조 헤더 파일, 단일 연결 목록의 정의 및 함수 선언에 사용됩니다.

SList.c는   함수 정의에 사용됩니다.

Test.c는     링크드 리스트 기능 테스트에 사용됩니다.

단일 연결 리스트 구조 정의

SList.h 아래

#pragma once//使同一个文件不会被包含(include)多次,不必担心宏名冲突

//先将可能使用到的头文件写上
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDataType;//假设结点的数据域类型为 int

//单链表的结构体定义
typedef struct SListNode
{
	SLTDataType data;//结点的数据域,用来存储数据元素
	struct SListNode* next;//结点的指针域,用来存储下一个结点地址的指针域
    //next的意思就是下一个结点的指针,上一个结点存的是下一个结点的地址
    //每个结点都是结构体指针类型
	//有些人会把上一行代码写成SListNode* next;这是不对的,因为C语言中 
    //struct SListNode 整体才是一个类型(但C++可以)
	//或者写成SLTNode* next;这也是错的,因为编译器的查找规则是从上忘下找
}SLTNode;

단일 연결 리스트 출력

단일 연결 목록을 이해하기 위해 먼저 단일 연결 목록의 출력물을 작성합니다.

SList.h 아래

//链表的打印——助于理解链表
void SLTPrint(SLTNode* phead);
SList.c에서
#include "SList.h"//别忘了

//链表的打印
void SLTPrint(SLTNode* phead)
{
	//assert(phead);这里并不需要断言phead不为空
	//为什么这里不需要断言phead?
	//空链表可以打印,即phead==NULL可以打印,直接断言就不合适了
	//那空顺序表也可以打印,那它为什么就要断言呢?
	//因为phead是指向第一个存有数据的结点的
	//而顺序表的ps是指向一个结构体
	SLTNode* cur = phead;//将phead赋值给cur,所以cur也指向第一个结点
	while (cur != NULL)//或while(cur)
	{
		printf("%d->", cur->data);//打印的时候加了个箭头更方便理解
		cur = cur->next;//next是下一个结点的地址
		//++cur/cur++;这种是不行的,指针加加,加到的是连续的下一个位置
		//链表的每一个结点都是单独malloc出来的,我们不能保证结点之间的地址是连续的
	}
	printf("NULL\n");
}

phead가 주장되지 않는 이유에 대해

단일 연결 리스트의 논리적 구조와 물리적 구조에 대하여

                     

                     

                   

                     

인쇄하기 전에 데이터가 있어야 합니다.

단일 연결 목록의 꼬리 삽입

SList.h 아래

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//用二级指针,解释看下文,x为要插入的数据

보조 포인터가 사용되는 이유

SList.c에서

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//pphead是plist的地址,不能为空
    //注意区分几个断言的判断,plist有可能是空,pphead一定不能为空

	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//先创建个结点
	if (newnode == NULL)//如果malloc失败
	{
		perror("malloc fail");
		return;
	}
	//如果malloc成功
	newnode->data = x;//插入的数据
	newnode->next = NULL;//初始化为空
	//找尾(尾插之前先找到尾)
	if (*pphead == NULL)//若链表为空
	{
		*pphead = newnode;
	}
	else//若链表不为空
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)//对于不为空的链表:尾插的本质
                                  //是原尾结点要存新尾结点的地址
		{
			tail = tail->next;
		}
		tail->next = newnode;
		/*有些同学会写成:
		while (tail != NULL)
		{
			tail = tail->next;
		}
		tail = newnode;*/
	}
}

테일 플러그의 성질에 대해

 

 그리고

꼬리 찾기의 전 과정에 대한 설명

 

 ↓

Test.c에서

#include "SList.h"//别忘了

//用于函数功能的测试
void TestSList1()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);

	SLTPrint(plist);
}

int main()
{
	TestSList1();
	return 0;
}

단일 연결 목록을 인쇄하기 위해 보조 포인터를 전달할 필요가 없는 이유에 대해

단일 연결 목록을 인쇄해도 포인터가 변경되지 않기 때문입니다. 전달된 포인터(실제 매개변수)를 변경하려면 실제 매개변수의 주소를 전달해야 하며 변경되지 않으면 전달하지 마십시오.

단일 연결 목록의 동적 애플리케이션 노드

헤드 플러그를 작성할 때 테일 플러그와 헤드 플러그 모두 노드에 대해 동적으로 적용해야 하므로 먼저 재사용할 함수를 작성할 수 있습니다.

SList.c에서

// 动态申请一个结点
SLTNode* BuySLTNode(SLTDataType x)
{
	//同样不需要断言
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//先创建个结点
	if (newnode == NULL)//如果malloc失败
	{
		perror("malloc fail");
		return NULL;
	}
	//如果malloc成功
	newnode->data = x;//插入的数据
	newnode->next = NULL;//初始化为空

	return newnode;//返回newnode
}

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);//pphead是plist的地址,不能为空

	SLTNode* newnode = BuySLTNode(x);
	//找尾(尾插之前先找到尾)
	if (*pphead == NULL)//若链表为空
	{
		*pphead = newnode;
	}
	else//若链表不为空
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
        //对于不为空的链表:尾插的本质是原尾结点要存新尾结点的地址
		{
			tail = tail->next;
		}
		tail->next = newnode;
		/*有些同学会写成:
		while (tail != NULL)
		{
			tail = tail->next;
		}
		tail = newnode;*/
	}
}

단일 연결 목록 헤드 플러그

 SList.h 아래

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

SList.c에서

// 单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	//发现plist不管是否为空,头插的方法都一样
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

Test.c에서

void TestSList1()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);

	SLTPrint(plist);
}

void TestSList2()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);

	SLTPrint(plist);
}


int main()
{
	TestSList1();
	TestSList2();
	return 0;
}

단일 연결 리스트의 꼬리 삭제

꼬리 삭제에 보조 포인터가 필요합니까? 원하다!

SList.h 아래

// 单链表的尾删
void SLTPopBack(SLTNode** pphead);

Test.c에서

void TestSList2()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);
}


int main()
{
	TestSList2();
	return 0;
}

SList.c에서

어떤 사람들은 다음과 같이 작성하여 시작합니다.

// 单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
    assert(pphead);//pphead是plist的地址,不能为空

	SLTNode* tail = *pphead;
	while (tail->next != NULL)
	{
		tail = tail->next;
	}
	
	free(tail);
	tail = NULL;
}

밝혀지다:

임의의 값이 나타납니다 --> 대부분 와일드 포인터 때문입니다.

 

 왜?

다음은 변경된 SList.c에 대한 두 가지 방법입니다.

방법 1:

// 单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);//pphead是plist的地址,不能为空

    //法一:
	SLTNode* prev=NULL;
	SLTNode* tail = *pphead;
	while (tail->next != NULL)
	{ 
		prev = tail;
		tail = tail->next;
	}
	
	free(tail);
	tail = NULL;
	
	prev->next = NULL;
}

법2:

// 单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);//pphead是plist的地址,不能为空

	//法二:
	SLTNode* tail = *pphead;
	while (tail->next->next != NULL)
	{
		tail = tail->next;
	}

	free(tail->next);
	tail->next = NULL;
}

 하지만 몇 가지 세트를 더 테스트해 보겠습니다.

Test.c에서

void TestSList2()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

    //尾删四个数据
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
}


int main()
{
	TestSList2();
	return 0;
}

결과:

두 가지 방법 중 마지막에 남은 것은 단 하나!

그 이유는 노드가 하나뿐이거나 노드가 없는 경우를 고려하지 않기 때문입니다.

다시 변경한 후 SList.c는 다음과 같습니다.

// 单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);//pphead是plist的地址,不能为空

	//检查有无结点
	assert(*pphead != NULL);
	//1.只有一个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//2.有多个结点
		/*//法一:
		SLTNode* prev=NULL;
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;

		prev->next = NULL;*/
		//法二:
		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}

		free(tail->next);
		tail->next = NULL;
	}
}

단 하나의 노드

노드 없음 

단일 연결 리스트의 헤드 삭제

SList.h 아래

// 单链表头删
void SLTPopFront(SLTNode** pphead);

SList.c에서

// 单链表头删
void SLTPopFront(SLTNode** pphead)
{
	//检查有无结点
	assert(*pphead != NULL);

	SLTNode* first = *pphead;
	*pphead = first->next;
	free(first);
	first = NULL;
}

Test.c에서

TestSList3()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
}

int main()
{
	TestSList3();
	return 0;
}

연결된 목록 조회

SList.h 아래

// 单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

SList.c에서

// 单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;//用cur去遍历,不用phead
	while (cur)//找x
	{
		if (cur->data == x)//如果找到了
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;//如果找不到
}

Test.c에서

void TestSList4()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	//将寻找和修改结合
	//eg:值为2的结点*2
	SLTNode* ret = SLTFind(plist, 2);
	ret->data *= 2;
	SLTPrint(plist);
}


int main()
{
	TestSList4();
	return 0;
}

단일 연결 목록은 pos 위치 앞에 x를 삽입합니다(pos 위치에 삽입하는 것으로 이해될 수도 있음).

SList.h 아래

//单链表在pos位置之前插入x(效率较低)(得传头指针)(也可以理解为在pos位置插入)
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x);

SList.c에서

//单链表在pos位置之前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);//pphead是plist的地址,不能为空

	assert(pos);//默认pos一定会找到
	if (pos == *pphead)//如果pos在第一个位置——那就是头插
	{
		SLTPushFront(pphead, x);
	}
	else//如果pos不是第一个位置
	{
		//找到pos的前一个位置
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

Test.c에서

TestSList5()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	//寻找值为2的结点
	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertBefore(&plist, ret, 20);//在该结点前插入值为20的结点
	SLTPrint(plist);

}

int main()
{
	TestSList5();
	return 0;
}

단일 연결 리스트는 pos 위치 앞의 값을 삭제합니다(pos 위치의 값을 삭제하는 것으로 이해하기도 함).

SList.h 아래

// 单链表删除pos位置之前的值(效率较低)(得传头指针)(也可以理解为删除pos位置的值)
void SLTEraseBefore(SLTNode** pphead, SLTNode* pos);

SList.c에서

// 单链表删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)//如果pos在第一个位置
	{
		SLTPopFront(pphead);//头删
	}
	else//如果不在第一个位置
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		//pos = NULL;形参的改变不影响实参,加不加这句话都可以
	}
}

Test.c에서

TestSList6()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	//寻找值为2的结点
	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseBefore(&plist, ret);
	ret = NULL;//一般在这里置空
	SLTPrint(plist);
}

int main()
{
	TestSList6();
	return 0;
}

단일 연결 목록은 pos 위치 뒤에 x를 삽입합니다. 

SList.h 아래

// 单链表在pos位置之后插入x,单链表比较适合这种
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

SList.c에서

어떤 사람들은 다음과 같이 쓸 것입니다.

//单链表在pos位置之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	pos->next = newnode;
	newnode->next=pos->next;
}

결과:

 그래서 주황색과 보라색 두 걸음은 자리를 바꿔야

변경된 SList.c

//单链表在pos位置之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

Test.c에서

TestSList7()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertAfter(ret, 20);
	SLTPrint(plist);
}

int main()
{
	TestSList7();
	return 0;
}

단일 연결 목록은 pos 위치 뒤의 값을 삭제합니다.

SList.h 아래

// 单链表删除pos位置之后的值,单链表比较适合这种
void SLTEraseAfter(SLTNode* pos);

SList.c에서

// 单链表删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLTNode* del = pos->next;//保存要删除的结点
	pos->next = pos->next->next;//或者写成pos->next=del->next;
	free(del);
	del = NULL;
}

Test.c에서

TestSList8()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseAfter(ret);
	SLTPrint(plist);
}

int main()
{
	TestSList8();
	return 0;
}

헤더 포인터를 전달하지 않고 pos 앞에 삽입/삭제하는 방법에 대해(독창적)

삽입 : 먼저 단일 연결 목록을 사용하여 pos의 위치 뒤에 x의 함수를 삽입한 다음 pos와 pos->next의 값을 교환합니다.

SList.h 아래

// 不传头指针,在pos前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore1(SLTNode* pos, SLTDataType x);

SList.c에서

// 不传头指针,在pos前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore1(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	//调用单链表在pos位置之后插入x的函数
	SLTInsertAfter(pos, x);
	
	//交换pos和pos->next的值
	SLTDataType temp;
	temp = pos->data;
	pos->data = pos->next->data;
	pos->next->data = temp;
}

Test.c에서

TestSList9()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertBefore1(ret,20);
	SLTPrint(plist);
}

int main()
{
	TestSList9();
	return 0;
}

삭제 : 먼저 pos->next의 값을 pos에 할당한 후 단일 연결 리스트를 사용하여 pos 위치 이후의 함수를 삭제합니다. ( 단, 이 방법은 tail-delete 불가 )

SList.h 아래

// 不传头指针,删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore1(SLTNode* pos);

SList.c에서

// 不传头指针,删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore1(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);//不能尾删
	
	SLTNode* del = pos->next;
	pos->data = pos->next->data;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

Test.c에서

TestSList10()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseBefore1(ret);
	SLTPrint(plist);
}

int main()
{
	TestSList10();
	return 0;
}

단일 연결 목록 파괴

방법 1(보조 포인터를 전달하지 않음):

SList.h 아래

// 单链表的销毁,不传二级
void SLTDestroy(SLTNode* phead);

SList.c에서

// 单链表的销毁
void SLTDestroy(SLTNode* phead)
{
	SLTNode* cur = phead;
	/*//有些人一开始会这样写
	while (cur)
	{
		//free不是销毁这个指针指向的内存,而是将指针指向的内存还给操作系统
		free(cur);//cur依旧指向free之前的地址
		cur = cur->next;
	}*/

	while (cur)
	{
		SLTNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
}

Test.c에서

TestSList11()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTDestroy(plist);
	plist = NULL;
}


int main()
{
	TestSList11();
	return 0;
}

방법 2(보조 포인터 전달):

SList.h 아래

// 单链表的销毁,传二级
void SLTDestroy1(SLTNode** pphead);

SList.c에서

// 单链表的销毁,传二级
void SLTDestroy1(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
	*pphead = NULL;
}

Test.c에서

TestSList12()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTDestroy1(&plist);
	SLTPrint(plist);
}

int main()
{
	TestSList12();
	return 0;
}

전체 코드(결과를 직접 보고 싶다면 여기에서 볼 수 있음)

SList.h 아래

#pragma once//使同一个文件不会被包含(include)多次,不必担心宏名冲突

//先将可能使用到的头文件写上
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDataType;//假设结点的数据域类型为 int

// 单链表的结构体定义
//		↓结点  单链表 Singly Linked List
typedef struct SListNode
{
	SLTDataType data;//结点的数据域,用来存储数据元素
	struct SListNode* next;//结点的指针域,用来存储下一个结点地址的指针域
	//next的意思就是下一个结点的指针,上一个结点存的是下一个结点的地址
	//每个结点都是结构体指针类型
	//有些人会把上一行代码写成SListNode* next;
    //这是不对的,因为C语言中 struct SListNode 整体才是一个类型(但C++可以)
	//或者写成SLTNode* next;这也是不对的,因为编译器的查找规则是从上忘下找
}SLTNode;


// 链表的打印——助于理解链表
void SLTPrint(SLTNode* phead);

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);//用二级指针,x为要插入的数据

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

// 单链表的尾删
void SLTPopBack(SLTNode** pphead);

// 单链表头删
void SLTPopFront(SLTNode** pphead);

// 单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

// 单链表在pos位置之前插入x(效率较低)(得传头指针)(也可以理解为在pos位置插入)
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x);

// 单链表删除pos位置之前的值(效率较低)(得传头指针)(也可以理解为删除pos位置的值)
void SLTEraseBefore(SLTNode** pphead, SLTNode* pos);

// 单链表在pos位置之后插入x,单链表比较适合这种
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

// 单链表删除pos位置之后的值,单链表比较适合这种
void SLTEraseAfter(SLTNode* pos);

// 不传头指针,在pos前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore1(SLTNode* pos, SLTDataType x);

// 不传头指针,删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore1(SLTNode* pos);

// 单链表的销毁,不传二级
void SLTDestroy(SLTNode* phead);

// 单链表的销毁,传二级
void SLTDestroy1(SLTNode** pphead);

SList.c에서

#include"SList.h"//别忘了

//链表的打印
void SLTPrint(SLTNode* phead)
{
	//assert(phead);这里并不需要断言phead不为空
	//为什么这里不需要断言?
	//空链表可以打印,即phead==NULL可以打印,直接断言就不合适了
	//那空顺序表也可以打印,那它为什么就要断言呢?
	//因为phead是指向第一个存有数据的结点的
	//而顺序表的ps是指向一个结构体
	SLTNode* cur = phead;//将phead赋值给cur,所以cur也指向第一个结点
	while (cur != NULL)//或while(cur)
	{
		printf("%d->", cur->data);//打印的时候加了个箭头更方便理解
		cur = cur->next;//next是下一个结点的地址
		//++cur/cur++;这种是不行的,指针加加,加到的是连续的下一个位置
		//链表的每一个结点都是单独malloc出来的,我们不能保证结点之间的地址是连续的
	}
	printf("NULL\n");
}

// 动态申请一个结点
SLTNode* BuySLTNode(SLTDataType x)
{
	//同样不需要断言
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//先创建个结点
	if (newnode == NULL)//如果malloc失败
	{
		perror("malloc fail");
		return NULL;
	}
	//如果malloc成功
	newnode->data = x;//插入的数据
	newnode->next = NULL;//初始化为空

	return newnode;//返回newnode
}

// 单链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//pphead是plist的地址,不能为空
	//注意区分几个断言的判断,plist有可能是空,pphead一定不能为空

	SLTNode* newnode = BuySLTNode(x);
	//找尾(尾插之前先找到尾)
	if (*pphead == NULL)//若链表为空
	{
		*pphead = newnode;
	}
	else//若链表不为空
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
        //对于不为空的链表:尾插的本质是原尾结点要存新尾结点的地址
		{
			tail = tail->next;
		}
		tail->next = newnode;
		/*有些同学会写成:
		while (tail != NULL)
		{
			tail = tail->next;
		}
		tail = newnode;*/
	}
}


// 单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//pphead是plist的地址,不能为空

	//发现plist不管是否为空,头插的方法都一样
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

// 单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);//pphead是plist的地址,不能为空

	//检查有无结点
	assert(*pphead != NULL);//或者写成assert(*pphead);
	//1.只有一个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//2.有多个结点
		/*//法一:
		SLTNode* prev=NULL;
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;

		prev->next = NULL;*/
		//法二:
		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}

		free(tail->next);
		tail->next = NULL;
	}
}

// 单链表头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);//pphead是plist的地址,不能为空

	//检查有无结点
	assert(*pphead != NULL);

	SLTNode* first = *pphead;
	*pphead = first->next;
	free(first);
	first = NULL;
}

// 单链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;//用cur去遍历,不用phead
	while (cur)//找x
	{
		if (cur->data == x)//如果找到了
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;//如果找不到
}

//单链表在pos位置之前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pos);//默认pos一定会找到
	assert(pphead);//pphead是plist的地址,不能为空
	if (pos == *pphead)//如果pos在第一个位置——那就是头插
	{
		SLTPushFront(pphead, x);
	}
	else//如果pos不是第一个位置
	{
		//找到pos的前一个位置
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

// 单链表删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)//如果pos在第一个位置
	{
		SLTPopFront(pphead);//头删
	}
	else//如果不在第一个位置
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		//pos = NULL;形参的改变不影响实参,加不加这句话都可以
	}
}

//单链表在pos位置之后插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

// 单链表删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLTNode* del = pos->next;//保存要删除的结点
	pos->next = pos->next->next;//或者写成pos->next=del->next;
	free(del);
	del = NULL;
}

// 不传头指针,在pos前插入x(也可以理解为在pos位置插入)
void SLTInsertBefore1(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	//调用单链表在pos位置之后插入x的函数
	SLTInsertAfter(pos, x);

	//交换pos和pos->next的值
	SLTDataType temp;
	temp = pos->data;
	pos->data = pos->next->data;
	pos->next->data = temp;
}

// 不传头指针,删除pos位置之前的值(也可以理解为删除pos位置的值)
void SLTEraseBefore1(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);//不能尾删

	SLTNode* del = pos->next;
	pos->data = pos->next->data;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

// 单链表的销毁
void SLTDestroy(SLTNode* phead)
{
	SLTNode* cur = phead;
	/*//有些人一开始会这样写
	while (cur)
	{
		//free不是销毁这个指针指向的内存,而是将指针指向的内存还给操作系统
		free(cur);//cur依旧指向free之前的地址
		cur = cur->next;
	}*/

	while (cur)
	{
		SLTNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
}

// 单链表的销毁,传二级
void SLTDestroy1(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* tmp = cur->next;
		free(cur);
		cur = tmp;
	}
	*pphead = NULL;
}

Test.c에서

#include"SList.h"

void TestSList1()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);

	SLTPrint(plist);
}

void TestSList2()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
}

TestSList3()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
}

void TestSList4()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	//将寻找和修改结合
	//eg:值为2的结点*2
	SLTNode* ret = SLTFind(plist, 2);
	ret->data *= 2;
	SLTPrint(plist);
}

TestSList5()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	//寻找值为2的结点
	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertBefore(&plist, ret, 20);//在该结点前插入值为20的结点
	SLTPrint(plist);

}

TestSList6()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	//寻找值为2的结点
	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseBefore(&plist, ret);
	ret = NULL;//一般在这里置空
	SLTPrint(plist);
}

TestSList7()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertAfter(ret, 20);
	SLTPrint(plist);
}

TestSList8()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseAfter(ret);
	SLTPrint(plist);
}

TestSList9()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertBefore1(ret, 20);
	SLTPrint(plist);
}

TestSList10()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseBefore1(ret);
	SLTPrint(plist);
}

TestSList11()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTDestroy(plist);
	plist = NULL;
}

TestSList12()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTDestroy1(&plist);
	SLTPrint(plist);
}

int main()
{
	//TestSList1();
	//TestSList2();
	//TestSList3();
	//TestSList4();
	//TestSList5();
	//TestSList6();
	//TestSList7();
	//TestSList8();
	//TestSList9();
	//TestSList10();
	//TestSList11();
	TestSList12();
	return 0;
}

수정 환영❀

추천

출처blog.csdn.net/outdated_socks/article/details/130071786