链表-数据结构(二)(C语言)

链表

火车由一节节车厢串联构成,除了火车头,每节车厢都紧紧地连接着前一节车厢,并且每节车厢都能载人或者货物。有种数据结构和火车的结构非常相似,那就是 链表

我们把链表想象成火车,火车头便是链表的表头,每节车厢就是链表的元素,车厢里载的人和物就是元素的数据域,连接车厢的部件就是元素的指针。由此,我们可以得出链表的一个特点:元素之间前后依赖,串联而成。

还有,我们可以发现,链表的元素不能随机访问。想象下,在高速运行的火车上,如果我们想从车头到达某个车厢,是不是只能挨个车厢走过去,而不能直接到达目标车厢。

另外,除了火车头,每节车厢前面只连接一节车厢;除了最后的车厢,每节车厢后面也只连接一节车厢。这也是链表的特点:元素前面和后面不会出现多个元素相连的情况。

我们先来想几个问题:

  • 如果要在火车中间加一节车厢,该怎么操作?
  • 如果要把火车中间某节车厢移除,又该怎么操作?
  • 我们如果想知道每节车厢有多少人,该如何遍历呢?
  • 如果想让火车调头,又该怎么办呢?
    现在我们就继续链表的学习,来解决这些问题。

链表创建、插入、遍历操作的实现

函数功能
insert(node, index)将 node 插入到链表中下标为 index 的位置
output()输出整个链表

链表插入操作需要实现的函数如下:

函数功能
insert(node, index)将 node 插入到链表中下标为 index 的位置
  • 链表插入操作的实现方法如下:
1. 找到链表中要插入的位置。
2. 令待插入结点的 next 指针指向插入位置的当前结点。
3. 令插入位置之前的当前结点的 next 指针指向待插入结点。

我们用一个实例来给大家进行链表插入的演示:

当前有一个含元素 1、 2、3 的链表,我们要在下标为 2 的位置插入一个 3。
在这里插入图片描述
首先从第 0 个结点开始查找待插入的位置。
在这里插入图片描述
未找到目标位置则继续向后查找,如果直到链表最后一个结点还未找到目标结点则说明插入位置是非法的,返回。在这里当我们查找到第 1 个位置时发现目标位置,此时新建一个值为 3 的元素。
在这里插入图片描述
当前我们所在的结点是在位置 1,我们让新插入的结点指向当前结点的后一个结点,再更新当前结点指向新插入的结点就好了。注意这里的顺序不能颠倒了,如果先更新当前结点的指针我们就很难找到新插入结点的指针指向的结点。
在这里插入图片描述
通过这些操作,我们就成功完成了一个新结点的插入工作。当然如果插入的位置在链表的表头或表尾,插入操作会有一些不同,同学们可以自己思考这个问题。
在这里插入图片描述
我们接着来分析链表插入操作的时间复杂度:在执行插入操作时,我们需要在整个链表中找到插入结点的目标位置,因此平均的查找次数是 n 2 \frac{n}{2} 2n
,链表插入操作的时间复杂度为 O ( n ) \mathcal O(n) O(n)

在对链表进行遍历操作时,我们会从头结点开始通过当前结点的指针找到下一节点直至表尾,并输出所有结点的值。
链表遍历操作需要实现的函数如下:

函数功能
output()输出整个链表

链表遍历操作的实现方法如下:

1. 定义一个用于遍历的变量,初始指向头结点。
2. 输出遍历变量所在结点的值,并更新遍历变量为当前结点的下一个结点。
3. 重复操作 2,直到遍历完所有结点。

在链表中查询一个元素也是从表头开始向后遍历,找到了目标结点则返回它的位置,在这里我们就不再给大家演示了。

链表遍历时也是从链表的表头通过指针一直遍历到表尾,因此它的时间复杂度也是 O ( n ) \mathcal O(n) O(n)

创造链表

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

// 请在下面实现结构体 Node
// 命名两个名字 Node 和 *LinkedList 
// 链表的结点都是由 数据域 和 指针域组成, 在结构体中定义一个 int 类型的 变量 data, 记录火车节车厢的人数,再定义一个Node 类型的指针 next,  用来指向下一个结点 。
typedef struct Node {
    int data;
    struct  Node *next;
} Node, *LinkedList;

