顺序表和链表(详解)

线性表

线性表( linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
注意我们学习数据结构也就是学习增删查改,后期会围绕这四个字进行讲解,本文的讲解会把基础模拟实现部分讲的很透彻,算法oj题可以基于基础知识向外扩展,有了基础数据结构理解相信算法oj题也很好拿下了

顺序表

2.1 概念及结构
顺序表是用一段 物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储元素
2. 动态顺序表:使用动态开辟的数组存储。
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。

接口实现

我们一般需要定义3个变量,数组,容量和实际数据数量,因为要方便后续扩容操作

下面我会把每个接口的意义讲解,这部分需要结构体和动态内存的基础,我前边讲的很详细,可以去看看每个函数都会断言,所以我们不会每个都讲,但是大家一定要明白断言的意义,一般是防止对空指针操作和检查越界

顺序表的初始化,默认先开辟一块4个整形(我们假设存储整形)的空间,size和capacity置为0。

销毁,先释放malloc的空间,将指针置空,容量和size置0

打印数据,也就是遍历这个数组很简单,用下标访问数据

检查扩容操作,当实际数据数量等于容量,要扩容,当我们插入数据时,先判断是否需要扩容,再插入,这样就能实现简易版的vector了(注意要断言是因为防止传空指针)
pos是要插入的下标,end最后一个数据下标,size是实际数据个数
插入逻辑,要记得断言下标要大于等于0并且小于等于size,下标是0相当于头插,下标是size相当于尾插,这部分一定要画图,画图以后这个插入逻辑也就是挪动覆盖,下标<size是随机插入,下标=size是尾插
删除逻辑,断言+挪动数据覆盖逻辑

 

尾插很简单,(注释的部分和没注释的是两个版本,可以用常规思路扩容+尾插,也可以直接复用插入逻辑,把参数改成最后一个下标就行)

尾删,要记得先断言检查下标,有效数据个数一定要大于0才能尾删,也可以直接复用删除逻辑

头删很简单,可以直接复用插入逻辑,参数给0就行

头删也很简单,复用删除逻辑

修改逻辑很简单,一行代码,可是为什么这一行代码也封装成一个函数呢,因为我们最好不要直接访问数组数据,缺少断言检查,用这个函数可以防止我们传越界的参数

以上就是关于顺序表实现的全部逻辑,希望能对大家有用

顺序表实现的完整代码

seqlist.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>// 静态顺序表
//#define N 1000
//typedef int SLDataType;
//
//struct SeqList
//{
//	SLDataType a[N];
//	int size;
//};// 动态顺序表
typedef int SLDataType;typedef struct SeqList
{SLDataType* a;int size;        // 存储有效数据个数int capacity;    // 空间大小
}SL;// 管理数据 -- 增删查改
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);
void SLCheckCapacity(SL* ps);// 头插头删 尾插尾删
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);// 返回下标,没有找打返回-1
int SLFind(SL* ps, SLDataType x);// 在pos位置插入x
void SLInsert(SL* ps, int pos, SLDataType x);
// 删除pos位置的值
void SLErase(SL* ps, int pos);void SLModify(SL* ps, int pos, SLDataType x);

seqlist.c

