C语言数据结构二:单向链表

单项链表由两个结构体组成。

一、链表结点的结构体

首先是链表的结构体。链表的结构体有两部分构成:数据域和指针

数据域:之前我们会写具体类型,但是在数据结构中为了让类型泛指,所以我们也是void*类型。节点上只存地址。类型由使用者指向来确定。

指针域:指向下一个结点。

struct LinkNode {
	void* data;
	struct LinkNode* next;
};

这个链表数据结构如上。

之前写的链表初始化函数,有个不好的地方,只返回链表头结点。

拿到链表头结点,就相当于拿到了整个链表。

这样有个问题:如果链表一千多个结点,那么每次取值都要把前面一千多个取出来,才轮到要取的值。每次统计个数,也要数这么多。

所以我们加第二个结构体。

小结数据域更新为void*类型


二、第二个结构体:链表的结构体

这个结构体的成员,我们首先写上链表的头结点,相当于拿到了整个链表。

一般链表常见的操作是尾插,为了便于我们尾插,我们可以在链表结构体中,再定义一个尾结点。这张插入值时,只要在尾结点后面加上一个值即可,然后更新尾结点。不用再从头跑过来。

所以这第二个结构体,是需要结合我们使用情况来自定义的。如果我们的场景需要频繁尾插,就加上某项信息(属性)。

第二个结构体,更像是一个对链表的信息统计和管理

// 链表数据类型
struct LinkList {
	struct LinkNode header;  // 如果写指针,需要手动开辟内存,拿到它。
	int size;
	//struct LinkNode Tail;
};


三、头文件

#pragma once

// 宏定义,保证C程序在C++中也可以运行。
#ifdef __cplusplus
extern "C" {
#endif
	// 链表结点数据类型
	struct LinkNode {
		void* data;
		struct LinkNode *next;

	};
	// 链表数据类型
	struct LinkList {
		struct LinkNode header;  // 如果写指针,需要手动开辟内存,拿到它。
		int size;
		//struct LinkNode Tail;
	};

	// 初始化链表:以前返回链表,现在不再返回链表,不希望用户修改链表信息,做个隐藏。
	// 我们定义一个新类型:
	typedef void* LinkListll;
	LinkListll Init_LinkList();
	
	// 插入链表
	LinkListll Insert_LinkList(LinkListll list, int pos, void* data);

	// 遍历链表
	typedef void(*FOREACH)(void*);// 定义一个回调函数。
	void Foreach_LinkList(ListListll list, FOREACH myforeach);



#ifdef __cplusplus
}
#endif

当然了,如果为了进一步隐藏结构体,我们可以把结构体放在.c文件中,这样就可以实现对数据的隐藏。

注意我们初始化定义了输出了一个void*类型。所以后面所有调用这个list时,函数内部,必须强制类型转换



四、接口实现

#include"LinkList.h"

//链表节点数据类型
struct LinkNode
{
	void *data;
	struct LinkNode *next;
};

//链表数据类型
struct LList
{
	struct LinkNode header;
	int size;
};



//初始化链表
LinkList Init_LinkList()
{
	struct LList *list = malloc(sizeof(struct LList));
	if (NULL == list)
	{
		return NULL;
	}

	list->header.data = NULL;
	list->header.next = NULL;
	list->size = 0;

	return list;
}
//插入节点
void Insert_LinkList(LinkList list, int pos, void *data)
{
	
	if (NULL == list)
	{
		return;
	}

	if (NULL == data)
	{
		return;
	}

	struct LList * mylist = (struct LList *)list;

	if (pos < 0 || pos > mylist->size)
	{
		pos = mylist->size;
	}

	//查找插入位置
	struct LinkNode *pCurrent = &(mylist->header);
	for (int i = 0; i < pos; ++i)
	{
		pCurrent = pCurrent->next;
	}

	//创建新节点
	struct LinkNode *newnode = malloc(sizeof(struct LinkNode));
	newnode->data = data;
	newnode->next = NULL;

	//新节点插入到链表中
	newnode->next = pCurrent->next;
	pCurrent->next = newnode;

	mylist->size++;
}


//遍历链表
void Foreach_LinkList(LinkList list, FOREACH myforeach) /*回调函数*/
{

	if (NULL == list)
	{
		return;
	}

	if (NULL == myforeach)
	{
		return;
	}

	struct LList * mylist = (struct LList *)list;

	struct LinkNode *pCurrent = mylist->header.next;

	while (pCurrent != NULL)
	{
		myforeach(pCurrent->data);
		pCurrent = pCurrent->next;
	}

}


