数据结构:线性结构之顺序表、链表篇

数据结构:顺序表、链表篇

  • 线性表
  • 一、顺序表
    • (一)顺序表的结构定义
    • (二)顺序表的功能实现
      • 1、初始化
      • 2、销毁
      • 3、扩容
      • 4、插入
      • 5、删除
    • (三)顺序表例题分析
        • 1、删除有序数组中的重复项
        • 2、合并两个有序数组
      • (四)顺序表的弊端
  • 二、链表
    • (一)链表的结构定义
    • (二)链表的功能实现
      • 1、链表的初始化
      • 2、链表的插入
      • 3、链表的删除
      • 4、链表的销毁
    • (三)链表的例题分析
      • 1、移除链表元素
      • 2、反转链表
        • 题目分析
      • 3、链表的中心结点
        • 题目分析
      • 4、合并两个有序链表
        • 题目分析
      • 5、链表的回文结构
        • 题目分析
        • 方法一
        • 方法二
      • 6、相交链表
        • 题目分析
      • 7、环形链表
        • 题目分析
      • 8、环形链表||
        • 题目分析
      • 9、随机链表的复制
        • 题目分析
      • 结束语

线性表

线性表:线性表是具有逻辑结构是连续,物理结构不一定是连续的一类数据结构的集合。链表和顺序表都是线性表

顺序表 : 物理结构连续,逻辑结构连续

链表 : 物理结构不一定连续(动态内存申请的空间可能是连续的,但是一般不会), 逻辑结构连续

一、顺序表

物理地址连续的存储单元依次储存数据结构的线性结构,一般采用数组实现。顺序表分为动态顺序表和静态顺序表,为了防止空间过度浪费,空间不足,我们一般采用动态顺序表。

(一)顺序表的结构定义

typedef int SLdataType;typedef struct SeqList {SLdataType* data;int count, size;
}SeqList;

用到typedef, 可以使得我们的顺序表存放数据的类型更加的灵活。

(二)顺序表的功能实现

1、初始化

void initSeqList(SeqList* SL) {SL->data = NULL;SL->count = SL->size = 0;return;
}

2、销毁

void clearSeqList(SeqList* SL) {if (SL == NULL) return;if (SL->data) free(SL->data);SL->data = NULL;SL->count = SL->size = 0;return;
}

3、扩容

采用 realloc 进行扩容,考虑到原来的容量为 0, 不可单纯的进行乘二

void SLCheckCapacity(SeqList* SL) {if (SL->count == SL->size) {int n = SL->count == 0 ? 4 : 2 * SL->size;SLdataType* temp = (SLdataType*)realloc(SL->data, sizeof(SLdataType) * n);if (temp == NULL) {perror("realloc fail\n");exit(1);}SL->data = temp;SL->size *= n;}return;
}

4、插入

插入操作分为头插、尾插,和任意位置插入。
插入操作需要整体后移 : 从后面像前面遍历,反之会产生数据的覆盖。

//头插
void insertPushFront(SeqList* SL, SLdataType x) {SLCheckCapacity(SL);for (int i = SL->count - 1; i >= 0 ; i--) {SL->data[i + 1] = SL->data[i];}SL->data[0] = x;SL->count += 1;return;
}//尾插
void insertPushBack(SeqList* SL, SLdataType x) {SLCheckCapacity(SL);SL->data[SL->count++] = x;return; 
}//任意位置插入
void insert(SeqList* SL, SLdataType x, int pos) {if (pos < 0 && pos > SL->count) return;SLCheckCapacity(SL);for (int i = SL->count - 1; i >= pos; i--) {SL->data[i + 1] = SL->data[i];}SL->data[pos] = x;SL->count += 1;return;
}

5、删除

删除操作分为头删、尾删,和任意位置删除。
删除操作需要整体前移 : 从前面向后面遍历,反之会产生数据的覆盖。