#include"seqlist.h"void SLInit(SL* ps)
{assert(ps);ps->a = (SLDataType*)malloc(sizeof(SLDataType) * 4);if (ps->a == NULL){perror("malloc failed");exit(-1);//return;}ps->size = 0;ps->capacity = 4;
}void SLDestroy(SL* ps)
{assert(ps);free(ps->a);ps->a = NULL;ps->capacity = ps->size = 0;
}void SLPrint(SL* ps)
{assert(ps);for (int i = 0; i < ps->size; i++){printf("%d ", ps->a[i]);}printf("\n");
}void SLCheckCapacity(SL* ps)
{assert(ps);// 满了要扩容if (ps->size == ps->capacity){SLDataType* tmp = (SLDataType*)realloc(ps->a, ps->capacity * 2 * (sizeof(SLDataType)));if (tmp == NULL){perror("realloc failed");exit(-1);}ps->a = tmp;ps->capacity *= 2;}
}// 17:25继续
void SLPushBack(SL* ps, SLDataType x)
{assert(ps);/*SLCheckCapacity(ps);ps->a[ps->size] = x;ps->size++;*/SLInsert(ps, ps->size, x);
}void SLPopBack(SL* ps)
{assert(ps);// 温柔的检查//if (ps->size == 0)//return;// 暴力的检查//assert(ps->size > 0);ps->a[ps->size - 1] = 0;//ps->size--;SLErase(ps, ps->size - 1);
}void SLPushFront(SL* ps, SLDataType x)
{assert(ps);//SLCheckCapacity(ps);挪动数据//int end = ps->size - 1;//while (end >= 0)//{//	ps->a[end + 1] = ps->a[end];//	--end;//}//ps->a[0] = x;//ps->size++;SLInsert(ps, 0, x);
}void SLPopFront(SL* ps)
{assert(ps);/*assert(ps->size > 0);int begin = 1;while (begin < ps->size){ps->a[begin - 1] = ps->a[begin];++begin;}ps->size--;*/SLErase(ps, 0);
}int SLFind(SL* ps, SLDataType x)
{assert(ps);for (int i = 0; i < ps->size; i++){if (ps->a[i] == x){return i;}}return -1;
}// 在pos位置插入x
void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);assert(pos >= 0 && pos <= ps->size);SLCheckCapacity(ps);int end = ps->size - 1;while (end >= pos){ps->a[end + 1] = ps->a[end];--end;}ps->a[pos] = x;ps->size++;
}// 删除pos位置的值
void SLErase(SL* ps, int pos)
{assert(ps);assert(pos >= 0 && pos < ps->size);int begin = pos + 1;while (begin < ps->size){ps->a[begin - 1] = ps->a[begin];++begin;}ps->size--;
}void SLModify(SL* ps, int pos, SLDataType x)
{assert(ps);assert(pos >= 0 && pos < ps->size);ps->a[pos] = x;
}

还有一些测试用例,感兴趣的可以尝试运行一下,已经测试过了代码都是对的

#include<stdio.h>
#include"seqlist.h"void TestSeqList1()
{SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPushBack(&sl, 5);SLPushBack(&sl, 6);SLPushBack(&sl, 6);SLPushBack(&sl, 0);SLPushBack(&sl, 0);SLPrint(&sl);SLPopBack(&sl);SLPopBack(&sl);SLPrint(&sl);SLPopBack(&sl);SLPopBack(&sl);SLPopBack(&sl);SLPopBack(&sl);SLPopBack(&sl);SLPopBack(&sl);SLPopBack(&sl);//SLPopBack(&sl);//SLPopBack(&sl);/*SLPopBack(&sl);SLPopBack(&sl);SLPopBack(&sl);SLPopBack(&sl);*/SLPrint(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPrint(&sl);SLDestroy(&sl);
}void TestSeqList2()
{SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPushBack(&sl, 5);SLPrint(&sl);SLPushFront(&sl, 10);SLPushFront(&sl, 20);SLPushFront(&sl, 30);SLPushFront(&sl, 40);SLPrint(&sl);SLDestroy(&sl);
}void TestSeqList3()
{SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPushBack(&sl, 5);SLPrint(&sl);SLPopFront(&sl);SLPopFront(&sl);SLPrint(&sl);SLPopFront(&sl);SLPopFront(&sl);SLPopFront(&sl);//SLPopFront(&sl);SLPrint(&sl);SLPushBack(&sl, 4);SLPushBack(&sl, 5);SLPrint(&sl);SLDestroy(&sl);
}void TestSeqList4()
{SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPushBack(&sl, 5);SLPushFront(&sl, -1);SLPushFront(&sl, -2);SLPrint(&sl);SLInsert(&sl, 3, 40);SLPrint(&sl);int x;scanf("%d", &x);int pos = SLFind(&sl, x);if (pos != -1){SLInsert(&sl, pos, x * 10);}SLPrint(&sl);SLDestroy(&sl);}void TestSeqList5()
{SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPushBack(&sl, 5);SLPrint(&sl);SLErase(&sl, 2);SLPrint(&sl);int x;scanf("%d", &x);int pos = SLFind(&sl, x);if (pos != -1){SLErase(&sl, pos);}SLPrint(&sl);SLDestroy(&sl);}void TestSeqList6()
{SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPushBack(&sl, 5);SLPrint(&sl);SLModify(&sl, 2, 20);sl.a[2] = 20;SLPrint(&sl);/*int x;scanf("%d", &x);int pos = SLFind(&sl, x);if (pos != -1){SLModify(&sl, pos, x*10);}SLPrint(&sl);*/int pos, x;scanf("%d%d", &pos, &x);//sl.a[pos] = x;SLModify(&sl, pos, x);SLPrint(&sl);SLDestroy(&sl);
}void TestSeqList7()
{SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPushBack(&sl, 5);SLPrint(&sl);SLPopFront(&sl);SLDestroy(&sl);
}int main()
{TestSeqList1();return 0;
}