//按位置删除
void RemoveByPos_LinkList(LinkList list, int pos)
{

	if (NULL == list)
	{
		return;
	}

	struct LList *mylist = (struct LList *)list;

	if (pos < 0 || pos > mylist->size - 1)
	{
		return;
	}


	//找位置
	struct LinkNode *pCurrent = &(mylist->header);
	for (int i = 0; i < pos; ++i)
	{
		pCurrent = pCurrent->next;
	}


	//先保存待删除结点
	struct LinkNode *pDel = pCurrent->next;
	//重新建立待删除结点的前驱和后继结点关系
	pCurrent->next = pDel->next;
	//释放删除节点内存
	free(pDel);
	pDel = NULL;

	mylist->size--;

}
//按值删除
void RemoveByVal_LinkList(LinkList list, void *data, COMPARE compare)
{

	if (NULL == list)
	{
		return;
	}

	if (NULL == data)
	{
		return;
	}

	if (NULL == compare)
	{
		return;
	}

	struct LList *mylist = (struct LList *)list;

	//辅助指针变量
	struct LinkNode *pPrev = &(mylist->header);
	struct LinkNode *pCurrent = pPrev->next;

	while (pCurrent != NULL)
	{
		if (compare(pCurrent->data, data))
		{
			//找到了
			pPrev->next = pCurrent->next;
			//释放删除节点内存
			free(pCurrent);
			pCurrent = NULL;
			mylist->size--;
			break;
		}

		pPrev = pCurrent;
		pCurrent = pCurrent->next;
	}

}
//清空链表
void Clear_LinkList(LinkList list)
{
	if (NULL == list)
	{
		return;
	}

	struct LList *mylist = (struct LList *)list;

	//辅助指针变量
	struct LinkNode *pCurrent = mylist->header.next;

	while (pCurrent != NULL)
	{
		//先缓存下一个节点的地址
		struct LinkNode *pNext = pCurrent->next;
		//释放当前结点内存
		free(pCurrent);

		pCurrent = pNext;

	}

	mylist->header.next = NULL;
	mylist->size = 0;

}
//大小
int Size_LinkList(LinkList list)
{
	if (NULL == list)
	{
		return -1;
	}

	struct LList *mylist = (struct LList *)list;

	return mylist->size;
}
//销毁链表
void Destroy_LinkList(LinkList list)
{

	if (NULL == list)
	{
		return;
	}

	//清空链表
	Clear_LinkList(list);
	free(list);
	list = NULL;
}

注意遍历时需要用户输入回调函数;

注意按值删除时,需要用户输入值对比函数myCompare。因为到底如何对比,我们在设计时是不知道的,但是我们知道这边必须输入一个比较的函数。


五、链表的更高级用法

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

struct LinkNode
{
	struct LinkNode *next;
};


struct Person
{
	struct LinkNode node;
	char name[64];
	int age;
};

类似于类的继承关系。

单向链表如上,如果是双向链表,那么在LinkNode中再加一个自身结构体指针就可以。

解释一句:这个LinkNode最好放在结构体的前面,这样的结构便于查找地址,如果放在后面,就增加了计算偏移量的操作。也就是说:node属性在name和age之前。

最重要的是:注意类型的转换。

void test()
{
	
	struct Person p1 = { NULL, "aaa", 10 };
	struct Person p2 = { NULL, "bbb", 20 };
	struct Person p3 = { NULL, "ccc", 30 };
	struct Person p4 = { NULL, "ddd", 40 };
	struct Person p5 = { NULL, "eee", 50 };

	//这种指针类型转换一定要看懂。
	struct LinkNode *pp1 = (struct LinkNode *)&p1;
	struct LinkNode *pp2 = (struct LinkNode *)&p2;
	struct LinkNode *pp3 = (struct LinkNode *)&p3;
	struct LinkNode *pp4 = (struct LinkNode *)&p4;
	struct LinkNode *pp5 = (struct LinkNode *)&p5;

	// 这种转成指针域指针之后,无法访问到数据域的内容。
	pp1->next = pp2;
	pp2->next = pp3;
	pp3->next = pp4;
	pp4->next = pp5;
	pp5->next = NULL;


	struct LinkNode *pCurrent = pp1;
	while (pCurrent != NULL)
	{
		// 再将类型转换回来。变成数据域指针。
		struct Person *person = (struct Person *)pCurrent;
		printf("Name:%s Age:%d\n",person->name,person->age);

		pCurrent = pCurrent->next;
	}
}