//头删
void erasePopFront(SeqList* SL) {for (int i = 1; i < SL->count; i++) {SL->data[i - 1] = SL->data[i];}SL->count -= 1;return;
}//尾删
void erasePopBack(SeqList* SL) {assert(SL);assert(SL->count);SL->count -= 1;return;
}//任意位置删除
void erase(SeqList* SL, int pos) {if (pos < 0 && pos >= SL->count) return;for (int i = pos; i < SL->count - 1; i++) {SL->data[i] = SL->data[i + 1];}SL->count -= 1;return;
}

(三)顺序表例题分析

1、删除有序数组中的重复项

https://leetcode.cn/problems/remove-duplicates-from-sorted-array/
在这里插入图片描述

class Solution {
public:int removeDuplicates(vector<int>& nums) {int src = 1, dst = 0;while(src < nums.size()){if(nums[src] == nums[dst]){src += 1;}else{nums[++dst] = nums[src++];}}return dst + 1;}
};

题目中我们用到双指针指针删除重复项,其中while 循环的条件设计十分巧妙

2、合并两个有序数组

https://leetcode.cn/problems/merge-sorted-array/

在这里插入图片描述

小结 : 采用两个指针分别指向两个数组的末尾,依次将数据放在数组一。
while 循环可以用 && 也可以用 || 采用两种代码实现

采用 || 的方式实现


class Solution {
public:void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {int l1 = m - 1, l2 = n - 1, l3 = m + n - 1;while (l1 >= 0 || l2 >= 0) {if (l2 < 0 || (l1 >= 0 && nums1[l1] > nums2[l2]))nums1[l3--] = nums1[l1--];elsenums1[l3--] = nums2[l2--];}}
};

或者采用 && 的方式实现

class Solution {
public:void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {int l1 = m - 1, l2 = n - 1, l3 = m + n - 1;while (l1 >= 0 && l2 >= 0) {if (nums1[l1] > nums2[l2])nums1[l3--] = nums1[l1--];elsenums1[l3--] = nums2[l2--];}while (l2 >= 0) {nums1[l3--] = nums2[l2--];}}
};

(四)顺序表的弊端

1、顺序表的插入删除操作的时间复杂度为O(n)
2、顺序表扩容后任然可能造成空间的浪费,并且顺序表扩容带来性能消耗

二、链表

(一)链表的结构定义

这里也用的typedef 可以使得我们的数据类型更加灵活。

typedef int SLTDataType;typedef struct SListNode {SLTDataType data;struct SListNode* next;
}SLTNode;

(二)链表的功能实现

1、链表的初始化

同顺序表相同,链表在初始化时也采取泛型的方式,适应多种数据类型。

SLTNode* BuyNode(SLTDataType x) {SLTNode* p = (SLTNode*)malloc(sizeof(SLTNode));p->data = x;p->next = NULL;return p;
}

2、链表的插入

链表的插入分为头插、尾插和任意位置插入。任意位置插入时采用双指针定向移动。

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x) {SLTNode* node = BuyNode(x);if (*pphead == NULL) {*pphead = node;return;}SLTNode* p = *pphead;while (p->next) p = p->next;p->next = node;return;
}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x) {SLTNode* node = BuyNode(x);if (*pphead == NULL) {*pphead = node;return;}node->next = *pphead;*pphead = node;return;
}//任意位置之前插入,采用双指针的方式
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {assert(pos);assert(*pphead);SLTNode* node = BuyNode(x);if (*pphead == pos) {node->next = pos;*pphead = node;return;}SLTNode* fast = (*pphead)->next, * slow = *pphead;while (fast != pos) {fast = fast->next;slow = slow->next;}slow->next = node;node->next = fast;return;
}//任意位置之后插入方式会大大简便
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {assert(pos);SLTNode* node = BuyNode(x);node->next = pos->next;pos->next = node;return;
}

3、链表的删除

链表的插入分为头删、尾删和任意位置删除。任意位置删除时采用双指针定向移动。