相关oj题

27. 移除元素 - 力扣(LeetCode)

26. 删除有序数组中的重复项 - 力扣(LeetCode)

88. 合并两个有序数组 - 力扣(LeetCode)

顺序表的讲解基本结束,还有顺序表和链表的对比,在STL我已经写过了,可以看看是一个道理

链表

链表的概念及结构
概念:链表是一种 物理存储结构上非连续、非顺序的存储结构,数据元素的 逻辑顺序是通过链表中的 指针链接次序实现的 。
现实中链表存放的都是malloc出来的地址,是随机值,但是也不排除会有这种很规律的取值
我们要注意单链表是没有哨兵位,且只有一个next后继指针,头指针存放第一个节点的地址,后边的内存块如图所示,每个内存块存放有下一个内存块的地址,最后一个内存块存放NULL(这个是规则)

链表的分类

别看有这么多种链表,其实实际常用的也就是单链表和双向带头循环链表
其实学会这两种链表就行,单链表适合做oj题,双向链表适合存储数据
带头双向循环链表由哨兵位,哨兵位可以理解为不存有效数据的第一个头结点,遍历只会遍历有效数据结点,只有一个哨兵位等价于链表为NULL

单链表的实现

 

注意单链表部分一定要学会传二级指针,因为我们要改变结构体指针的内容(当链表为空),因为没有哨兵位,只有一个头指针,后边的带头双向循环链表由哨兵位的头结点,就不用传二级指针了(单链表的增删查改的情况有些多,大致分为链表为空或者链表非空来讨论)

链表内部存储两个数据,一个是值x,一个是结构体指针*next(用来存放下一个结点的地址)

打印操作,参数是结构体变量的地址,拿到数据向后遍历,最后一个数据next为空,停下来

这个函数用来申请一个新节点的空间,并且初始化为NULL和x,类似c++的new

尾插操作,参数要传二级指针(也就是头结点指针的地址,这样在链表第一个节点为空的时候可以直接尾插)

链表不为空,要多定义一个尾指针,遍历链表到最后一个,将NULL改为新new处结点的地址,就完成链表的插入了(道理很简单,实在没想明白可以画图,数据结构部分就是要多画图才能学好)

头删很简单,先断言plist不能是NULL,空链表没必要删除了

先保存要删除结点的下一个结点地址,然后再free掉要删除的节点,并且让头指针指向新的首结点

尾删结点,是空链表或者只有一个结点的链表很简单,当两个以上的时候定义一个前驱指针tailprev,遍历链表,当tail的next是NULL的时候,跳出循环并且free掉尾指针指向的结点,将前驱指针的next置空,这样就完成了尾删很简单

头插很简单,先new(并不是c++的new,只是我想这样写)一个新节点,将新节点的next存放第一个结点的地址,plist存放新节点的地址,就完成头插

查询某个值,很简单遍历一下,找到就返回下标,没找到就返回NULL

在pos位置之前插入,如果我们要在第一个结点前插入,直接复用头插逻辑,其他情况我们要定义一个前驱指针prev,用来指向pos位置之前的结点,当prev指向要插入位置之前结点的时候,跳出循环,链接数据。很简单

删除pos位置的值,和插入逻辑相似,删除第一个节点直接复用头删逻辑,其他情况定义一个前驱指针,当prev指向pos之前的结点时,链接要删除节点左右节点,删除pos结点并且置空(也可以不置空,因为栈帧到此会销毁结束,我们也访问不到了)

以上就是我对单链表的理解,希望能帮到大家

