数据结构-单链表基础1

0.基本结构和函数

点击查看代码
typedef int ElemType;
typedef struct LNode {
	ElemType data;
	struct LNode *next;
} LNode, *linkList;

void PrintList(linkList L) {
	linkList p = L->next;
	while (p) {
		printf("%d ", p->data);
		p = p->next;
	}
	printf("\n");
}

1.单链表的建立

1.1.头插法

头插法可以实现逆序输入元素建立单链表的功能

1.1.1分析

每次输入元素时肯定要新建一个结点,然后输入其值,但是实现瓶颈在于如何操作指针的指向,可以想象成已经形成一条链,但是每次的元素都挤进头结点后面的位置,这样就可以实现倒序输入,即p->next = (*L)->next;(*L)->next = p;// L为头结点,p为每次输入的新结点

时间复杂度为\({O}(n)\) 空间复杂度为\({O}(1)\)

1.1.2代码具体实现

点击查看代码
void CreateList_H(linkList *L, int n) {
	*L = (linkList) malloc(sizeof(LNode));
	(*L)->next = NULL;
	for (int i = 0 ; i < n; i++) 
	{
		LNode *p = (LNode *) malloc(sizeof(LNode));
		scanf("%d", &(p->data));
		p->next = (*L)->next;	//先和头结点指向的一样
		(*L)->next = p;		//再让头结点指向这个结点
		//实现了挤入的操作
	}
}

1.2 尾插法

尾插法可以实现正序输入元素建立单链表的功能

1.2.1分析

尾插法更符合我们程序设计的思路,同样的,我们每次需要新建一个结点,显然我们只需要将新结点接入前趋结点\(next\)即可,由于链表无法访问上一个元素,我们只需要使用一个辅助指针,指向上次的结点即可,即linkList lastp = *L;/*辅助结点*/......p->next = NULL; lastp->next = p; lastp = p;

时间复杂度为\({O}(n)\) 空间复杂度为\({O}(1)\)

1.2.2代码具体实现

点击查看代码
void CreateList_R(linkList *L, int n) {
	*L = (linkList) malloc(sizeof(LNode));
	(*L)->next = NULL;
	linkList lastp = *L;
	for (int i = 0; i < n; i++) {
		LNode *p = (LNode *) malloc(sizeof(LNode));
		scanf("%d", &(p->data));
		p->next = NULL;
		lastp->next = p;	//要先让辅助结点的next指向新结点
		lastp = p;	//再让新结点‘成为’辅助结点
	}
}

2.单链表的查找

2.1简述

给定一个元素\(e\),要求返回\(e\)在单链表中第一次出现的位置(\(1-based\)),如果没有找到,则返回\(-1\)

2.2分析

查找操作非常简单,就是从头结点开始依次寻找,这里直接给出代码实现

时间复杂度为\({O}(n)\) 空间复杂度为\({O}(1)\)

2.3代码具体实现

点击查看代码
int Finde(linkList L,int e){
	linkList p = L->next;
	int cnt = 1;//需要返回位置,所以从1开始计数
	while(p){
		if(p->data==e) return cnt;
		p=p->next;
		cnt++;
	}
	return -1;//未找到
}

3.单链表的插入

3.1简述

给定一个元素\(e\),要求插入后这个元素的位置为\(id\),保证插入位置合法

3.2分析

首先,如果不保证插入位置合法,需要考虑边界条件的判定,显然当一个长度为\(n\)的单链表,\(id\)合法范围是\([1,n+1]\),因为\([1,n]\)都是显然的,如果新元素插入在链表末尾,就成为了第\(n+1\)个元素,所以边界包含\(n+1\),思考完这个问题之后,我们来考虑插入操作的实现,原来的位置\(id-1\)前趋元素\(next\)指针指向位置为\(id\)的元素,我们只需要把这个指针指向新的结点,再把新结点的\(next\)指针指向原来位置在\(id\)的元素即可,特殊的,如果\(id\)\(n+1\),只需要指向\(null\)即可

时间复杂度为\({O}(n)\) 空间复杂度为\({O}(1)\)

3.3代码具体实现

点击查看代码
void Insert(linkList *L, int e, int id) {
	linkList p = *L;
	int j;
	for (j = 0; p && j < id - 1; j++) {
		p = p->next;
	}
	linkList nod = (linkList) malloc(sizeof(LNode));
	nod->data = e;
	nod->next = p->next;
	p->next = nod;
}

4.单链表的删除

4.1简述

要求删除单链表中的第\(id\)个元素,如果删除成功输出修改后的单链表,否则输出\(-1\)

4.2分析

首先需要考虑边界情况,很显然id的合法范围是\([1,n]\),然后思考代码实现,之前处理插入操作的时候,相当于将两块积木拆开,然后放一块积木作为这两块的夹心,然后再拼起来。这里的删除操作其实也是类似的,我们需要将三块积木中间的那一块拆开丢掉,然后将剩下的两块拼起来就可以,特殊的当我们删除第n个结点的时候,只需要将第\(n-1\)个结点的指针指向\(null\)即可
值得注意的是如果直接实现可以这样写p->next=p->next->next,但是这样就无法释放内存,如果多次操作可能会出现问题,所以我们使用一个辅助结点,操作完成之后可以释放掉辅助结点。

时间复杂度为\({O}(n)\) 空间复杂度为\({O}(1)\)

4.3代码具体实现

点击查看代码
if (id < 1 || id > n) {
	printf("-1");
	return 0;
}
Delete(&L, id);
......
void Delete(linkList *L, int id) {
	linkList p = *L;
	int j;
	for (j = 0; p->next && j < id - 1; j++)
	//我们想要删除第id位置的元素,所以要一直遍历到id-1位置,然后修改这个结点的next指针
		p = p->next;
	linkList q = p->next;
	p->next = q->next;
	free(q);//释放内存
}

5.单链表的合并

5.1简述

给定两个有序链表($L1 $ 和\(L2\)),要求合并生成新的有序链表,可以包含重复元素
我们把将多个已经有序的数据结构合并成一个总的数据结构这个操作称为归并,所以这个问题又称为单链表归并问题

5.2分析

显然这需要使用双指针实现,一个指向\(L1\),一个指向\(L2\),然后逐个比较,选择较小的那个元素插入进新链表,这样操作完成之后可能会有一个链表还有剩余元素,我们不需要遍历,只需要找到那个还有剩余元素的链表,将前面操作好的链表的最后一个结点的指针指向第一个剩余结点即可

时间复杂度为\({O}(n)\) 空间复杂度为\({O}(1)\)

5.3代码具体实现

点击查看代码
void Merge(linkList L1, linkList L2, linkList *L3) {
	*L3 = (linkList) malloc(sizeof(LNode));
	(*L3)->next = NULL;
	linkList p1 = L1->next, p2 = L2->next, lastp = *L3;
	while (p1 && p2) {
		if (p1->data < p2->data)
			lastp->next = p1, p1 = p1->next;
		else
			lastp->next = p2, p2 = p2->next;
		lastp = lastp->next;
	}
	lastp->next = (p1) ? p1 : p2;
}
posted @ 2025-09-16 00:16  薄荷味小哀  阅读(118)  评论(0)    收藏  举报