int main(){

	test();

	system("pause");
	return EXIT_SUCCESS;
}

将一种结构体数据域指针类型的指针,转换为另一种结构体指针域类型的指针。这样就可以保证转换之后,使用者访问不到数据域内容,进而只操作指针域内容。完成链接。

如果需要访问数据域内容,就需要再次转换回来。

指针类型可以实现互相转换呢。

完整写法:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>


//链表节点数据结构
struct LinkNode
{
	struct LinkNode *next;
};


//链表结构体
struct LList
{
	struct LinkNode header; //头结点
	int size;
};

typedef void * LinkList;


//初始化链表
LinkList Init_LinkList()
{

	struct LList *list = malloc(sizeof(struct LList));
	if (NULL == list)
	{
		return NULL;
	}

	list->header.next = NULL;
	list->size = 0;

	return list;
}


void Insert_LinkList(LinkList list, int position, void *data)
{
	if (NULL == list)
	{
		return;
	}

	if (NULL == data)
	{
		return;
	}

	struct LList * mylist = (struct LList *)list;
	struct LinkNode *mynode = (struct LinkNode *)data;


	if (position < 0 || position > mylist->size)
	{
		position = mylist->size;
	}


	//找位置(找到position位置的前一个位置)
	struct LinkNode *pCurrent = &(mylist->header); 
	for (int i = 0; i < position; ++i)
	{
		pCurrent = pCurrent->next;
	}


	//数据入链表
	mynode->next = pCurrent->next;
	pCurrent->next = mynode;


	mylist->size++;
}


void Foreach_LinkList(LinkList list, void(*myforeach)(void *) )
{

	if (NULL == list)
	{
		return;
	}

	if (NULL == myforeach)
	{
		return;
	}

	struct LList * mylist = (struct LList *)list;

	struct LinkNode *pCurrent = mylist->header.next;

	while (pCurrent != NULL)
	{
		struct LinkNode *pNext = pCurrent->next;
		myforeach(pCurrent);
		pCurrent = pNext;
	}

}

//删除节点
void RemoveByPos_LinkList(LinkList list, int position)
{
	if (NULL == list)
	{
		return;
	}

	struct LList * mylist = (struct LList *)list;

	if (position < 0 || position > mylist->size - 1)
	{
		return;
	}

	//辅助指针
	struct LinkNode *pCurrent = &(mylist->header);
	for (int i = 0; i < position; ++i)
	{
		pCurrent = pCurrent->next;
	}


	//缓存下待删除节点
	struct LinkNode *pDel = pCurrent->next;

	//重新建立待删除节点的前驱和后继结点关系
	pCurrent->next = pDel->next;

	mylist->size--;
}


void Destroy_LinkList(LinkList list)
{
	if (NULL == list)
	{
		return;
	}

	free(list);
	list = NULL;
}

struct Person
{
	struct LinkNode node; //结构体应该指针?
	char name[64];
	int age;
};

void myPrint(void *data)
{
	struct Person *person = (struct Person *)data;
	printf("Name:%s Age:%d\n", person->name,person->age);
}

void test()
{

	//初始化链表
	LinkList list = Init_LinkList();


	//创建数据
	struct Person p1 = { NULL, "aaa", 10 };
	struct Person p2 = { NULL, "bbb", 20 };
	struct Person p3 = { NULL, "ccc", 30 };
	struct Person p4 = { NULL, "ddd", 40 };
	struct Person p5 = { NULL, "eee", 50 };
	struct Person p6 = { NULL, "fff", 60 };


	//插入数据
	Insert_LinkList(list, 0, &p1);
	Insert_LinkList(list, 0, &p2);
	Insert_LinkList(list, 0, &p3);
	Insert_LinkList(list, 0, &p4);
	Insert_LinkList(list, 0, &p5);
	Insert_LinkList(list, 0, &p6);


	//遍历
	Foreach_LinkList(list, myPrint);

	//删除
	RemoveByPos_LinkList(list, 3);

	printf("------------------\n");

	//遍历
	Foreach_LinkList(list, myPrint);

	//销毁
	Destroy_LinkList(list);
}

int main(){

	test();

	system("pause");
	return EXIT_SUCCESS;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值