单链表完整源码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLTPrint(SLTNode* phead);SLTNode* BuySListNode(SLTDataType x);
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//SLTNode* SLTPushBack(SLTNode* phead, SLTDataType x);void SLTPushFront(SLTNode** pphead, SLTDataType x);void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);SLTNode* SLTFind(SLTNode* phead, SLTDataType x);// 在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);// 删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos);

#include"slist.h"
void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;//while (cur != NULL)while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}SLTNode* BuySListNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}// 16:07继续
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySListNode(x);if (*pphead == NULL){// 改变的结构体的指针,所以要用二级指针*pphead = newnode;}else{SLTNode* tail = *pphead;while (tail->next != NULL){tail = tail->next;}// 改变的结构体,用结构体的指针即可tail->next = newnode;}
}void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;
}void SLTPopBack(SLTNode** pphead)
{assert(pphead);// 1、空assert(*pphead);// 2、一个节点// 3、一个以上节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLTNode* tailPrev = NULL;SLTNode* tail = *pphead;while (tail->next){tailPrev = tail;tail = tail->next;}free(tail);//tail = NULL;tailPrev->next = NULL;}
}void SLTPopFront(SLTNode** pphead)
{assert(pphead);// 空assert(*pphead);// 非空SLTNode* newhead = (*pphead)->next;free(*pphead);*pphead = newhead;
}SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySListNode(x);prev->next = newnode;newnode->next = pos;}
}// 删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);if (pos == *pphead){SLTPopFront(pphead);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);//pos = NULL;}
}

#define _CRT_SECURE_NO_WARNINGS 1
#include"slist.h"void TestSList1()
{int n;printf("请输入链表的长度:");scanf("%d", &n);printf("\n请依次输入每个节点的值:");SLTNode* plist = NULL;for (size_t i = 0; i < n; i++){int val;scanf("%d", &val);SLTNode* newnode = BuySListNode(val);// 头插newnode->next = plist;plist = newnode;}SLTPrint(plist);SLTPushBack(&plist, 10000);SLTPrint(plist);
}void TestSList2()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPushBack(&plist, 5);SLTPrint(plist);SLTPushFront(&plist, 10);SLTPushFront(&plist, 20);SLTPushFront(&plist, 30);SLTPushFront(&plist, 40);SLTPrint(plist);
}void TestSList3()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPushBack(&plist, 5);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);SLTPopBack(&plist);SLTPrint(plist);// SLTPopBack(&plist);// SLTPrint(plist);
}void TestSList4()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPushBack(&plist, 5);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);SLTPrint(plist);SLTPopFront(&plist);//SLTPopFront(&plist);SLTPrint(plist);
}void TestSList5()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPushBack(&plist, 5);SLTPrint(plist);SLTNode* pos = SLTFind(plist, 40);if (pos){pos->data *= 10;}SLTPrint(plist);int x;scanf("%d", &x);pos = SLTFind(plist, x);if (pos){SLTInsert(&plist, pos, x * 10);}SLTPrint(plist);
}int main()
{TestSList1();return 0;
}

双向链表的实现

带头双向循环链表,有前驱指针,后继指针和数据

最基础的申请新结点空间,赋值并且置空,后期new的雏形,申请空间+初始化都干了

这个是哨兵位头结点,会返回给外边的plist用来控制增删查改逻辑

打印逻辑,遍历链表并且打印值

在某个位置之前插入

pos之前插入很简单,定义一个posprev前驱指针,将newnode链接上去

牛逼的地方是,传哨兵位的下一个节点也就是头结点的指针,就成为头插了;传哨兵位指针,完成尾插,因为哨兵位的前一个节点就是整个链表最后一个数据

尾删

头删

删除逻辑很简单将要删除结点的前结点和后结点保存,删除pos位置节点,然后链接左右节点

尾插直接复用插入逻辑,传哨兵位指针过去就行,完成尾插,当然也可以自己实现,但是建议直接复用,节省代码

头插也就是在哨兵位下一个之前插入,直接复用

尾部删除,注意要断言(,当只有哨兵位结点时不能删除,此时有效数据为0),直接复用删除逻辑

头删直接复用删除逻辑,删除哨兵位下一个结点就是头删

最后还有一个求有效数据个数的代码,很简单,遍历链表到哨兵位结束也就是计算了有效数据(除哨兵位)