// 请在下面实现函数 clear
// 在程序结束前需要释放它所有占用的内存空间 
//  这一步 我们先在 主函数之前把释放内存的 函数 clear 定义好,函数没有返回值, 参数为LinkedList  类型的变量 head.



void clear(LinkedList head) {
	//  先定义一个Node 类型的指针变量 current_node,  初始让它指向 head.
    Node *current_node =  head; 
    //  我们需要让 current_node  不断指向后一个结点来遍历 整个链表来释放所有结点的内存空间, 在这里 我们可以用 while  循环实现这个逻辑, 
    //  循环条件是 current_node 不为空。 
    while (current_node != NULL) {
    	// 每次我们需要定义一个Node 类型的指针变量 delete_node  来 保存当前结点, 
        Node *delete_node =  current_node;
        //  之后让当前结点 current_node  指向它后一个结点.
        current_node = current_node->next;
        //  最后我们需要释放 delete_node  指向的内存空间 。 
        free(delete_node);
        
    }
}

int main() {
    //  定义一个空链表,请在主函数 定义一个 LinkedList 的实例 linkedlist  并且初始指向空NULL,
    LinkedList linkedlist = NULL;
    // 在主函数中调用 clear函数。 
    clear(linkedlist);
    
    return 0;
}

插入结点

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

typedef struct Node{
    int data;
    struct Node *next;
}Node, *LinkedList;

// 请在下面实现插入函数 insert
//  插入函数insert, 第一个LinkedList 类型的变量 head,  表示要插入结点的链表的头节点, 第二是Node 类型的指针变量 node, 第三个是int类型的 变量 index ,  表示插入结点后,这个结点时第index 个结点,
LinkedList insert(LinkedList head, Node *node, int index) {
    
    //  一种是头指针为空指针,那么我们就让 node 成为头指针。
    // 如果头指针为空,即链表为空,则当且仅当 index  为0 的时候,才能执行插入操作,否则这将是一个非法的操作,直接结束函数即可。
    
    if(head == NULL) {
     //   如果index 不为空,则直接用return head  结束 。
    
        if (index != 0) {
             return head;             
        }
        head = node;
        return head;
        
    }
    // 如果 头指针为空 并且 index 为0 ,那么我们就可以把 node 赋给 head, 也就是让 node成为 头指针, 然后用return head 结束。 
    if (index == 0) {
       node->next = head;
       head = node;
       return head;         
    }
    // 如果插入结点后的位置是 链表首位,也就是 index 等于 0 的 时候, 我们只要把node 插入到 head 前面即可 。
       // 如果结点插入后面链表首位,那么先让 node 的指针 指向 当前表头 head, 完成node 的插入, 然后让node 成为头节点 head, 完成表头的更新。然后用return head 语句结束。
  // 对于一般情况  在插入元素前, 我们要先找到目标的前一个位置,  然后将结点插入到 链表里。 于是,  我们先定义两个变量: 一个是Node  类型的指针 current_node , 用来在链表里遍历, 初始指向头指针 head ; 另一个是 int 类型的变量 count , 用来统计遍历了多少 个结点 初始值为 0. 
    Node *current_node = head;
    int count  = 0; 
    //接下来 我们用while 循环来找目标位置的前一个位置, 需要满足以下两个条件; current_node 的下一个结点不能为空, 如果为空表示已经到表尾了; count要小于 index - 1 .
    // 接下来 我们来把 while 循环写完整, 首先 current_node  指针需要指向 它的一个结点, 然后计数变量 count 要加1. 
    // 现在我们通过  while  找到了 目标位置的前一位, 想想看 是不是有可能 index  远大于 链表的长度吗这样就不符合 条件了。 
   while (current_node->next != NULL && count < index -1){
    current_node = current_node->next;
    count ++;   
            
    }
   // 我们要在 while  循环语句 后面加一盒限制条件 ,要求 count 等于 index - 1 ,  这样也正好解释了 上面 while  循环 里为什么 要满足的第二个 条件了。 
   //  我们找到了 目标位置的前一位, 现在 我i们 把这个新节点 插入到链表里, 首先让新结点 node 的指针 指向目标位置的后一个位置,也就是 current_node 的下一个结点,然后让目标位置的前一个系欸DNA指向鑫结点, 也就是额昂 current_node 指向新结点。
   if(count == index -1) {
        node->next = current_node -> next;
        current_node->next = node;
   }
 	//   最后我们将 插入结点的链表头节点head  作为  返回值结束函数,完成了数据的插入操作。 
     return head;
    
}

