1、使用动态链表

带头结点单链表的初始化

// 带头结点链表初始化
void InitList(Linklist *l) {
	*l = (Linklist)malloc(sizeof(LNode));
	(*l)->next = NULL;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = 1;
	s->next = NULL;
	(*l)->next = s;
}

Linklist 是指向 LNode 结构体的指针类型,l 本身就是一个指针。在 InitList() 函数中,需要修改传入指针的值,让 l 指向链表的头节点(使用malloc函数开辟),因此传参时需要传递指针的地址,所以 InitList() 函数的参数定义为 LinkList *l ,l 为指向指针的指针。

为什么需要传递指针的地址

1. 修改指针的值

  • 当我们传递一个指针(如 Linklist l)给一个函数时,实际上传递的是指针的副本。
  • 在函数内部对这个副本进行的任何修改都不会影响到原始指针。

2. 在函数内部分配内存

  • 在 InitList 函数中,我们需要为链表的头结点分配内存,并让传入的指针指向这块内存。
  • 如果只传递指针的副本,分配的内存将无法反映到函数外部。

示例说明

假设我们有以下代码:

void InitList(Linklist l) {
    l = (Linklist)malloc(sizeof(LNode));
    // 其他初始化操作
}

int main() {
    Linklist l = NULL;
    InitList(l);
    // 此时 l 仍然是 NULL,因为在 InitList 中修改的是 l 的副本
}

在这个例子中,InitList 函数内部的 l 只是 main 函数中 l 的一个副本。即使我们在 InitList 中为 l 分配了内存,这个修改也不会影响到 main 函数中的 l。 个人理解为:在 main 函数中 l 指向某个地址,在 InitList 中对 l 的操作相当于让 l 的副本指向其他地址,而 main 函数中的 l 没有变。

正确的做法

通过传递指针的地址(即指向指针的指针),我们可以在函数内部修改原始指针的值:

void InitList(Linklist *l) {
    *l = (Linklist)malloc(sizeof(LNode));
    // 其他初始化操作
}

int main() {
    Linklist l = NULL;
    InitList(&l);
    // 现在 l 已经指向分配的内存,因为在 InitList 中修改的是 l 本身
}

个人理解:* 作为解引用操作符‌:用于获取指针指向的变量的值;让指针指向系统新开辟节点的地址。

按值查找

依次遍历查找,若找到,返回节点指针,否则返回空。

查找过程中不修改链表,因此形参传值时只传“副本”即可。

// 按值查找
LNode * GetLNode(Linklist l, int xNum) {
	LNode *r = l->next;
	while (r) {
		if (r->data == xNum) {
			return r;
		}
		r = r->next;
	}
	return NULL;
}

将 y 插入到 x 后面

// 插入节点
void Insert(Linklist l, int xNum, int yNum) {
	LNode *s, *r = GetLNode(l, xNum);
	if (r == NULL) {	// x 不存在
		return;
	}
	s = (LNode*)malloc(sizeof(LNode));
	s->data = yNum;
	s->next = r->next;
	r->next = s;
}

查询 x 后面的元素 

// 查找结点
void Find(Linklist l, int xNum) {
	LNode *r = GetLNode(l, xNum);
	if (r == NULL || r->next == NULL) {	// x 不存在或在最后一个
		printf("0\n");
	}
	else {
		printf("%d\n", r->next->data);
	}
}

删除 x 后面的元素

// 删除节点
void Delete(Linklist l, int xNum) {
	LNode *r = GetLNode(l, xNum), *s;
	if (r == NULL || r->next == NULL) {	// x 不存在或在最后一个
		return;
	}
	s = r->next;
	r->next = r->next->next;
	free(s);
}

释放内存

内存泄漏:指程序在申请内存后,无法释放已申请的内存空间,导致系统中可用的内存空间逐渐减少。

在链表操作过程中,我们会使用malloc等函数动态分配内存给每个节点。如果不在使用完链表后释放这些内存,就会造成内存泄漏。

LNode* temp;
while (l) {
	temp = l;
	l = l->next;
	free(temp);
}

完整代码

#include<stdio.h>
#include<stdlib.h>

typedef struct LNode {
	int data;
	struct LNode *next;
}LNode, *Linklist;

// 带头结点链表初始化
void InitList(Linklist *l) {
	*l = (Linklist)malloc(sizeof(LNode));
	(*l)->next = NULL;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = 1;
	s->next = NULL;
	(*l)->next = s;
}

// 按值查找
LNode * GetLNode(Linklist l, int xNum) {
	LNode *r = l->next;
	while (r) {
		if (r->data == xNum) {
			return r;
		}
		r = r->next;
	}
	return NULL;
}

// 插入节点
void Insert(Linklist l, int xNum, int yNum) {
	LNode *s, *r = GetLNode(l, xNum);
	if (r == NULL) {	// x 不存在
		return;
	}
	s = (LNode*)malloc(sizeof(LNode));
	s->data = yNum;
	s->next = r->next;
	r->next = s;
}

// 查找结点
void Find(Linklist l, int xNum) {
	LNode *r = GetLNode(l, xNum);
	if (r == NULL || r->next == NULL) {	// x 不存在或在最后一个
		printf("0\n");
	}
	else {
		printf("%d\n", r->next->data);
	}
}

// 删除节点
void Delete(Linklist l, int xNum) {
	LNode *r = GetLNode(l, xNum), *s;
	if (r == NULL || r->next == NULL) {	// x 不存在或在最后一个
		return;
	}
	s = r->next;
	r->next = r->next->next;
	free(s);
}

int main() {
	int opNum;
	int opType, x, y;
	Linklist l = NULL;

	InitList(&l);
	scanf("%d", &opNum);
	while (opNum--) {
		scanf("%d", &opType);
		switch (opType) {
		case 1:
			scanf("%d %d", &x, &y);
			Insert(l, x, y);
			break;
		case 2:
			scanf("%d", &x);
			Find(l, x);
			break;
		case 3:
			scanf("%d", &x);
			Delete(l, x);
			break;
		default:
			break;
		}
	}

	LNode* temp;
	while (l) {
		temp = l;
		l = l->next;
		free(temp);
	}

	return 0;
}

使用动态链表最后两组测试点超时无法通过! 

2、使用数组

数组下标表示元素,数组的值表示下一个元素。

完整代码

#include<stdio.h>

#define MAX 1000001    // 1 <= x, y <= 10^6

void Insert(int x, int y, int num[]) {
	num[y] = num[x];
	num[x] = y;
}

void Find(int x, int num[]) {
	printf("%d\n", num[x]);
}

void Delete(int x, int num[]) {
	int temp = num[x];
	num[x] = num[num[x]];
	num[temp] = -1;
}

int main() {
	int table[MAX] = {-1};    // 将数组值初始化为 -1
	int opNum, opType;
	int x, y;

	table[1] = 0;    // 开始只有元素 1,将最后一个元素的值设为 0
	scanf("%d", &opNum);
	while (opNum--) {
		scanf("%d", &opType);
		switch (opType) {
		case 1:
			scanf("%d %d", &x, &y);
			Insert(x, y, table);
			break;
		case 2:
			scanf("%d", &x);
			Find(x, table);
			break;
		case 3:
			scanf("%d", &x);
			Delete(x, table);
		default:
			break;
		}
	}

	return 0;
}

缺点:表的长度过大,占用大量内存。

Logo

集算法之大成!助力oier实现梦想!

更多推荐