【数据结构(郝斌)】03线性结构-链表

一、链表的定义

n个节点离散分配
彼此通过指针相连
每个节点只有一个前驱结点、一个后续节点。首节点没有前驱结点,尾节点没有后续节点。

二、专业术语

首节点:第一个有效节点
尾节点:最后一个有效节点
头节点:首节点之前的节点,没有实际含义(不存放有效数据)的节点,为了方便链表操作。

头结点的数据域可以不存储任何信息,也可存储与数据元素类型相同的其他附加信息。例如,当数据元素为整数型时,头结点的数据域中可存放该线性表的长度。

作用:
(1)便于首元结点的处理
(2)便于空表和非空表的统一处理

头指针:指向头节点的指针变量
尾指针:指向尾节点的指针变量

三、注意

1.如果希望通过一个函数来对列表进行处理,我们至少需要接收链表的哪些参数?

一个参数:头指针
通过头指针可以推出链表的其他所有信息。
单链表可由头指针唯一确定

四、链表的分类

单链表

单链表是非随机存取的存储结构,要取得第l个数据元素必须从头指针出发顺链进行寻找,也称为顺序存取的存取结构。因此,其基本操作的实现不同于顺序表。

双链表:每个节点有两个指针域,一个指向前个节点,一个存放后个节点的地址。
循环链表:能通过任何一个节点找到其他所有的节点。最后一个节点指向首节点。
非循环链表

五、算法

遍历
查找
清空
销毁
求链表长度
排序
删除节点
插入节点

1.创建链表节点结构体

单链表中每个结点的存储结构:

#include <stdio.h>

struct Node
{
	int data;//数据域
	struct Node* pNext;//指针域

}NODE, * PNODE;//*PNODE 相当于struct Node*;NODE相当于struct Node

为了提高程序的可读性,在此对同一结构体指针类型起了两个名称,PNODE与NODE* , 两者本质上是等价的。通常习惯上用PNODE定义单链表,强调定义的是某个单链表的头指针;用NODE*定义指向单链表中任意结点指针变量

2、创建空节点

创建一个节点pHead赋null,后续会创建一个链表,并将链表的头节点的地址赋给pHead。

3.创建链表 create_list()

函数类型:PNODE
作用:创建一个链表,并将链表的头节点的地址赋给pHead。
参数:无 void
返回值:节点类型的地址

思路
定义两个节点指针动态开空间:一个pHead在函数刚开始创建当做链表的头节点,一个pNew在循环里动态创建当做每次新创建的节点。
定义一个节点指针pTail使它永远指向尾节点(方便新加节点每次加载最后一个节点后面)

步骤

1.动态创建一个pHead当做链表的头节点。并判断是否创建成功。

	int len;//有效结点的个数,链表长度
	int i;
	int val;//临时存放用户输入的节点的数据值
	PNODE pHead = (PNODE)malloc(sizeof(NODE));
	if (NULL == pHead)
	{
		printf("分配失败!程序终止!");
		exit(-1);
	}
	printf("请输入您需要生成的链表节点的个数:len=");
	scanf("%d", &len);

2.在循环里动态创建一个pNew当做每次链表新添加的节点。并判断是否创建成功。

	for ( i = 0; i < len; i++)
	{
		printf("请输入第%d个节点的值:", i + 1);
		scanf("%d", &val);
		PNODE pNew = (PNODE)malloc(sizeof(NODE));
		if (NULL == pNew)
		{
			printf("分配失败!程序终止!");
			exit(-1);
		}
	}

3.将新节点挂载链表的最尾端。此时需要一个节点指针pTail 永远指向链表的最后一个节点。该节点指针一开始指向头结点。每次创建完新节点,就将新节点挂在pTail后面,新节点pNext为null,然后该指针再指向新节点。pTail指向成为最后一个节点,便于下次添加节点添加在尾节点后面

PNODE pTail = pHead;
pTail->pNext = NULL;

for ( i = 0; i < len; i++)
	{
		printf("请输入第%d个节点的值:", i + 1);
		scanf("%d", &val);
		PNODE pNew = (PNODE)malloc(sizeof(NODE));
		if (NULL == pNew)
		{
			printf("分配失败!程序终止!");
			exit(-1);
		}
		pNew->data = val;//数据赋给pNew
		pTail->pNext = pNew;
		pNew->pNext = NULL;
		pTail = pNew;//pTail指向成为最后一个节点,便于下次添加节点添加在尾节点后面

	}

完整代码