以上就是我对双向链表的理解,很透彻希望对大家有用

双向链表模拟实现完整代码

#include"List.h"LTNode* BuyLTNode(LTDataType x)
{LTNode* node = (LTNode*)malloc(sizeof(LTNode));if (node == NULL){perror("malloc fail");exit(-1);}node->data = x;node->next = NULL;node->prev = NULL;return node;
}LTNode* LTInit()
{LTNode* phead = BuyLTNode(0);phead->next = phead;phead->prev = phead;return phead;
}void LTPrint(LTNode* phead)
{assert(phead);printf("phead<=>");LTNode* cur = phead->next;while (cur != phead){printf("%d<=>", cur->data);cur = cur->next;}printf("\n");
}void LTPushBack(LTNode* phead, LTDataType x)
{assert(phead);LTInsert(phead, x);
}void LTPopBack(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTErase(phead->prev);
}void LTPushFront(LTNode* phead, LTDataType x)
{assert(phead);LTInsert(phead->next, x);
}void LTPopFront(LTNode* phead)
{assert(phead);assert(phead->next != phead);LTErase(phead->next);
}int LTSize(LTNode* phead)
{assert(phead);int size = 0;LTNode* cur = phead->next;while (cur != phead){++size;cur = cur->next;}return size;
}// pos֮ǰx
void LTInsert(LTNode* pos, LTDataType x)
{assert(pos);LTNode* posPrev = pos->prev;LTNode* newnode = BuyLTNode(x);posPrev->next = newnode;newnode->prev = posPrev;newnode->next = pos;pos->prev = newnode;
}// ɾposλ
void LTErase(LTNode* pos)
{assert(pos);LTNode* posPrev = pos->prev;LTNode* posNext = pos->next;free(pos);posPrev->next = posNext;posNext->prev = posPrev;
}

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int LTDataType;
typedef struct ListNode
{struct ListNode* next;struct ListNode* prev;LTDataType data;
}LTNode;LTNode* BuyLTNode(LTDataType x);
LTNode* LTInit();
void LTPrint(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);int LTSize(LTNode* phead);LTNode* LTFind(LTNode* phead, LTDataType x);// pos֮ǰx
void LTInsert(LTNode* pos, LTDataType x);
// ɾposλ
void LTErase(LTNode* pos);

#include"List.h"void TestList1()
{LTNode* plist = LTInit();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPushBack(plist, 5);LTPrint(plist);LTPushFront(plist, 10);LTPushBack(plist, 10);LTPrint(plist);
}void TestList2()
{LTNode* plist = LTInit();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPushBack(plist, 5);LTPrint(plist);LTPopBack(plist);LTPopFront(plist);LTPrint(plist);LTPopFront(plist);LTPopFront(plist);LTPopFront(plist);//LTPopFront(plist);LTPrint(plist);
}void TestList3()
{LTNode* plist = LTInit();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPushBack(plist, 5);LTPrint(plist);LTPushFront(plist, 10);LTPushFront(plist, 20);LTPushFront(plist, 30);LTPushFront(plist, 40);LTPrint(plist);
}void TestList4()
{LTNode* plist = LTInit();LTPushBack(plist, 1);LTPushBack(plist, 2);LTPushBack(plist, 3);LTPushBack(plist, 4);LTPushBack(plist, 5);LTPrint(plist);LTPopFront(plist);LTPrint(plist);LTPopBack(plist);LTPrint(plist);
}int main()
{TestList4();return 0;
}

链表面试oj题

203. 移除链表元素 - 力扣(LeetCode)

206. 反转链表 - 力扣(LeetCode)

876. 链表的中间结点 - 力扣(LeetCode)

21. 合并两个有序链表 - 力扣(LeetCode)

链表分割_牛客题霸_牛客网

链表的回文结构_牛客题霸_牛客网

160. 相交链表 - 力扣(LeetCode)

141. 环形链表 - 力扣(LeetCode)

142. 环形链表 II - 力扣(LeetCode)

138. 随机链表的复制 - 力扣(LeetCode)

题目有点多,都是经典链表的题,可以研究一下(有了基础内容的讲解,算法题可以尝试完成)

顺序表和链表的区别

打对钩的是优点,错误是缺点,可以了解一下,因为各有各的优势,在不同场景要用不同的数据结构