void clear(LinkedList head) {
    Node *current_node = head;
    while (current_node != NULL) {
        Node *delete_node = current_node;
        current_node = current_node->next;
        free(delete_node);
    }
}

int main() {
    LinkedList linkedlist = NULL;
    // 在主函数  中调用 insert 函数 将 1 到 10  个数字插入到鑫创建的链表 linkedlist 里。 这里我们分两步来完成.  首先我们在主函数第二行写一个 for 循环, 用int 变量 i  从 1 循环到 10 记得带上 花括号。
    for(int i = 1;  i <= 10; i++) {
        //  首先定义一个Node  类型的指针变量 node  申请一个 Node  大小的内存单元。 然后把node 的数据域设为i,  并将next 的指针指向空 完成初始化。 然后调用 insert 函数 在链表 linkedlist  中插入结点 node , 返回值赋予linkedlist. 
        //  这里我们把每个结点 都插入到链表的最后, 注意  如果要插入到链表首位, 参数index   应该为0, 而不是1.  这一步有四步,
        Node  *node =  (Node *)malloc(sizeof(Node));
        node->data = i;
        node->next = NULL;
        linkedlist = insert(linkedlist, node, i -1);
        
     }
    clear(linkedlist);
    return 0;
}

链表的遍历操作

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

typedef struct Node{
    int data;
    struct Node *next;
}Node, *LinkedList;

LinkedList insert(LinkedList head, Node *node, int index) {
    if (head == NULL) {
        if (index != 0) {
            return head;
        }
        head = node;
        return head;
    }
    if (index == 0) {
        node->next = head;
        head = node;
        return head;
    }
    Node *current_node = head;
    int count = 0;
    while (current_node->next != NULL && count < index - 1) {
        current_node = current_node->next;
        count++;
    }
    if (count == index - 1) {
        node->next = current_node->next;
        current_node->next = node;
    }
    return head;
}

// 请在下面实现输出函数 output
1. 先定义输出函数output, 函数只有一个LinkedList 类型的参数 head, 没有返回值。
2. 首先我们需要判断一下链表是否为空,如果链表为,也就是 head 为空指针,那么就用return ; 语句结束函数。
3. 接下来我们继续实现这个函数。首先我们需要定义一个
Node 类型的指针变量current_node,  用来遍历整个链表,刚开始指针current_node 先指向头指针head.
4. 那么接下来我们就用一个while 循环来遍历 链表。
循环需满足的条件很简单,就是current_node 指针不能为空。
5. 满足条件则说明结点是链表里的元素,那我们就把元素输出来,先输出结点的数据域,后面跟一个空格用来区分。
输出完,我们让current_node  指针指向它的下一个结点。
6. 到这里,我们就已经实现了链表的遍历输出了,为了输出看起来更美观,在while 循环后面再输出一个换行。
7. 接下来我们来看看怎么在主函数里调用它。
请在主函数最后调用遍历函数 output ,输出刚刚创建的链表 linkedlist.

void output(LinkedList head) {
    if(head == NULL) {
        return ;
    } 
    Node *current_node = head;
    while (current_node != NULL) {
        printf("%d ", current_node->data);
        current_node = current_node->next;
    }
    printf("\n");
}

void clear(LinkedList head) {
    Node *current_node = head;
    while (current_node != NULL) {
        Node *delete_node = current_node;
        current_node = current_node->next;
        free(delete_node);
    }
}

int main() {
    LinkedList linkedlist = NULL;
    for (int i = 1; i <= 10; i++) {
        Node *node = (Node *)malloc(sizeof(Node));
        node->data = i;
        node->next = NULL;
        linkedlist = insert(linkedlist, node, i - 1);
    }
    
    output(linkedlist);
    clear(linkedlist);
    return 0;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值