[C语言]链表概念及基本用法

前言

  • 这篇文章主要谈到了链表的概念及基本用法,适合初学者。
  • 最近在教链表,我想着算法还没开始学,得自己补一下。

一、链表基础

1.链表的基本概念

  • 链表是一种常见的数据结构,它由一系列节点(Node)组成。每个节点包含两部分:数据部分和指针部分。数据部分用于存储实际的数据,比如一个整数、一个字符或者一个结构体等。指针部分则用于存储下一个节点的地址,通过这种方式将多个节点连接起来,形成一个链状结构。
  • 与数组不同,数组在内存中是连续存储的,而链表的节点在内存中的位置可以是不连续的。这使得链表在插入和删除操作时具有一定的优势。

2.链表节点的定义

  • 假设我们要创建一个存储整数的简单链表,节点的结构体定义如下:
struct Node {
    int data;
    struct Node *next;
};
  • 这里struct Node是节点的类型,data成员用于存储整数,next是一个指针,它指向struct Node类型的另一个节点。

3.链表的基本操作

a.创建链表:

  • 首先要创建一个头节点(head),头节点通常用于标记链表的开始位置。例如:
struct Node *head = NULL;
  • 然后可以逐个创建节点并插入到链表中。比如创建一个新节点并插入到链表头部:
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->data = 1;
newNode->next = head;
head = newNode;
  • 这里malloc函数用于在内存中分配足够的空间来存储一个struct Node类型的节点。newNode->data = 1是将数据部分赋值为 1,newNode->next = head是将新节点的下一个节点指针指向当前的头节点,然后head = newNode将新节点赋值为头节点。

b. 遍历链表:

  • 遍历链表是为了访问链表中的每个节点。可以使用一个指针来遍历链表。例如:
struct Node *current = head;
while (current!= NULL) {
    printf("%d ", current->data);
    current = current->next;
}
  • 这里首先将current指针指向头节点,然后在while循环中,只要current指针不为NULL(即还没有到达链表的末尾),就打印当前节点的数据部分,并将current指针移动到下一个节点(通过current = current->next)。
    c.插入节点:
  • 在链表中插入节点有多种情况,如在头部插入(前面已经示例)、在中间插入和在尾部插入。在中间插入节点时,假设要在值为x的节点之后插入一个新节点,代码如下:
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->data = newData;
struct Node *current = head;
while (current!= NULL && current->data!= x) {
    current = current->next;
}
if (current!= NULL) {
    newNode->next = current->next;
    current->next = newNode;
}
  • 首先找到值为x的节点,然后将新节点插入到它后面。先将新节点的next指针指向当前节点的下一个节点,再将当前节点的next指针指向新节点。

d.删除节点:

  • 同样有多种情况,以删除值为y的节点为例。
struct Node *current = head;
struct Node *prev = NULL;
while (current!= NULL && current->data!= y) {
    prev = current;
    current = current->next;
}
if (current!= NULL) {
    if (prev == NULL) {
        head = current->next;
    } else {
        prev->next = current->next;
    }
    free(current);
}
  • 这里在遍历链表寻找值为y的节点过程中,使用prev指针记录当前节点的前一个节点。当找到要删除的节点后,如果prevNULL,说明要删除的是头节点,直接将head指向当前节点的下一个节点;否则,将prev节点的next指针指向当前节点的下一个节点,最后使用free函数释放被删除节点占用的内存。

4.链表的类型

  • 单向链表:前面介绍的就是单向链表,它的节点只包含一个指向下一个节点的指针,只能沿着一个方向遍历。
  • 双向链表:双向链表的节点包含两个指针,一个指向前一个节点,一个指向下一个节点。它的节点结构体定义可能如下:
struct DoublyNode {
    int data;
    struct DoublyNode *prev;
    struct DoublyNode *next;
};
  • 双向链表在某些操作上(如反向遍历)比单向链表更方便,但占用的空间相对多一些,因为每个节点多了一个指针。
  • 循环链表:循环链表可以是单向循环链表或双向循环链表。在单向循环链表中,最后一个节点的next指针指向头节点,形成一个环。在双向循环链表中,头节点的prev指针指向最后一个节点,最后一个节点的next指针指向头节点,也形成一个环。这种链表结构在某些特定的应用场景(如循环缓冲区)中很有用。

