「数据结构」线性表(C语言):链表

前言

  • 关于单链表:单链表在插入结点上的效率优于顺序表,单链表使用malloc函数为链表的结点分配空间,通过头结点来访问链表。
  • 为什么要建立头结点
    建立头结点统一了空表与非空表的区别,简化了链表的操作;如果直接删除链表的第一个结点,可能会导致表的丢失。
  • 想要学好单链表,就得动手画一画,链表中频繁使用指针来访问结点,写写画画能让我们的思路保持清晰

单链表

单链表的存储结构

单链表的结点由数据域指针域两部分构成。
单链表结点
结构体表示:

typedef struct Node
{
	ElemType data;	//ElemType可以根据实际需求更改为需要的数据类型
	struct Node * next;	//本类型指针
}Node, * LinkList;

单链表ADT实现

单链表的逻辑结构:头结点的next指针指向链表的首个结点
单链表逻辑结构图

  • CreateList(LinkList L, int *a, int i): 创建单链表,L为指向链表第一个元素的指针,即头指针;a为数组的地址,数组a用来接收从键盘获得数据;
//尾插法建立单链表,不断向链表的末尾添加结点,
//每一个插入的结点的next指针都为NULL
void CreateList(LinkList L, int * a, int i)
{
	Node * node;
	Node * head = L;
	while(i < length)
	{
		node = (Node *)malloc(sizeof(Node));
		node->data = *(a+i);
		node->next = NULL;
		L->next = node;
		L = L->next;
		printf("%d",node->data);
		i++;
	}
	L = head;
}

//头插法建立单链表,每个结点都插入为链表的第一个结点
//头结点的next指针始终指向插入结点
void CreateList(LinkList L, int *a, int i)
{
	Node * head;
	Node * node;
	while(i < length)
	{
		node = (Node *)malloc(sizeof(Node));
		node->data = *(a + length - ++i);
		node->next = L->next;
		L->next = node;
	}
}
  • PrintList(LinkList L): 打印链表
void PrintList(LinkList L)
{
	printf("打印结果:\n");
	for(int j = 0; j < length; j++)
	{
		L = L->next;
		printf("%d\t",L->data);
	}
	printf("\n");
}
  • DeleteList(LinkList L): 删除链表
void DeleteList(LinkList L)
{
	while(L->next != NULL)
	{
		Node *t = L->next;
		L->next = L->next->next;
		free(t);
	}
	printf("删除成功\n");
}
  • GetNode(LinkList L, int i): 查找结点,i 为的结点位置
Node* GetNode(LinkList L, int i)
{
	for(int j = 0; j < i; j++)
	{
		L = L->next;
	}
	return L;
}
  • Locate(LinkList L, int x): 查找结点,x为结点的数据值
int Locate(LinkList L, int x)
{
	for(int j = 0; j < length; j++)
	{
		L = L->next;
		if(L->data == x)
			break;
	}
	return ++j;
}
  • DeleteNode(LinkList L, int i): 删除结点,i 为结点位置
void DeleteNode(LinkList L, int i)
{
	for(int j = 0; j < i - 1; j++)
	{
		L = L->next;
	}
	Node *t;
	t = L->next;
	L->next = L->next->next;
	free(t);
	length--;
}
  • DeleteData(LinkList L, int x): 删除结点,x为结点的数据值
void DeleteData(LinkList L, int x)
{
	int i = Locate(L, x);
	if(i > length)
		printf("删除元素不存在");
	else
		DeleteNode(L, i);
}

链表初始化问题

在链表的结构体中,*LinkList 为结构体指针类型,LinkList L,L表示指向结构体的指针;LinkList *L,L是「指针的指针」,表示指向结构体指针的指针。
问题: 仅仅声明头指针,而未使用malloc函数为头指针分配空间,然后初始化链表,会出现程序异常结束问题。
原因:

声明一个指向结构体的指针并不创建该结构,而是给出足够的空间容纳结构可能会使用的地址。创建未被声明过的记录的唯一方法是使用malloc库函数。

例如

//定义头指针后变将,头指针的next指针域指向NULL
LinkList L;
L->next = NULL;
L = (LinkList)malloc(sizeof(Node));

问题截图

链表删除问题

链表通过访问头指针来,遍历结点,如果直接删除结点会导致,部分结点无法访问到,从而无法完成整个链表的删除的操作;故我们需要一个中间结点来暂存即将删除的结点。
例如

Node *t = L->next;
L->next = L->next->next;
free(t);

双向链表

双向链表的结构体

双向链表在单链表的基础上添加一个指针指向结点的前驱结点。当链表为空时,头结点的两个指针都指向NULL。

typedef struct Node
{
	int data;
	struct Node * prior, *next;	//prior指向前驱结点,next指向后继结点
}Node,*DoubleList;

双向链表的逻辑结构

双向链表的逻辑结构图

双向链表的ADT实现

双向链表的操作和单链表的操作相似,如果从尾部遍历链表,双向链表比单链表更加方便。

双向链表的结点删除问题

与单链表结点删除不同,当删除单链表的最后一个结点时,只需要将前一个结点的next指针指向最后一个结点的next指针指向,即指向NULL。
而双向链表删除结点不仅需要修改next指针的指向,还需要修改prior指针的指向,故需要判断删除的结点是否为最后一个结点。

//删除指定位置结点
void DeleteNode(DoubleList L, int i)
{
	for(int j = 0; j < i - 1; j++)
	{
		L = L->next;
	}
	Node *t;
	t = L->next;
	if(t->next != NULL)
	{
		L->next = L->next->next;
		L->next->prior = L->next;
	}
	free(t);
	length--;
}

循环链表

循环链表逻辑结构

循环双向链表
循环链表有循环单链表和循环双链表,循环链表的最后一个结点指向头结点(单链表和双链表中指向NULL),循环双向链表的头指针的前驱结点为最后一个结点(循环链表可以设置头指针,也可以设置尾指针,也可以不设置)。

全部代码

GitHub地址:
单链表
双向链表

运行结果

在这里插入图片描述

发布了20 篇原创文章 · 获赞 75 · 访问量 6907

猜你喜欢

转载自blog.csdn.net/weixin_42089228/article/details/103548061