当前位置: 首页 > news >正文

【数据结构入门】链表

目录

1. 概念

2. 链表的实现

3.相关OJ题

3.1删除有序数组中的重复项

分析:

代码:

 3.2 数组形式的整数加法

分析:

代码:

3.3 反转单链表(非递归)

分析1:

代码1:

分析2:

代码2:


1. 概念

        链表是一种结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。也就是说链表的每一个节点,物理上不是连续存储的,每一个节点只有下一个结构体的地址信息

        相较于顺序表而言,每次新增一个节点只会新开辟一个节点的空间,而不是和顺序表一样,每次扩容需要翻倍增加新的空间。

2. 链表的实现

主要实现头插、头删、尾插、尾删。

注意:

①链表一般会设置一个头结点,这个头结点一般是一个结构体指针,那么要修改链表的时候(插入删除)需要传入这个指针的地址,也就是二级指针,才能修改链表的值

②头插节点的时候,情况要考虑完全,头结点可能为空,头结点可能有值;当头结点为空的时候,直接将新节点作为头结点,当头结点不为空的时候需要将头结点的下一个节点保存起来(NULL或者其他节点),将头结点的next置为新节点,保存的节点作为新节点的next。

③尾删节点的时候,需要分三种情况,如果头结点为空,提示不能删除,如果只有头结点有值,那么头结点置空,如果至少有两个节点,那么需要找到倒数第二个节点(先找到最后一个,用一个变量每次保存当前遍历的节点),只需要释放倒数第一个节点,让倒数第二个节点的next指向空即可。

③查找节点的时候只需要传入头结点指针即可(结构体变量的地址),这里不传二级指针的原因是因为,没有涉及到修改链表的内容,这里如果找到了那个结构体,只需要返回当前结构体指针即可,后续根据这个结构体地址可以对这个结构体进行修改。

④这里删除、插入指定位置的后一个元素,并不是插入、删除当前元素,因为形参只有当前结构体地址,如果要删除当前的元素,需要再传一个头指针。

