数据结构——链表 原理及C语言代码实现(可直接运行版)

1.链表

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

2.链表的分类

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

①单向或者双向

②带头或者不带头

③循环或者非循环

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

  • 无头单向非循环链表

        无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

  • 带头双向循环链表

        带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。


小记tip:

全局变量或者静态存储都在数据段上,而在函数内部临时开辟的则存储在栈区(记忆技巧:栈区后来先出,生命周期短,适合存储临时使用的变量,在函数执行结束时会自动被释放),堆区存放的则是在函数运行过程中开辟出来的空间,需要程序员进行手动分区以及释放,也可能程序结束时由OS回收,所以也可能会发生内存泄漏问题

3.无头单向非循环链表

3.1接口实现

<SList.h>

#pragma once

typedef int SLNDataType;

// Single List
typedef struct SListNode
{
	SLNDataType val;
	struct SListNode* next;
}SLNode;

void SLTPrint(SLNode* phead); // 遍历打印
void SLTPushBack(SLNode** pphead, SLNDataType x); // 尾插
void SLTPushFront(SLNode** pphead, SLNDataType x); // 头插
SLNode* CreatNode(SLNDataType);// 新节点的创建
SLNode* SLTFind(SLNode* phead, SLNDataType x); // 查找

void SLTPopBack(SLNode** pphead); // 尾删
void SLTPopFront(SLNode** pphead);  // 头删

void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x); //在pos的前面插入
void SLTErase(SLNode** pphead, SLNode* pos); //删除pos位置

void SLTInsertAfter(SLNode* pos, SLNDataType x); //在后面插入
void SLTEraseAfter(SLNode* pos); //在后面删除

void SLTDestroy(SLNode** pphead); //销毁整个单链表

3.2函数实现

<SList.c>

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"SList.h"
#include<stdlib.h>
#include<assert.h>

void SLTPrint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d-> ", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}

SLNode* CreatNode(SLNDataType x) // 新节点的创建
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;
	return newnode;
}

//尾部插入
void SLTPushBack(SLNode** pphead, SLNDataType x)//pphead是plist的拷贝,改变pphead不能改变plist
{
	assert(pphead);
	SLNode* newnode = CreatNode(x);

	if (*pphead == NULL) // 链表为空
	{
		*pphead = newnode;
	}
	else 
	{
		//找尾,最后一个节点
		SLNode* tail = *pphead;
		while (tail->next != NULL)//tail->next原因:没有把链表和新节点连接起来,不指向next的话会发生内存泄漏,出了作用域tail销毁
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}



//头部插入
void SLTPushFront(SLNode** pphead, SLNDataType x)
{
	assert(pphead);
	SLNode* newnode = CreatNode(x);
	newnode->next = *pphead;
	*pphead = newnode; 
}



// 尾删
void SLTPopBack(SLNode** pphead) 
{
	//如果删完了
	if (*pphead == NULL)
		return;
	//或者用断言
	//assert(*pphead);

	//if --- 1个节点
	//else --- 多个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//找尾,最后一个节点
		SLNode* prev = NULL;
		SLNode* tail = *pphead;
		while (tail->next != NULL)//tail->next原因:没有把链表和新节点连接起来,不指向next的话会发生内存泄漏,出了作用域tail销毁
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

// 头删
void SLTPopFront(SLNode** pphead)
{
	//空
	if (*pphead == NULL)
		return;
	//或者用断言
	//assert(*pphead);

	// 第一种写法
	SLNode* tail = *pphead;
	*pphead = tail->next;
	free(tail);

	// 第二种写法
	/*SLNode* tmp = (*pphead)->next;
	free(*pphead); // 在 C 语言中是安全的,但是 *pphead = tmp; 将会使头指针 pphead 指向 NULL,这在某些情况下可能是不期望的行为,比如如果链表设计为不允许为空
	*pphead = tmp;*/
}


// 查找
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{
	SLNode* cur = phead;
	while (cur)
	{
		if (cur->val == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;//没有此节点返回空
}

// 插入,在pos的前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);

	//assert((!pos && !(*pphead)) || (pos && *pphead));

	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLNode* newnode = CreatNode(x);
		newnode->next = pos;
		prev->next = newnode;

	}


}

//删除指定数pos
void SLTErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);

	if (*pphead == pos)
	{
		//头插
		SLTPopFront(pphead);
	}
	else
	{
		//pos不是第一个
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

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

void SLTInsertAfter(SLNode* pos, SLNDataType x)
{
	assert(pos);
	SLNode* newnode = CreatNode(x);

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

}


void SLTEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);//pos->next->next;执行这句时如果pos->next为空,空指针没有next,会报错,所以先断言

	SLNode* tmp = pos->next;
	pos->next = pos->next->next;

	free(tmp);
	tmp = NULL;
}