//尾删
void SLTPopBack(SLTNode** pphead) {assert(*pphead);if (!(*pphead)->next) {free(*pphead);*pphead = NULL;return;}SLTNode* ptail = *pphead;SLTNode* prev = NULL;while (ptail->next){prev = ptail;ptail = ptail->next;}prev->next = NULL;free(ptail);ptail = NULL;return;
}//头删
void SLTPopFront(SLTNode** pphead) {assert(*pphead);SLTNode* node = (*pphead)->next;free(*pphead);*pphead = node;return;
}//任意位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos) {assert(*pphead);assert(pos);if (pos == *pphead) {SLTPopFront(pphead);}else {SLTNode* p = *pphead;while (p->next != pos) {p = p->next;}p->next = pos->next;free(pos);pos = NULL;}return;
}

4、链表的销毁

存储链表的下一个结点,然后进行 free

void SListDestroy(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

(三)链表的例题分析

1、移除链表元素

https://leetcode.cn/problems/remove-linked-list-elements/
在这里插入图片描述

class Solution {
public:ListNode* removeElements(ListNode* head, int val) {ListNode* newhead = NULL, *tail = NULL, *p = head;while(p){if(p->val != val){if(newhead == NULL) {newhead = tail = p;}else{tail->next = p;tail = tail->next;}}p = p->next;}if(tail) tail->next = NULL;return newhead;}
};

小结: 如果 tail 不是NULL, 要将 tail 置空。

2、反转链表

https://leetcode.cn/problems/reverse-linked-list/
在这里插入图片描述

题目分析

在这里插入图片描述

class Solution {
public:ListNode* reverseList(ListNode* head) {if(head == NULL) return NULL;ListNode* pre = NULL, * cur = head, * Next = head->next;while(cur){cur->next = pre;pre = cur;cur = Next;if(!cur) break;Next = Next->next;}return pre;}
};

小结:与另外新建一个链表的方式不同,这种算法可以在原来的链表上进行处理就能达到反转链表的效果。

3、链表的中心结点

https://leetcode.cn/problems/middle-of-the-linked-list/description/
在这里插入图片描述

题目分析

采用快慢指针进行分析,快指针每次走两步,慢指针每次走一步,当
fast = NULL 或者 fast -> next = NULL 时,slow指针指向的就是中间位置的指针。

在这里插入图片描述

class Solution {
public:ListNode* middleNode(ListNode* head) {ListNode* fast, *slow;fast = slow = head;while(fast && fast->next){fast = fast->next->next;slow = slow->next;}return slow;}
};

小结: 用快慢指针有很多好处,这道题的中间值就是一个

4、合并两个有序链表

https://leetcode.cn/problems/merge-two-sorted-lists/description/
在这里插入图片描述

题目分析

这道题的思路并不困难,主要是学一种新的头节点创建方式,通过 malloc 申请内存来获得头节点。

class Solution {
public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {ListNode* newhead, * tail;newhead = tail = (ListNode*)malloc(sizeof(ListNode));newhead->next = NULL;while(list1 && list2){if(list1->val < list2->val){tail->next = list1;tail = tail ->next;list1 = list1->next;}else{tail->next = list2;tail = tail->next;list2 = list2->next;}}if(list1) tail->next = list1;if(list2) tail->next = list2;ListNode* ret = newhead->next;free(newhead);newhead = NULL;return ret;}
};

小结:这道题可以有三种方式创建头结点
1、直接开辟变量 Node head, 返回head->next;
2、创建两个指针Node* head, * tail;
3、用 malloc 开辟空间,返回malloc 的下一个结点, 记得要free;

5、链表的回文结构

https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&qru=/ta/2016test/question-ranking
在这里插入图片描述

题目分析

这道题有两个思路:
方法一 : 采用数组,将链表的结点数据依次放入数组之中,然后创建两个指针向中间移动,依次比较。但是创建数组的时间复杂度为O(n),不可以通过。
方法二 : 中间结点后面的结点进行反转,切记反转链表反转的是指针的方向,数据位置没有变。然后同一。

方法一
class PalindromeList {
public:bool chkPalindrome(ListNode* A) {int arr[1000], i = 0;ListNode* p = A;while (p) {arr[i++] = p->val;p = p->next;}int left = 0, right = i - 1;while(left <= right) {if(arr[left++] != arr[right--]) return false;}return true;}
};
方法二

在这里插入图片描述

这种在原链表上进行修改的反转操作有妙用

6、相交链表

https://leetcode.cn/problems/intersection-of-two-linked-lists/description/
在这里插入图片描述

题目分析

将长的链表先截成和短的链表的长度,因为是后面部分相交,挨个比较直到两个指针的地址相等为相交结点

class Solution {
public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {ListNode* p = headA;int lenA = 0, lenB = 0;while(p){p = p->next;lenA += 1;}p = headB;while(p){p = p->next;lenB += 1;}int length = abs(lenA - lenB);ListNode* longlist = headA;ListNode* shortlist = headB;if(lenA < lenB){longlist = headB;shortlist = headA;}while(length--){longlist = longlist->next;}while(longlist != shortlist){longlist = longlist->next;shortlist = shortlist->next;}return shortlist;}
};

7、环形链表

https://leetcode.cn/problems/linked-list-cycle/
在这里插入图片描述

题目分析

用快慢指针,如果有环,那么快指针就会追上慢指针。

问题一 : 快指针为什么一定会追上慢指针
因为每次追逐两个指针的距离都会减一

问题二:快指针每次可以走2, 3, 4 ~步吗
下面我们以快指针每次走三步为例,结果是一定相遇,其他推理结论相同
在这里插入图片描述

class Solution {
public:bool hasCycle(ListNode *head) {ListNode* fast ,* slow;fast = slow = head;while(fast && fast->next){fast = fast->next->next;slow = slow->next;if(fast == slow) return true;}return false;}
};

小结:上面的文章里面,我们用快慢指针找中间结点,现在又多了一种新的用法,用来判断是否有环。

8、环形链表||

https://leetcode.cn/problems/linked-list-cycle-ii/description/
在这里插入图片描述

题目分析

在相遇之后,相遇点和头结点到环的起始点的距离相等,用两个指针从这两个位置同时出发,直到相遇。
在这里插入图片描述

9、随机链表的复制

https://leetcode.cn/problems/copy-list-with-random-pointer/description/在这里插入图片描述

题目分析

如下图
在这里插入图片描述
步骤一 : 添加复制的结点
在这里插入图片描述
步骤二: 给random 赋值
步骤三: 断开原来的链表和拷贝链表
在这里插入图片描述
在这里插入图片描述

class Solution {
public:Node* BuyNode(int val) {Node* p = (Node*)malloc(sizeof(Node));p->val = val;p->next = p->random = NULL;return p;}void AddNode(Node* head) {Node *pcur = head, *next;while (pcur) {next = pcur->next;Node* node = BuyNode(pcur->val);pcur->next = node;node->next = next;pcur = next;}return;}Node* SetRandom(Node* head) {Node* pcur = head;while (pcur) {Node* temp = pcur->next;if (pcur->random) {temp->random = pcur->random->next;}pcur = pcur->next->next;}return head;}Node* getNewLinkList(Node* head) {Node *newHead, *tail;Node* pcur = head;newHead = tail = head->next;while (pcur) {pcur->next = tail->next;if (tail->next) {tail->next = pcur->next->next;tail = tail->next;}pcur = pcur->next;}// tail->next = NULL; // 确保新链表的尾部正确return newHead;}Node* copyRandomList(Node* head) {if (head == NULL)return NULL;AddNode(head);head = SetRandom(head);head = getNewLinkList(head);return head;}
};

小结:无需多言,值得反复学习

结束语

好了,小编也要睡觉了,下一篇小编会带来双向链表的博文。如果感兴趣的话记得要给博主一个关注哦,不然就再也找不到啦,小伙伴们周末快乐!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/402884.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据技术——DolphinScheduler的集群部署

目录 第1章 DolphinScheduler简介 1.1 DolphinScheduler概述 1.2 DolphinScheduler核心架构 第2章 DolphinScheduler部署说明 2.1 软硬件环境要求 2.1.1 操作系统版本要求 2.1.2 服务器硬件要求 2.2 部署模式 2.2.1 单机模式 2.2.2 伪集群模式 2.2.3 集群模式 第3章…

进程间通信—无名管道

gg shiftg快速对齐 加锁顺序问题时&#xff0c;如果解锁了&#xff0c;两个同时申请抢锁&#xff0c;谁抢到了运行谁&#xff0c;循环迭代时释放锁也是同时申请锁&#xff0c;循环部分如果没抢到锁就进入循环等待 总结: IPC 进程间通信 interprocess communicate //signal…

免费简单的制作3D卡通建模——Fuse软件和Readyplayer的使用介绍

最终效果 文章目录 最终效果一、使用Fuse软件去Steam下载安装捏人选择身体部位自定义人物细节参数换装贴图修改导出OBJ文件即可 二、使用ReadyplayerReadyplayer官网地址选择从模板开始&#xff0c;或者拍照选择图片进行捏脸将模型导入Unity通过Readyplayer官方插件导入模型通过…

UI设计:蒸汽波风格页面有啥特征,应用哪些场景?

一、什么是蒸汽波风格 蒸汽波风格&#xff08;Steampunk&#xff09;是一种将19世纪工业时代的技术和想象力与未来科技相结合的艺术和文化流派。它通常描绘了一个类似维多利亚时代的世界&#xff0c;其中蒸汽动力是主要能源&#xff0c;机械装置和复杂的齿轮系统被广泛应用。 …

若依服务器上云部署

准备条件&#xff1a;安装好mysql和redis并配置好密码。 1.安装JDK&#xff0c;我这里使用的是1.8 wget --no-check-certificate --no-cookies --header "Cookie: oraclelicenseaccept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u131-b11/…

idea 对于mybatis-plus框架JRebelX和XRebel热启动失效问题

1.mybatis-plus不需要使用热启动插件&#xff0c;修改完代码后&#xff0c;直接重新编译一下即可&#xff0c;不需要重启 2.如果是mapper.xml文件&#xff0c;则直接安装JRebel MybatisPlus extension 插件即可完成mapper.xml静态文件更改进行热加载

XSS小游戏(题目+解析)DOM破坏!!!

文章目录 一、Ma Spaghet!二、Jefff三、Ugandan Knuckles四、Ricardo Milos五、Ah Thats Hawt六、Ligma七、Mafia方法一&#xff1a;可以用匿名函数来试试方法二&#xff1a;利用toString方法方法三&#xff1a;利用location和hash切片slice 八、Ok, Boomer九、svg十、DOM破坏十…

阿里巴巴Arthas详解

Arthas 是 Alibaba 在 2018 年 9 月开源的 Java 诊断工具。支持 JDK6&#xff0c; 采用命令行交互模式&#xff0c;可以方便的定位和诊断线上程序运行问题。Arthas 官方文档十分详细&#xff0c;详见&#xff1a;arthas Arthas使用场景 得益于 Arthas 强大且丰富的功能&#x…

基于 Appium 的 App 爬取实战

除了运行 Appium 的基本条件外&#xff0c;还要一个日志输出库 安装&#xff1a; pip install loguru 思路分析 首先我们观察一下整个 app5 的交互流程&#xff0c;其首页分条显示了电影数据&#xff0c; 每个电影条目都包括封面&#xff0c;标题&#xff0c; 类别和评分 4…

EMQX Platform Snowflake:构建可再生分布式能源的智慧未来

引言 可再生能源如风力和太阳能发电&#xff0c;具有低成本和环保的特性&#xff0c;是未来能源供应的主要方向。然而&#xff0c;这类发电方式存在供应分散、设备数量多、地区分布广等特点。再加上不同地区的季节和天气变化&#xff0c;不确定性极大。 随着社会用电需求的持…

移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——5.string(模拟实现)

1.存储结构 namespace zone {class string{public:private: //设置私有&#xff0c;不允许随便访问底层数据char* _str; //字符串存储空间首地址指针size_t _size; //当前字符数量size_t _capaicty; //可用容量static const size_t npos;}const size_t string::nops -1;//在类…

【C++STL详解(十一)】map/set/multimap/multiset的介绍与使用

目录 一、关联式容器 二、键值对 三、set 介绍 简单使用 1.构造 2.相关迭代器 3.容量 4.修改 四、multiset 五、map 介绍 使用 1.定义的方式 2.迭代器相关 3.容量与operator【】(重点) 4.修改 小总结&#xff1a; 六、multimap 一、关联式容器 在CSTL中…

硬件面试经典 100 题(51~70 题)

51、请列举您知道的覆铜板厂家。 生益、建滔。 52、示波器铭牌一般都会标识两个参数&#xff0c;比如泰克 TDS1002B 示波器标识的 60MHz 和 1GS/s&#xff0c;请解释这两个参数的含义。 60MHz 是指示波器的带宽&#xff0c;即正常可以测量 60MHz 频率以下的信号。 1GS/s 是指示…

MySQL进阶难度知识点分析

以下为本人在阅读《MySQL是怎样运行的&#xff1a;从根儿上理解MySQL》这本书时对一些难度和重点的笔记&#xff0c;主要用于个人学习使用&#xff0c;内容可能存在出入&#xff0c;望理性食用~ 1. sql执行流程 一条sql的执行流程大致可分为客户端获取与数据库服务器的连接&am…

【JavaEE精炼宝库】网络原理基础——UDP详解

文章目录 一、应用层二、传输层2.1 端口号&#xff1a;2.2 UDP 协议&#xff1a;2.2.1 UDP 协议端格式&#xff1a;2.2.2 UDP 存在的问题&#xff1a; 2.3 UDP 特点&#xff1a;2.4 基于 UDP 的应用层协议&#xff1a; 一、应用层 我们 Java 程序员在日常开发中&#xff0c;最…

【排序篇】插入排序与选择排序

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;Linux专栏&#xff1a;Linux &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 文章目录 1. 排序的概念及其应用1.1 排序的概念1.2 排序的应用场景1.3 常见的排序算法 2.常…

Vue3+vite+ts 项目使用mockjs

1、安装mockjs npm i mockjs 2、安装vite-plugin-mock npm i vite-plugin-mock -D 3、安装axios npm i axios 4.在src目录下创建mock文件夹,在文件夹内创建login.ts等文件&#xff0c;并在文件夹内放置以下内容&#xff08;注&#xff1a;URL要和真实请求地址保持一致&am…

走向绿色:能源新选择,未来更美好

当前&#xff0c;全球范围内可再生能源正经历着从辅助能源向核心能源的深刻转型&#xff0c;绿色能源日益渗透至居住、出行、日常应用等多个领域&#xff0c;深刻影响着我们的生活方式&#xff0c;使我们能够更加充分地体验清洁能源所带来的优质生活。 一、绿色能源与“住” …

搭建知识中台:让企业告别低效率

在当今这个信息爆炸、知识更新日新月异的时代&#xff0c;企业面临着前所未有的挑战与机遇。如何在浩瀚的信息海洋中高效筛选、整合并利用知识资源&#xff0c;成为决定企业竞争力的关键因素之一。因此&#xff0c;搭建知识中台&#xff0c;构建企业知识管理的核心枢纽&#xf…

day 28 HTTP协议

一、TCP粘包问题 TCP发送数据是连续的&#xff0c;两次发送的数据可能粘连成一包被接收到 解决粘包问题方法&#xff1a; 1.接收指定长度&#xff1a;&#xff08;不稳定&#xff09; 2.睡眠&#xff1a;&#xff08;效率低&#xff09; 让每次…