以上就是我对数据结构链表和顺序表的全部理解,以后会创作更多用心作品(目录有对应的模块,需要什么看什么)

感谢大家的支持!!!

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

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

相关文章

初阶5 排序

本章重点 排序的概念常见排序的算法思想和实现排序算法的复杂度以及稳定性分析 1.排序的概念 排序: 所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。稳定性: 假定在待排序的记录序列中&#xff0…

Flink底层架构与运行流程

这张图展示了Flink程序的架构和运行流程。 主要组件及功能&#xff1a; Flink Program&#xff08;Flink程序&#xff09;&#xff1a; 包含Program code&#xff08;程序代码&#xff09;&#xff0c;这是用户编写的业务逻辑代码。经过Optimizer / Graph Builder&#xff08…

MyBatis和JPA区别详解

文章目录 MyBatis和JPA区别详解一、引言二、设计理念与使用方式1、MyBatis&#xff1a;半自动化的ORM框架1.1、代码示例 2、JPA&#xff1a;全自动的ORM框架2.1、代码示例 三、性能优化与适用场景1、MyBatis&#xff1a;灵活的SQL控制1.1、适用场景 2、JPA&#xff1a;开发效率…

计算机视觉——Intel RealSense D435的使用及python环境下的实现

什么是深度相机&#xff0c;以及深度相机的分类和工作原理 ​ 深度相机是一种能够捕捉场景中物体的深度信息&#xff08;即物体与相机之间的距离&#xff09;的设备。与传统的二维相机不同&#xff0c;深度相机除了拍摄图像的颜色和亮度外&#xff0c;还能生成一个关于场景中每…

Servlet快速入门

Servlet 由于目前主流使用SpringBoot进行开发Servlet可以说是时代的眼泪&#xff0c;这篇文章主要介绍我基于SpringBoot对应Servlet的浅薄认知&#xff0c;有利于更好的理解前端界面和java服务器的数据交换过程 快速入门 我比较推荐这篇文章来对Servlet有一个大概的了解 都2…

windows平台intel-vpl编译

需要先在本机编译好opencl库 git clone --recursive https://github.com/KhronosGroup/OpenCL-SDK.git cmake -A x64 -T v143 -D OPENCL_SDK_BUILD_OPENGL_SAMPLESOFF -B OpenCL-SDK\build -S OpenCL-SDKcmake --build OpenCL-SDK\build --config Releasecmake --install O…

MTK MT6890:LCD ST7789P3驱动移植调试

一、功能简述 LK阶段:开机logo、关机充电动画 Kernel阶段:开机logo、GUI用户交互界面 二、硬件连接及器件选型 ST7789P3 240RGB * 320 dot 262K Color TFT屏 SPI-II型panel ST7789P3接主控MT6890平台的DBI-C接口 SPI-II型读时序: 写时序: GPIO206: DISP_PWM (Func1) …

Vscode配置continue运行ollama部署的Qwen2.5

Vscode配置continue运行ollama部署的Qwen2.5 1.安装Continue插件 离线安装Continue访问下面网址下载插件&#xff1a;continue插件下载地址 将continue窗口迁右边&#xff08;根据个人习惯&#xff0c;可选&#xff09; 点击Continue图标会出CONTINUE窗口&#xff0c;鼠标选…

62,【2】 BUUCTF WEB [强网杯 2019]Upload1

进入靶场 此处考点不是SQL&#xff0c;就正常注册并登录进去 先随便传一个 进行目录扫描&#xff0c;我先用爆破代替 先随便后面写个文件名 为了提供payload位置 www.tar.gz真的存在 返回浏览器修改url就自动下载了 看到tp5,应该是ThinkPHP5框架 参考此博客的思路方法c[强网杯…

SpringCloud微服务Gateway网关简单集成Sentinel

Sentinel是阿里巴巴开源的一款面向分布式服务架构的轻量级流量控制、熔断降级组件。Sentinel以流量为切入点&#xff0c;从流量控制、熔断降级、系统负载保护等多个维度来帮助保护服务的稳定性。 官方文档&#xff1a;https://sentinelguard.io/zh-cn/docs/introduction.html …

高效安全文件传输新选择!群晖NAS如何实现无公网IP下的SFTP远程连接