/*
函数类型:PNODE
作用:创建一个链表,并将链表的头节点的地址赋给pHead。
参数:无 void
返回值:节点类型的地址
*/
PNODE create_list(void)
{
	int len;//有效结点的个数,链表长度
	int i;
	int val;//临时存放用户输入的节点的数据值
	PNODE pHead = (PNODE)malloc(sizeof(NODE));

	PNODE pTail = pHead;
	pTail->pNext = NULL;

	if (NULL == pHead)
	{
		printf("分配失败!程序终止!");
		exit(-1);
	}
	printf("请输入您需要生成的链表节点的个数:len=");
	scanf("%d", &len);

	for (i = 0; i < len; i++)
	{
		printf("请输入第%d个节点的值:", i + 1);
		scanf("%d", &val);
		PNODE pNew = (PNODE)malloc(sizeof(NODE));
		if (NULL == pNew)
		{
			printf("分配失败!程序终止!");
			exit(-1);
		}
		pNew->data = val;//数据赋给pNew
		pTail->pNext = pNew;
		pNew->pNext = NULL;
		pTail = pNew;//pTail指向成为最后一个节点,便于下次添加节点添加在尾节点后面

	}
	return pHead;
}

4.遍历链表 traverse_list()

作用:遍历打印整个链表的值
参数: PNODE pHead 链表头节点
返回值:无

思路:一个节点指针为头节点next,如果p为空,说明该链表为空。p不为null,就打印当前节点数据值,并向后偏移,知道尾节点。遍历完毕。

//指针不断偏移
void  traverse_list(PNODE pHead)
{
	PNODE p = pHead->pNext;
	while (NULL!=p)
	{
		printf("%d ", p->data);
		p = p->pNext;

	}
	printf("\n");

}

main测试:

int main(void)
{
	//struct Node* pHead = NULL;
	PNODE pHead = NULL;
	pHead = create_list();//创建一个非循环链表,并将链表的头节点的地址赋给pHead
	traverse_list(pHead);
	return 0;

}

结果:
在这里插入图片描述

5.判断链表是否为空(内存满–不太可能)is_empty(PNODE pHead)

函数类型:bool
作用:判断链表是否为空
参数:无 void
返回值:节点类型的地址

思路:链表头节点指针域为NULL,就代表该链表为空链表。

#include<stdbool.h>//bool:true\false
bool is_empty(PNODE pHead)
{
	if (NULL == pHead->pNext)
	{
		return true;
	}
	else
	{
		return false;
	}
}

6.链表长度 int length_list(PNODE pHead)

函数类型:PNODE
作用:链表长度
参数:PNODE
返回值:节点类型的地址

思路:定义一个int值len记录链表有效节点个数,让一个节点p赋值pHead->pNext,如果p不为NULL,则len+1.知道最后一个节点,然后返回len。

代码

int length_list(PNODE pHead)
{
	int len=0;
	PNODE p = pHead->pNext;
	while (NULL!=p)
	{
		++len;
		p = p->pNext;
	}
	return len;
}

main测试:

int main(void)
{
	//struct Node* pHead = NULL;
	PNODE pHead = NULL;
	pHead = create_list();//创建一个非循环链表,并将链表的头节点的地址赋给pHead
	traverse_list(pHead);
	if (is_empty(pHead))
	{
		printf("该链表为空!\n");
	}
	else
	{
		printf("该链表bu为空!\n");
	}
	int len = length_list(pHead);
	printf("该链表的长度为%d\n", len);
	return 0;

}

结果:
在这里插入图片描述

7. 插入节点 bool insert_list(PNODE pHead、int pos,int var)

函数类型:bool
作用:在p->pHead所指向的链表的第pos个节点的前面插入一个新的节点。该节点的值为var。
参数:PNODE pHead、int ,int
返回值:true false

思路(伪代码)

p是链表中一个节点,现要将一个节点q插入到p节点。
① q的指针域存放p的下个节点的地址*(也就是原先p的指针域存放的),然后p的指针域再存放q的地址。顺序不能颠倒。
q->next=p->next,p->next=q
② 借助一个节点r,先存放原先p的下个节点的地址(也就是原先p的指针域存放的),然后p的指针域再存放q的地址,q的指针域再存放r。
r=p->next,p->next=q,q->next=r

步骤

  • 创建一个节点赋pHead
  • 遍历节点指针直到待插入位置pos的前一个节点,跳出循环
  • 此时新动态创建一个节点pNew
  • 将var赋给pNew的data 新创建一个节点q保存原先pos的上个节点的地址
  • p的next指向新节点pNew
  • 新节点pNew的next指向q(原先pos的上个节点的地址)

完毕,此时新插入的节点就是该链表的第pos个节点啦

代码

bool insert_list(PNODE pHead, int pos, int val)
{
	int i = 0;
	PNODE p = pHead;

	while (NULL != p && i < pos - 1)
	{
		p = p->pNext;
		i++;
	}
	if (i > pos - 1 || NULL == p)
	{
		return false;
	}
	PNODE pNew = (PNODE)malloc(sizeof(NODE));
	if (NULL == pNew)
	{
		printf("insert_list pNew动态分配内存失败!");
		exit(-1);
	}
	pNew->data = val;
	PNODE q = p->pNext;
	p->pNext = pNew;
	pNew->pNext = q;
	return true;
}