二、例题讲解

1.题目(此处我们选择比较基础的题目)

//Q504.(语言: C)在一个有序(按非递减顺序)的链表中插入一个元素为x的结点,
//使插入后的链表仍然有序(链表数据域为整型数,N为6)。
//* *输入提示:"输入数组6个元素的值。\n"
//* *输入格式:"%d"
//* *输出提示:"此链表各个结点的数据域为:"
//* *输出格式:"%d "   (注:所有数据输出结束后有一个回车)
//* *输入提示:"输入要插入的数据x:"
//* *输入格式:"%d"
//* *输出提示:"插入后链表各个结点的数据域为:"
//* *输出格式:"%d "  (注:所有数据输出结束后有一个回车)
//程序运行示例如下:
//输入数组6个元素的值。↙
//12 23 34 45 56 67
//此链表各个结点的数据域为:12 23 34 45 56 67 ↙
//输入要插入的数据x : 36
//插入后链表各个结点的数据域为:12 23 34 36 45 56 67↙

2.题解

a.头文件

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

b.节点的定义

typedef struct listnode {
	int num;
	struct listnode* next;
}lsnd;
  • lsndstruct listnode的别名。

c.创建链表

lsnd* createNode(int arr[],int ipt) {
	lsnd* head = NULL;
	lsnd* tail = NULL;

	for (int n = 0; n < ipt; n++) {
		lsnd* newNode = (lsnd*)malloc(sizeof(lsnd));

		newNode->num = arr[n];
		newNode->next = NULL;
		if (head == NULL) {
			head = newNode;
			tail = newNode;
		}
		else {
			tail->next = newNode;
			tail = newNode;
		}
	}

	return head;
}
  • 此部分有个比较难理解的点(个人感觉):
    tail->next = newNode;tail = newNode;
    • 我们可以类比一下,比如你有三个钩子(节点),钩子的尾部系了一段绳子(.next),第一个钩子叫tail,第二个叫newNode,第三个叫tailNext
    • 本来tail的尾绳被tailNext钩住,但是你现在改变了他的状态。
    • newNode钩住tail的尾绳(tail->next = newNode;),再用tailNext钩住newNodetail = newNode;),这样newNode就插在了tailtailNext的中间。

d.插入节点

lsnd* insert(lsnd* head, int ipt) {
	lsnd* iptNode = (lsnd*)malloc(sizeof(lsnd));
	iptNode->num = ipt;

	if (head == NULL) {
		return iptNode;
	}
	if (ipt < head->num) {
		iptNode->next = head;
		return iptNode;
	}

	lsnd* tmp = head;
	while (tmp != NULL && tmp->next != NULL && tmp->next->num < ipt) {
		tmp = tmp->next;
	}

	iptNode->next = tmp->next;
	tmp->next = iptNode;
	return head;
}

e.打印链表

void printList(lsnd* head) {
	lsnd* tmp = head;
	while (tmp != NULL) {
		printf("%d ", tmp->num);
		tmp = tmp->next;
	}
	printf("\n");
}

f.主函数

int main() {
	int arr[6] = { 0 };
	printf("输入数组6个元素的值。\n");
	for (int n = 0; n < 6; n++) {
		scanf("%d", &arr[n]);
	}

	lsnd* head = createNode(arr, 6);
	printList(head);

	printf("输入要插入的数据x:");
	int ipt = 0;
	scanf("%d", &ipt);
	head = insert(head, ipt);

	printf("插入后链表各个结点的数据域为:");
	printList(head);

	return 0;
}

结语

  • 这篇文章主要是留下日后复习。
  • 可能存在部分错误欢迎指正。
  • 讲解比较清楚,例题比较简单,希望这篇文章能够帮到你。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值