文章目录 前言1. 开启群晖SFTP连接2. 群晖安装Cpolar工具3. 创建SFTP公网地址4. 群晖SFTP远程连接5. 固定SFTP公网地址6. SFTP固定地址连接 前言 随着远程办公和数据共享成为新常态&#xff0c;如何高效且安全地管理和传输文件成为了许多人的痛点。如果你正在寻找一个解决方案…

U3D的.Net学习

Mono&#xff1a;这是 Unity 最初采用的方式&#xff0c;它将 C# 代码编译为中间语言 (IL)&#xff0c;然后在目标平台上使用虚拟机 (VM) 将其转换为本地机器码执行。 IL2CPP&#xff1a;这是一种较新的方法&#xff0c;它会将 C# 代码先编译为 C 代码&#xff0c;再由 C 编译器…

2024年博客之星主题创作|2024年度感想与新技术Redis学习

Redis工具深入了解 1.引言与感想2.Redis工具了解2.分布式系统了解2.1单机架构2.2分布式是什么2.3应用服务和数据库服务分离2.4引入更多的应用服务器2.5理解负载均衡器2.6数据库读写分离2.7引入缓存2.8数据库分库分表2.9引入微服务2.10分布式系统小结 1.引言与感想 2024学习了很…

IDEA中Maven使用的踩坑与最佳实践

文章目录 IDEA中Maven使用的踩坑与最佳实践一、环境配置类问题1. Maven环境配置2. IDEA中Maven配置建议 二、常见问题与解决方案1. 依赖下载失败2. 依赖冲突解决3. 编译问题修复 三、效率提升技巧1. IDEA Maven Helper插件使用2. 常用Maven命令配置3. 多模块项目配置4. 资源文件…

qml Timer详解

1、概述 Timer是QML中用于创建定时器的元素。它允许你设置一个延迟时间&#xff0c;在延迟时间结束后触发一个信号或执行一段代码。Timer非常适用于需要在特定时间间隔后执行操作的场景&#xff0c;如动画延迟、定时任务等。 2、重要属性 interval: 定时器触发的时间间隔&…

为什么redis会开小差?Redis 频繁异常的深度剖析与解决方案

文章目录 导读为什么redis会开小差&#xff1f;1.连接数过多2.bigkey3.慢命令操作4.内存策略不合理5.外部数据双写一致性6.保护机制未开启7. 数据集中过期8. CPU饱和9. 持久化阻塞10. 网络问题结论 导读 提起分布式缓存&#xff0c;想必大多数同学脑海中都会浮出redis这个名字…

国产编辑器EverEdit - 快捷目录

1 面板-快捷目录 1.1 应用场景 快捷目录提供了一个用户常用文件或文件夹的收集器&#xff0c;方便用户快速找到并打开需要频繁编辑的文件。 1.2 使用方法 1.2.1 打开快捷目录面板 步骤1&#xff1a;选择菜单“查看 -> 停靠窗格 -> 快捷目录”&#xff0c;即可打开快捷…

正则表达式的艺术:轻松驾驭 Python 的 re 库

目录 一、正则表达式的基本概念 二、Python 的 re 库简介 三、正则表达式的元字符 四、正则表达式的贪婪与非贪婪模式 五、实战案例 六、总结 正则表达式&#xff08;Regular Expression&#xff09;是文本处理中不可或缺的工具&#xff0c;它强大而灵活&#xff0c;能够…

炸场硅谷,大模型“蒸汽机”迎来“瓦特时刻”

作者 | 曾响铃 文 | 响铃说 中国大模型又在包括硅谷在内的全球AI圈炸场了。 两天前&#xff0c;幻方量化旗下AI公司深度求索&#xff08;DeepSeek&#xff09;&#xff0c;以及月之暗面相隔20分钟相继发布了自家最新版推理模型&#xff0c;分别是DeepSeek-R1以及Kimi 全新多…

备赛蓝桥杯之第十五届职业院校组省赛第二题:分享点滴

提示&#xff1a;本篇文章仅仅是作者自己目前在备赛蓝桥杯中&#xff0c;自己学习与刷题的学习笔记&#xff0c;写的不好&#xff0c;欢迎大家批评与建议 由于个别题目代码量与题目量偏大&#xff0c;请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题&#xff0…