main测试:

int main(void)
{
	//struct Node* pHead = NULL;
	PNODE pHead = NULL;
	pHead = create_list();//创建一个非循环链表,并将链表的头节点的地址赋给pHead
	traverse_list(pHead);
	insert_list(pHead, 4, 45);
	traverse_list(pHead);
	/*if (is_empty(pHead))
	{
		printf("该链表为空!\n");
	}
	else
	{
		printf("该链表bu为空!\n");
	}
	int len = length_list(pHead);
	printf("该链表的长度为%d\n", len);
	sort_list(pHead);
	traverse_list(pHead);*/

	return 0;

}

结果:
在这里插入图片描述

8.删除节点 bool delete_list(PNODE pHead、int pos,int*val)

函数类型:bool
作用:删除节点
参数:PNODE pHead、int pos【删除第几个节点】 ,int*val【将删除的数值】
返回值:true false

free p相当于删除p指向节点所占的内存,不是删除p本身。 p->pNext;相当于p所指向结构体变量中的pNext成员本身

思路(伪代码)

删除p所指节点的后面节点:
猜测1:不能直接将p->pNext=p->pNext->pNext;(p指向原先p后面节点的后面节点)。不然会导致原先p后面节点(待删除节点内存泄漏)
猜测2:free p->pNext; 就找不到后面的节点了

正解
定义一个节点r保存带删除节点也就是r=p->pNext;
p指向要删除节点后面的那个节点:p->pNext=p->pNext->pNext;
释放掉删除节点的空间 free ( r );

代码

bool delete_list(PNODE pHead, int pos, int* val)
{
	int i = 0;
	PNODE p = pHead;

	while (NULL != p->pNext && i < pos - 1)
	{
		p = p->pNext;
		i++;
	}
	if (i > pos - 1 || NULL == p->pNext)
	{
		return false;
	}
	//将删除的值保存下来
	PNODE q = p->pNext;
	*val = q->data;
	//删除某个节点
	p->pNext = p->pNext->pNext;
	free(q);
	q = NULL;
	return true;
}

main测试

int main(void)
{
	//struct Node* pHead = NULL;
	int val;
	PNODE pHead = NULL;
	pHead = create_list();//创建一个非循环链表,并将链表的头节点的地址赋给pHead
	traverse_list(pHead);
	insert_list(pHead, 4, 45);
	traverse_list(pHead);
	delete_list(pHead, 3, &val);
	printf("您删除的数值为%d\n", val);
	traverse_list(pHead);
	/*if (is_empty(pHead))
	{
		printf("该链表为空!\n");
	}
	else
	{
		printf("该链表bu为空!\n");
	}
	int len = length_list(pHead);
	printf("该链表的长度为%d\n", len);
	sort_list(pHead);
	traverse_list(pHead);*/

	return 0;

}

结果:
在这里插入图片描述

9.排序 void sort_list(PNODE pHead)

函数类型:void
作用:将链表各节点数据进行比较排序,也就是数值最小的存在第一个节点里,以此类推。
参数:PNODE pHead
返回值:无

思路:冒泡排序算法。思路方法和数组排序一样。
i=0相当于i=pHead->pNext;
i++相当于i=i->pNext;
j = i+1相当于 j=i->pNext
a[i]>a[j] 相当于i->data > j->data

代码

数组排序:

int i, j, t;
//int len=length_list(pHead);
int len = 5;
for ( i = 0; i < len; i++)
{
	for ( j = i+1; j < len; j++)
	{
		if (a[i]>a[j])
		{
			t = a[i];
			a[i] = a[j];
			a[j] = t;
		}
	}
}

链表排序:

void sort_list(PNODE pHead)
{
	int i, j, t;
	PNODE p, q;
	int len = length_list(pHead);
	//int len = 5;
	for (i = 0, p = pHead->pNext; i < len - 1; i++, p = p->pNext)
	{
		for (j = i + 1, q = p->pNext; j < len; j++, q = q->pNext)
		{
			if (p->data > q->data)
			{
				t = p->data;//t = a[i];
				p->data = q->data;// a[i] = a[j];
				q->data = t;
			}
		}
	}
	return;
}

man测试

int main(void)
{
	//struct Node* pHead = NULL;
	PNODE pHead = NULL;
	pHead = create_list();//创建一个非循环链表,并将链表的头节点的地址赋给pHead
	traverse_list(pHead);
	if (is_empty(pHead))
	{
		printf("该链表为空!\n");
	}
	else
	{
		printf("该链表bu为空!\n");
	}
	int len = length_list(pHead);
	printf("该链表的长度为%d\n", len);
	sort_list(pHead);
	traverse_list(pHead);

	return 0;

}