#define _CRT_SECURE_NO_WARNINGS
#include"single_list.h"// 创建新节点
SingleListNode* createNode(dataType data)
{SingleListNode* node = (SingleListNode*)malloc(sizeof(SingleListNode));if (node == NULL){printf("申请内存失败!");exit(1);}node->data = data;node->next = NULL;return node;
}// 尾插
void push_back(SingleListNode** head, dataType data)
{SingleListNode* newNode = createNode(data);if (*head == NULL){*head = newNode;}else{SingleListNode* curr = *head;while (curr->next != NULL) // 找到最后一个节点{curr = curr->next;}curr->next = newNode;}
}// 尾删
void pop_back(SingleListNode** head)
{if (*head == NULL)  // 1.链表为空{printf("空链表无法删除!");exit(-1);}else if ((*head)->next ==NULL) // 2.链表只有一个元素{free(*head);(*head) = NULL;}else {// 3.链表至少2个元素// 遍历到倒数第二个SingleListNode* curr = *head;SingleListNode* prev = NULL;while (curr->next != NULL){prev = curr; // 倒数第二个节点curr = curr->next;}free(curr);prev->next = NULL;}}// 头插
void push_front(SingleListNode** head, dataType data)
{SingleListNode* node = createNode(data);node->next = *head;*head = node;
}// 头删
void pop_front(SingleListNode** head)
{if (*head == NULL){printf("链表为空,不能删除");exit(-1);}SingleListNode* tmp = *head;*head = (*head)->next;free(tmp);
}// 遍历链表打印
void list_print(SingleListNode* head)
{// 因为由头节点所以不需要判空SingleListNode* curr = head;while (curr != NULL){printf("%d\n", curr->data);curr = curr->next;}
}// 查找
SingleListNode* find(SingleListNode* head, dataType data)
{if (head == NULL){printf("链表为空,无法查找!");exit(-1);}while (head) {if (head->data == data){return head;}head = head->next;}return NULL; 
}// 插入指定位置的后面
void insert(SingleListNode** pos, dataType data) 
{assert(*pos); SingleListNode* newNode = createNode(data);newNode->next = (*pos)->next;(*pos)->next = newNode;
}// 删除指定位置的节点后面
void erase_pos(SingleListNode** pos)
{assert(*pos);SingleListNode* tmp = (*pos)->next;(*pos)->next = tmp->next;
}

3.相关OJ题

3.1删除有序数组中的重复项

给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k 。

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 不需要考虑数组中超出新长度后面的元素。

分析:

        这里使用双指针法,左指针起始位置下标为0,右指针起始位置下标为1,判断右指针是否小于整个数组的长度。

        当右指针所指向的数字等于左指针指向的数字,此时两个指针同时后移;

        当右指针所指向的数字不等于左指针指向的数字,此时需要将左指针指向的数字存储到当前数组的第一个位置中(使用index作为下标);此时两个指针同时后移。

        这个数组的最后两个数,存在两种情况:

        情况一:当这两个数不相等的时候,此时由于右指针最后+1,所以右指针会出右边界导致循环退出,最后一个数也是单独的数字,所以这里需要将最后一个数单独进行存储。

        情况二:此时这两个数如果相同,那么在最后一次循环的时候已经保存过了,但是因为要保持程序的一致性,并且多赋一次值也不会对整个程序造成性能影响,所以这里的情况按照情况一来处理。

代码:

int removeDuplicates(int* nums, int numsSize) {int left = 0;int right = 1;int index = 0;// 循环条件写在循环体中for(right;right < numsSize;){// 左右指针值相同,左右指针移动if(nums[left] == nums[right]){}else{// 左右指针值不相同,左指针赋值给index下标处nums[index++] = nums[left];}left++;right++;}// 最后一个元素若不同,需要再次赋值,相同则赋值没影响nums[index] = nums[left];index++;// 最后新数组的下标就是元素的个数return index;
}

 3.2 数组形式的整数加法

整数的 数组形式  num 是按照从左到右的顺序表示其数字的数组。

  • 例如,对于 num = 1321 ,数组形式是 [1,3,2,1] 。

给定 num ,整数的 数组形式 ,和整数 k ,返回 整数 num + k 的 数组形式 。

    示例 1:

    输入:num = [1,2,0,0], k = 34
    输出:[1,2,3,4]
    解释:1200 + 34 = 1234

    分析:

            这道题目的细节非常多,虽然标注的是简单题,但是可能对于初学者来说是中等题。总体思路一定不能直接把数组的数字转换成整型,如果数组的长度>30,那么整型就会溢出,所以这里其实在考大数相加的问题。

            我们可以将数组的每一位数字和k的每一位数字进行相加,一共要加多少次呢?3位数+2位数只需要加3次,也就是说循环的次数是位数更多的数字的位数。

            那么我们可以将相加的结果存在一个数组中,那么数组的长度怎么算呢?刚刚我们已经计算出来了两数较长的以为数的位数,只需要在此位数+1就是我们开辟数组的长度了,最极端的情况,三位数+三位数在进位的情况下,是四位数。流程梳理清楚,开始写代码。

    ①计算出k的位数为k_size;

    ②计算出k_size和num_size较大值,len;

    ③开辟出len+1的数组空间;

    ④开始按位相加,总共循环len次;

    ⑤当数组中的数字位数 < k的位数的时候,数组会越界,所以这里需要判断数组的合法下标,如果数组下标合法那就将该位参与运算,若不合法则使用0参与运算;

    ⑥k的末位和数组的最后一位数字(合法)相加,这里记录变量nextNum,表示是否进位,如果和>9那么说明需要进位,nextNum标注为1,否则是0,下一次按位相加的时候+nextNum,;

    ⑦此时和记录为ret,将ret的个位数存在返回数组的第一位、第二位以此类推;

    ⑧循环结束之后,需要判断两个数的第一位相加之后,是否需要进位,例如215 + 806 = 1021;

    由于循环只进行了三次,所以进位的数字没有进行保存,这里判断如果进位位1,那么需要单独处理,将1赋给返回数组的最新位,此时记录返回数组的下标再+1;

    ⑨将数组逆置,返回返回数组,此时的下标就是该数组的长度。

    代码:

    /*** Note: The returned array must be malloced, assume caller calls free().*/
    int* addToArrayForm(int* num, int numSize, int k, int* returnSize) {/*1.判断两个数哪个位数更长,创建新数组的长度就是改位数+1;2.从末尾开始按位相加,一共加的次数是长的那个数字的位数;3.相加完毕之后,是否进位需要进行保存,计算完毕的数字需要存放在新数组的首元素位置;4.循环开始的时候需要增加进位;5.如果数组的位数更大,那么需要判断合法性;6.将最后的数组逆序就是最后的结果。*/int k_num = k;// 1.判断数字的长度int k_size = 0;while (k) {k /= 10;k_size++;}int len = k_size > numSize ? k_size : numSize;int* retArr =(int*)malloc(sizeof(int) * (len + 1)); // 三位数+三位数 = 四位数// 2.按位计算int num_i = numSize - 1;int nextNum = 0; // 是否需要进位int ret_i = 0;while (len--) {// 当数组长度小于数字长度,那么此时len是数字长度就会越界// 判断数组下标是否合法int legal_num = 0;if(num_i >= 0){legal_num = num[num_i];num_i--;}int ret = legal_num + k_num % 10 + nextNum;if (ret > 9) {nextNum = 1;} else {nextNum = 0;}k_num /= 10;// 给新数组赋值retArr[ret_i++] = ret % 10;}// 如果nextNum == 1说明,第一位相加的时候需要进位,但是还没有处理if(nextNum == 1){retArr[ret_i] = 1;ret_i++;}// 将数组逆序int left = 0;int right = ret_i - 1;while(left < right){int tmp = retArr[left];retArr[left] = retArr[right];retArr[right] = tmp;left++;right--;}*returnSize = ret_i;return retArr;
    }

    3.3 反转单链表(非递归)

    给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

    示例 1:

    分析1:

    ①需要准备三个指针,第一个指针指向空,二、三指针分别指向head和head的next;

    ②这里需要先判断链表元素为1或者0的情况,这里直接返回head本身即可;

    ③2个或者2个以上的元素:第二个指针指向第一个指针、第一个指针重新赋值为第二个指针,第二个指针重新赋值为第三个指针、第三个指针重新赋值为他的下一个指针。

    所以每次逆向的操作都由n1和n2来完成,n3需要每次向后即可,给n2提供保证。 

    ④循环条件是当第三个指针为空就停止,循环停止的时候,第二个指针还没来得及指向第一个指针循环就结束了,这时候需要手动进行指向。 

    代码1:

    /*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
    struct ListNode* reverseList(struct ListNode* head) {// 三指针struct ListNode* n1 = NULL;struct ListNode* n2 = head;if(head ==NULL || head->next == NULL){return head;}struct ListNode* n3 = head->next;// 保存第三个变量while(n3){n2->next = n1;n1 = n2;n2 = n3;n3 = n3->next;}// 此时n3为空,但是n2没有指向n1n2->next = n1;return n2;
    }

    分析2:

            使用一个新链表,遍历老链表,每次遍历一个元素对新链表进行头插,需要注意的是需要将当前指针的下一个元素进行保存,不然无法迭代。

    代码2:

    /*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
    struct ListNode* reverseList(struct ListNode* head) {struct ListNode* newhead = NULL;struct ListNode* curr = head; // 遍历原链表,将元素头插到新链表中while (curr) {struct ListNode* nt = curr->next;curr->next = newhead;newhead = curr;curr = nt;}return newhead;
    }

    http://www.dtcms.com/a/314234.html

    相关文章:

  1. Solidity智能合约开发全攻略
  2. Java基础-斗地主游戏
  3. ArrayDeque双端队列--底层原理可视化
  4. ubuntu修改时区
  5. 【HZ-T536开发板免费体验】Cangjie Magic调用视觉语言大模型(VLM)真香,是不是可以没有YOLO和OCR了?
  6. 通用 PDF 文件流 OCR 到文本 API 接口
  7. 使用 Aspose.OCR 将图像文本转换为可编辑文本
  8. 基础14-Java集合框架:掌握List、Set和Map的使用
  9. 十字滑台:精密制造的“心脏“如何跳动?
  10. 耘瞳科技国产化点云处理软件,开启智能化三维测量新时代
  11. [机器学习]01-构建简单的贝叶斯分类器
  12. 抓包相关知识学习
  13. 项目复盘:Arena Of Furnace
  14. vtkSSAAPass代码解析
  15. [自动化Adapt] 回放策略 | AI模型驱动程序
  16. Python异常捕获全指南
  17. 智慧泵房赋能二次供水互联网化:物联网驱动下的全场景解决方案
  18. Solidity全局变量与安全实践指南
  19. Linux 文件与目录属性管理总结
  20. 设备能力指标(CP/CPK)
  21. C盘空间清理
  22. JVM学习日记(十六)Day16——性能监控与调优(三)
  23. AgxOrin平台JetPack5.x版本fix multi-cam race condition 补丁
  24. 【Conda】常用指令操作
  25. 机器学习——决策树(DecisionTree)+ 过采样 + 交叉验证 案例:电信客户流失数据
  26. VAE学习笔记
  27. Linux 网络深度剖析:传输层协议 UDP/TCP 原理详解
  28. 【STM32】GPIO的输入输出
  29. 正点原子STM32MP257开发板移植ubuntu24.04根文件系统(带桌面版)
  30. Android的UI View是如何最终绘制成一帧显示在手机屏幕上?