//依次释放单链表中的每个节点,直到整个链表被销毁
void SLTDestroy(SLNode** pphead)
{
	assert(pphead);

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

3.3调试

<Test.c>

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"SList.h"
#include<assert.h>

void TestSLT1()
{
	SLNode* plist = NULL; // 错误,比如函数内部实现了地址交换,形参并不会影响实参,要实现地址交换,需要传二级指针

	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

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

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

	SLTPopFront(&plist);
	SLTPrint(plist);

	SLNode* pos = SLTFind(plist, 3);
	SLTInsert(&plist, pos, 3);


}

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

	SLNode* pos = SLTFind(plist,4);
	SLTInsert(&plist, pos, 40);
	SLTPrint(plist);
	
	pos = SLTFind(plist, 2);
	SLTInsert(&plist, pos, 20);
	SLTPrint(plist);

}

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

	SLNode* pos = SLTFind(plist, 1);
	SLTErase(&plist, pos);
	SLTPrint(plist); 

	pos = SLTFind(plist, 3);
	SLTErase(&plist, pos);
	SLTPrint(plist);
}


int main()
{
	//TestSLT1();
	//TestSLT2();
	//TestSLT3();
	TestSLT4();
	return 0;
}

 4.带头双向循环链表

4.1接口实现

<List.h>

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 带头双向循环链表
typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType val;
}LTNode;


LTNode* CreatLTNode(LTDataType);//创建新节点
LTNode* LTInit();//初始化
void LTPrint(LTNode* phead);//打印

void LTPushBack(LTNode* phead, LTDataType x);//尾插
void LTPopBack(LTNode* phead);//尾删
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPopFront(LTNode* phead);//头删

LTNode* LTFind(LTNode* phead, LTDataType x);// 查找

void LTInsert(LTNode* pos, LTDataType x);// 在pos前面插入
void LTErase(LTNode* pos);// 删除pos位置

void LTDestroy(LTNode* phead);//销毁链表

4.2函数实现

<List.c>

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"List.h"


LTNode* CreatLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL; 

	return newnode;
}


LTNode* LTInit()
{
	LTNode* phead = CreatLTNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

void LTPrint(LTNode* phead)
{
	assert(phead);
	printf("哨兵卫");

	LTNode* cur = phead->next;
	while (cur != phead) 
	{
		printf("%d<=>", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = CreatLTNode(x);

	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTNode* tail = phead->prev;
	phead->prev = tail->prev;
	tail->prev->next = phead;

	free(tail);
	//tail = NULL;//tail变量会自动销毁,不需要写这行

}
//头插,也可以再创建一个指针保存phead的下一个节点,转换为三个节点的地址变换
//结构优势(C++ STL):如果链表为空,只有一个哨兵卫头结点,first就是自己
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = CreatLTNode(x);
	LTNode* first = phead->next;

	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}


//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);//如果只有头结点phead,first = phead,不能free,否则出现野指针

	LTNode* first = phead->next;
	LTNode* second = first->next;

	phead->next = second;
	second->prev = phead;
	free(first);
	first = NULL;
}

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

// 在pos前面插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = CreatLTNode(x);
	LTNode* posPrev = pos->prev;

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

// 删除pos位置
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posNext = pos->next;
	LTNode* posPrev = pos->prev;

	posPrev->next = posNext;
	posNext->prev = posPrev;

	free(pos);
}


void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

4.3调试

<Test.c>

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"List.h"
void Test1()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPushBack(plist, 5);
	LTPrint(plist);

	LTPopBack(plist);
	LTPrint(plist);
}

void Test2()
{
	LTNode* plist = LTInit();
	LTPushFront(plist, 10);
	LTPushFront(plist, 20);
	LTPushFront(plist, 30);
	LTPushFront(plist, 40);

	LTPrint(plist);

	LTPopFront(plist);
	LTPrint(plist);

	LTPopFront(plist);
	LTPrint(plist);
}

void Test3()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPushBack(plist, 5);
	LTPrint(plist);

	LTNode* pos = LTFind(plist, 3);
	if (pos)
	{
		pos->val *= 10;
	}
	LTPrint(plist);
	LTInsert(pos, 400);
	LTPrint(plist);

}

void Test4()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPushBack(plist, 5);
	LTPrint(plist);

	LTNode* pos = LTFind(plist, 3);
	if (pos)
	{
		LTErase(pos);
	}
	LTPrint(plist);
}

int main()
{
	/*Test1();*/
	/*Test2();*/
	/*Test3();*/
	Test4();
	return 0;
}

5.顺序表和链表的区别和联系

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Outlier_9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值