结果:
在这里插入图片描述

六、完整代码

#pragma warning(disable:4996)
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include<stdbool.h>//bool:true\false
typedef struct Node
{
	int data;//数据域
	struct Node* pNext;//指针域

}NODE, * PNODE;//*PNODE 相当于struct Node*;NODE相当于struct Node
PNODE create_list(void);
void  traverse_list(PNODE pHead);
bool is_empty(PNODE pHead);
int length_list(PNODE pHead);
bool insert_list(PNODE pHead, int pos, int val);
bool delete_list(PNODE pHead, int pos, int* val);
void sort_list(PNODE pHead);
int main(void)
{
	//struct Node* pHead = NULL;
	int val;
	PNODE pHead = NULL;
	pHead = create_list();//创建一个非循环链表,并将链表的头节点的地址赋给pHead
	traverse_list(pHead);
	insert_list(pHead, 4, 45);
	traverse_list(pHead);
	delete_list(pHead, 3, &val);
	printf("您删除的数值为%d\n", val);
	traverse_list(pHead);
	/*if (is_empty(pHead))
	{
		printf("该链表为空!\n");
	}
	else
	{
		printf("该链表bu为空!\n");
	}
	int len = length_list(pHead);
	printf("该链表的长度为%d\n", len);
	sort_list(pHead);
	traverse_list(pHead);*/

	return 0;

}
/*
函数类型:PNODE
作用:创建一个链表,并将链表的头节点的地址赋给pHead。
参数:无 void
返回值:节点类型的地址
*/
PNODE create_list(void)
{
	int len;//有效结点的个数,链表长度
	int i;
	int val;//临时存放用户输入的节点的数据值
	PNODE pHead = (PNODE)malloc(sizeof(NODE));

	PNODE pTail = pHead;
	pTail->pNext = NULL;

	if (NULL == pHead)
	{
		printf("分配失败!程序终止!");
		exit(-1);
	}
	printf("请输入您需要生成的链表节点的个数:len=");
	scanf("%d", &len);

	for (i = 0; i < len; i++)
	{
		printf("请输入第%d个节点的值:", i + 1);
		scanf("%d", &val);
		PNODE pNew = (PNODE)malloc(sizeof(NODE));
		if (NULL == pNew)
		{
			printf("分配失败!程序终止!");
			exit(-1);
		}
		pNew->data = val;//数据赋给pNew
		pTail->pNext = pNew;
		pNew->pNext = NULL;
		pTail = pNew;//pTail指向成为最后一个节点,便于下次添加节点添加在尾节点后面

	}
	return pHead;
}
//指针不断偏移
void  traverse_list(PNODE pHead)
{
	PNODE p = pHead->pNext;
	while (NULL != p)
	{
		printf("%d ", p->data);
		p = p->pNext;

	}
	printf("\n");

}

bool is_empty(PNODE pHead)
{
	if (NULL == pHead->pNext)
	{
		return true;
	}
	else
	{
		return false;
	}
}

int length_list(PNODE pHead)
{
	int len = 0;
	PNODE p = pHead->pNext;
	while (NULL != p)
	{
		++len;
		p = p->pNext;
	}
	return len;
}
bool insert_list(PNODE pHead, int pos, int val)
{
	int i = 0;
	PNODE p = pHead;

	while (NULL != p && i < pos - 1)
	{
		p = p->pNext;
		i++;
	}
	if (i > pos - 1 || NULL == p)
	{
		return false;
	}
	PNODE pNew = (PNODE)malloc(sizeof(NODE));
	if (NULL == pNew)
	{
		printf("insert_list pNew动态分配内存失败!");
		exit(-1);
	}
	pNew->data = val;
	PNODE q = p->pNext;
	p->pNext = pNew;
	pNew->pNext = q;
	return true;
}
bool delete_list(PNODE pHead, int pos, int* val)
{
	int i = 0;
	PNODE p = pHead;

	while (NULL != p->pNext && i < pos - 1)
	{
		p = p->pNext;
		i++;
	}
	if (i > pos - 1 || NULL == p->pNext)
	{
		return false;
	}
	//将删除的值保存下来
	PNODE q = p->pNext;
	*val = q->data;
	//删除某个节点
	p->pNext = p->pNext->pNext;
	free(q);
	q = NULL;
	return true;
}
void sort_list(PNODE pHead)
{
	int i, j, t;
	PNODE p, q;
	int len = length_list(pHead);
	//int len = 5;
	for (i = 0, p = pHead->pNext; i < len - 1; i++, p = p->pNext)
	{
		for (j = i + 1, q = p->pNext; j < len; j++, q = q->pNext)
		{
			if (p->data > q->data)
			{
				t = p->data;//t = a[i];
				p->data = q->data;// a[i] = a[j];
				q->data = t;
			}
		}
	}
	return;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值