//1、创建双向链表
node_p create_loop_double()
{
node_p H=(node_p)malloc(sizeof(node));
if(H==NULL)
return NULL;
H->pri=H;
H->next=H;
H->len=0;
return H;
}
//2、创建结点
node_p create_node(int data)
{
node_p new_node=(node_p)malloc(sizeof(node));
if(new_node==NULL)
{
printf("申请空间失败\n");
return NULL;
}
new_node->data=data;
new_node->pri=NULL;
new_node->next=NULL;
return new_node;
}
//3、判空
int empty_loop_double(node_p H)
{
if(H==NULL)
return -1;
return H->next==H?1:0;
}
//4、头插
void insert_head(node_p H,int value)
{
if(H==NULL)
{
printf("入参为空\n");
return;
}
node_p new=create_node(value);
new->next=H->next;
new->pri=H;
H->next->pri=new;
H->next=new;
H->len++;
}
//5.尾插
void insert_tail(node_p H,int value)
{
if(H==NULL)
return;
node_p p=H;
while(p->next!=H)
p=p->next;
node_p new=create_node(value);
new->pri=p;
new->next=H;
H->pri=new;
p->next=new;
H->len++;
}
//6.输出
void show_loop_double(node_p H)
{
if(H==NULL)
{
printf("入参为空\n");
return;
}
if(empty_loop_double(H))
return;
node_p p=H->next;
while(p!=H)
{
printf("%d <-> ",p->data);
p=p->next;
}
printf("H\n");
}
//7.任意位置插入
void insert_pos(node_p H,int pos,int value)
{
if(H==NULL)
return;
if(pos<=0)
return;
int i=0;
node_p p = H;
for(;inext)
{
if(i>0 && p==H)
{
printf("插入位置不合理\n");
return;
}
}
if(p==H)
{
printf("插入位置不合理\n");
return;
}
node_p new=create_node(value);
new->next=p->next;
new->pri=p;
p->next->pri=new;
p->next=new;
}
//8.头删
void dele_head(node_p H)
{
if(H==NULL)
return;
if(empty_loop_double(H))
return;
node_p dele=H->next;
H->next=dele->next;
if(H->next->next!=H)
dele->next->pri=H;
free(dele);
H->len--;
}
//9.尾删
void dele_tail(node_p H)
{
if(H==NULL)
return;
node_p p=H;
while(p->next!=H)
p=p->next;
p->pri->next=H;
H->pri=p->pri;
free(p);
H->len--;
}
//10.按位置删除
void dele_pos(node_p H,int pos)
{
if(H==NULL)
return;
if(empty_loop_double(H))
return;
int i=0;
node_p p=H;
for(;inext);
if(p==H)
{
printf("位置不合理\n");
return;
}
p->next->pri=p->pri;
p->pri->next=p->next;
free(p);
H->len--;
}
//11.按值查找返回位置
int search_value(node_p H,int value)
{
if(H==NULL)
{return -1;}
if(empty_loop_double(H)){return -2;}
int i=1;
node_p p=H->next;
for(;p!=H;++i,p=p->next)
{
if(p->data==value)
return i;
}
return -3;
}
//12.按位置修改元素
void update_pos(node_p H,int pos,int new_value)
{
if(H==NULL)
return;
if(empty_loop_double(H))
return;
if(pos<=0)
return;
int i=0;
node_p p=H;
for(;inext)
{
if(i>0 && p==H)
{
printf("输入位置不合理\n");
return;
}
}
p->data=new_value;
}
//13.释放双向循环链表
void des_loop_double(node_p* H)
{
if(H==NULL||*H==*H)
return;
while((*H)->next)
{
dele_head(*H);
}
free(*H);
*H=NULL;
}
在 C 语言里,链表和顺序表(也就是数组)是两种常用的数据结构,它们各有优缺点。
顺序表也就是数组
优点
- 随机访问效率高:借助下标,能在 O (1) 时间复杂度内迅速访问任意元素。
- 内存空间连续:这一特性有利于 CPU 缓存预取,可提升访问速度。
- 存储密度大:每个元素仅存储数据本身,无需额外的指针域,空间利用率高。
- 实现简单:基本操作如初始化、访问元素等易于实现。
缺点
- 插入和删除效率低:若要在中间或头部进行插入、删除操作,平均需要移动 O (n) 个元素。
- 大小固定:静态数组的大小在编译时就已确定,难以动态调整;动态数组虽可调整大小,但调整过程(如重新分配内存、复制元素)开销较大。
- 内存分配问题:如果分配的空间过大,会造成内存浪费;若分配空间过小,又需频繁扩容。
链表
优点
- 动态扩展灵活:可以按需动态分配和释放内存,适合处理元素数量不确定的情况。
- 插入和删除效率高:在已知前驱节点的前提下,插入和删除操作只需 O (1) 的时间复杂度。
- 内存利用率高:无需预先分配大块连续内存,能有效利用碎片化的内存空间。
缺点
- 不支持随机访问:要访问链表中的某个元素,必须从表头开始遍历,时间复杂度为 O (n)。
- 存储密度低:每个节点除了存储数据,还需额外存储指针域,增加了空间开销。
- 缓存局部性差:由于节点在内存中是离散存储的,不利于 CPU 缓存预取,访问速度会受到影响。
- 实现复杂:需要管理内存分配和指针操作,容易出现内存泄漏或悬空指针等问题。
总结
- 顺序表更适合对随机访问需求较高、数据元素数量固定或者变化不大的场景。
- 链表则在需要频繁进行插入删除操作、数据元素数量动态变化的情况下更为适用。
刷题
1.
对链表进行插入和删除操作时不必移动链表中结点。 ( )
A
正确
B
错误
正确答案:A
你的答案:B
官方解析:
链表是一种重要的线性数据结构,由节点构成,每个节点包含数据域和指向下一个节点的指针域。在链表中进行插入和删除操作时,确实不需要移动其他节点,只需要修改相关节点的指针即可。
具体分析:
1. 对于插入操作:
- 只需要修改插入位置前后节点的指针
- 新节点的指针指向后继节点
- 前驱节点的指针指向新节点
- 时间复杂度为O(1)
2. 对于删除操作:
- 只需要修改待删除节点前后节点的指针
- 将前驱节点的指针指向待删除节点的后继节点
- 时间复杂度为O(1)
这与数组不同。在数组中进行插入或删除操作时,需要移动插入或删除位置后的所有元素,时间复杂度为O(n)。
因此A选项"正确"是对的,B选项"错误"是不对的。这也是链表相对于数组的一个重要优势。这种特性使得链表在频繁进行插入删除操作的场景中具有更好的性能。知识点:链表
题友讨论(4)
单选题
链表
3.
链表所具备的特点是( )。
A
可以随机访问任一结点
B
占用连续的存储空间
C
可以通过下标对链表进行直接访问
D
插入删除元素的操作不需要移动元素结点
正确答案:D
你的答案:A
官方解析:
链表是一种常见的数据结构,其显著特点是通过节点之间的指针连接而成,而非占用连续的内存空间。D选项是正确的,因为在链表中进行插入和删除操作时,只需要修改相关节点的指针指向,无需像数组那样移动其他元素。
分析其他选项:
A错误:链表不支持随机访问。要访问链表中的某个节点,必须从头节点开始,沿着指针逐个遍历,直到找到目标节点。
B错误:链表的节点在内存中是分散存储的,每个节点占用的内存空间不需要连续。这与数组必须占用连续内存空间的特点完全不同。
C错误:链表不能像数组那样通过下标直接访问元素。要访问链表中的某个位置,必须从头遍历到指定位置,这是一个O(n)的操作。
这些特点使得链表特别适合需要频繁插入删除操作的场景,但在需要随机访问的场景下性能较差。知识点:链表
题友讨论(4)
单选题
链表
8.
链表不具有的特点是()
A
插入、删除不需要移动元素
B
可随机访问任一元素
C
不必事先估计存储空间
D
所需空间与线性长度成正比
正确答案:B
你的答案:D
官方解析:
链表是一种常见的数据结构,通过指针将各个节点连接在一起。B选项"可随机访问任一元素"是链表所不具备的特点。因为链表必须从头结点开始,依次遍历节点才能访问特定元素,这种访问方式是顺序访问,而不是随机访问。
分析其他选项:
A正确:链表的插入和删除操作只需要修改相关节点的指针,不需要像数组那样移动其他元素。
C正确:链表是动态分配存储空间的,不需要像数组那样事先确定大小,可以根据需要动态增长。
D正确:链表结构中每个节点都需要存储数据和指针,所需的存储空间会随着链表长度的增加而线性增加。
总的来说,链表的主要特点是:
1. 动态分配空间
2. 插入删除效率高
3. 顺序访问
4. 空间利用率高
但不支持随机访问,这是它区别于数组等其他数据结构的重要特征。知识点:链表
题友讨论(6)
单选题
链表
9.
链表不具有的特点是 ( ) ?
A
插入、删除不需要移动元素
B
可随机访问任一元素
C
不必事先估计存储空间
D
所需空间与线性长度成正比
正确答案:B
你的答案:C
官方解析:
链表是一种常见的线性数据结构,通过指针将零散的内存空间串联起来。B选项"可随机访问任一元素"是链表所不具备的特点,因为链表必须从头结点开始依次遍历才能访问特定位置的元素,这是顺序访问,而不是随机访问。
分析其他选项:
A正确:链表的插入和删除操作只需要修改相关结点的指针,不需要像数组那样移动其他元素
C正确:链表是动态分配存储空间的,不需要事先确定大小,可以根据需要动态增长
D正确:链表存储数据需要额外的指针空间,所需总空间与链表长度成正比关系
这也是为什么在需要频繁插入删除操作的场景中常用链表,而在需要随机访问的场景中常用数组的原因。数组可以通过下标直接访问任意位置的元素,时间复杂度是O(1),而链表的访问则需要O(n)的时间复杂度。知识点:链表
题友讨论(4)
单选题
链表
10.
下面关于线性表的叙述中,错误的是哪一个?()
A
线性表采用顺序存储,必须占用一片连续的存储单元。
B
线性表采用顺序存储,便于进行插入和删除操作。
C
线性表采用链接存储,不必占用一片连续的存储单元。
D
线性表采用链接存储,便于插入和删除操作。
正确答案:B
你的答案:A
官方解析:
在线性表的不同存储方式特点中,B选项是错误的。顺序存储结构虽然可以实现随机访问,但在插入和删除操作时需要移动大量元素,效率较低。
具体分析各选项:
A正确:顺序存储结构要求在内存中占用一整块连续的存储空间,这是其基本特征。
B错误:顺序存储恰恰不利于插入和删除操作。因为在顺序表中进行插入或删除时,需要将插入位置后的所有元素都向后或向前移动一个位置,时间复杂度为O(n)。
C正确:链式存储的节点可以散布在内存中的任何地方,只要通过指针域链接起来即可,不需要连续的存储空间。
D正确:链式存储的一大优点就是方便插入和删除操作。因为只需要修改相关节点的指针,无需移动其他元素,时间复杂度为O(1)。
总的来说,顺序存储的优点是支持随机访问,缺点是插入删除效率低;而链式存储的优点是插入删除方便,缺点是只能顺序访问。这是两种基本存储结构的重要区别。知识点:链表
题友讨论(9)
单选题
链表
11.
在顺序表中,只要知道_______,就可在相同时间内求出任一结点的存储地址。
A
基地址
B
结点大小
C
向量大小
D
基地址和结点大小
正确答案:D
你的答案:未作答
官方解析:
在顺序表中求任一结点的存储地址需要同时知道基地址和结点大小,这是因为:
顺序表中结点的存储地址计算公式为:
结点地址 = 基地址 + (i-1) × 结点大小
其中 i 为结点的序号
所以D选项"基地址和结点大小"是正确答案,因为:
1. 基地址确定了顺序表在内存中的起始位置
2. 结点大小决定了每个结点占用的存储空间大小
3. 只有同时知道这两个参数,才能通过上述公式准确计算出任意结点的存储地址
分析其他选项:
A错误:仅知道基地址只能确定起始位置,无法计算后续结点位置
B错误:仅知道结点大小只能确定偏移量,无法确定具体存储位置
C错误:向量大小指顺序表的容量,与计算结点存储地址无关
总之,在顺序表中要实现随机存取(在相同时间内访问任意结点),必须同时具备基地址和结点大小这两个关键信息。知识点:链表
题友讨论(7)
单选题
数组
链表
Java
14.
JDK8之后版本,HashMap的数据结构是怎样的?
A
数组
B
链表
C
数组+链表/红黑树
D
二叉树
正确答案:C
你的答案:B
官方解析:
HashMap采用的是数组+链表的组合数据结构,这也被称为"散列表"。具体实现上,HashMap使用一个Node数组来存储数据,每个数组元素称为桶(bucket),当发生hash冲突时,会在桶上形成一个链表来存储具有相同哈希值的元素。
从JDK 1.8开始,HashMap在链表长度超过8且数组长度超过64时,会将链表转换为红黑树,以提高查询效率。但其基本结构仍然是数组+链表的组合。
分析其他选项:
A错误:仅使用数组结构无法有效解决hash冲突问题。
B错误:仅使用链表结构会导致查询效率低下,不能发挥哈希的优势。
D错误:HashMap的基本结构不是二叉树。虽然在特定条件下会转换为红黑树,但这只是一种优化手段,不是基本数据结构。
HashMap采用数组+链表的结构设计有其合理性:
1. 数组提供了O(1)的直接访问能力
2. 链表解决了hash冲突问题
3. 这种组合结构能够在时间和空间上取得较好的平衡知识点:Java、数组、链表、Java工程师、安卓工程师、测试工程师、2019、测试开发工程师
题友讨论(99)
单选题
链表
15.
若某线性表中最常用的操作是在最后一个元素之后插入一个元素和删除第一个元素,则采用( )存储方式最节省运算时间。
A
单链表
B
仅有头指针的单循环链表
C
双链表
D
仅有尾指针的单循环链表
正确答案:D
你的答案:A
官方解析:
在这个题目中,需要考虑在线性表尾部插入元素和删除第一个元素这两种最常用操作的效率。D选项"仅有尾指针的单循环链表"是最优选择,原因如下:
1. 在表尾插入元素:
- 有了尾指针,可以直接定位到最后一个节点
- 由于是循环链表,尾节点的next指向头节点,便于维护链表的循环特性
- 插入操作的时间复杂度为O(1)
2. 删除第一个元素:
- 虽然没有头指针,但可以通过尾指针的next找到第一个节点
- 删除后只需修改尾指针的next即可
- 删除操作的时间复杂度也为O(1)
分析其他选项:
A. 单链表:
- 在表尾插入需要遍历到最后,时间复杂度O(n)
- 删除第一个元素虽然容易,但整体效率不如D选项
B. 仅有头指针的单循环链表:
- 删除第一个元素方便
- 但在表尾插入需要遍历到最后,时间复杂度O(n)
C. 双链表:
- 虽然功能强大,但需要维护两个指针
- 在此场景下多余,增加了存储开销知识点:链表
题友讨论(12)
单选题
链表
21.
对于一个头指针为L的带头结点的单链表,判定链表为空表的条件是()
A
L=NULL;
B
L->next=NULL;
C
L->next==NULL;
D
L!=NULL;
正确答案:C
你的答案:B
官方解析:
这道题目考察了单链表判空的基本概念。在带头结点的单链表中,C选项"L->next==NULL"是正确的判空条件,因为:
1. 带头结点的单链表中,头指针L指向的是头结点
2. 头结点是一个不存放数据的结点,始终存在
3. 当链表为空时,头结点的next指针指向NULL
4. 因此通过判断L->next是否为NULL来确定链表是否为空
分析其他选项:
A错误:L=NULL表示头指针为空,这与带头结点的链表特性矛盾,因为带头结点的链表头指针永远指向头结点
B错误:L->next=NULL是赋值语句,不是判断语句,用于将头结点的next指针置空
D错误:L!=NULL只是判断头指针是否为空,不能判断链表是否为空表,且带头结点的链表L永远不为NULL
因此判断带头结点的单链表是否为空,正确的条件是检查头结点的next指针是否为NULL,即L->next==NULL。知识点:链表
题友讨论(22)
单选题
链表
27.
线性表的顺序存储结构是一种_______的存储结构。
A
随机存取
B
顺序存取
C
索引存取
D
散列存取
正确答案:A
你的答案:D
官方解析:
线性表的顺序存储结构采用随机存取方式,这是因为:
1. 在顺序存储结构中,所有数据元素在内存中连续存储,且每个元素占用相同大小的存储空间。
2. 通过首地址和元素序号就可以直接计算出任意位置元素的存储地址:
LOC(i) = LOC(1) + (i-1) × size
其中LOC(i)为第i个元素的存储位置,LOC(1)为第一个元素的存储位置,size为每个元素占用的存储单元大小。
3. 可以在O(1)时间内直接访问任意位置的元素,不需要从头遍历。
分析其他选项:
B错误:顺序存取是指必须按照存储顺序依次访问元素,如链表结构。
C错误:索引存取是指通过建立索引表来加快检索,不是线性表顺序存储的本质特征。
D错误:散列存取是指通过散列函数计算存储位置,这是散列表的特征。
所以线性表的顺序存储结构采用的是随机存取方式,这也是它能够实现高效访问的重要原因。知识点:链表
题友讨论(10)
单选题
链表
数组
28.
用数组r存储静态链表,结点的next域指向后继,工作指针j指向链中结点,使j沿链移动的操作为()?
A
j=r[j].next
B
j=j+1
C
j=j->next
D
j=r[j]->next
正确答案:A
你的答案:D
官方解析:
在静态链表中,使用数组r来存储链表结点信息,每个结点的next域指向后继结点在数组中的下标位置。因此选项A "j=r[j].next" 是正确的,它表示j更新为当前结点j的后继结点的下标。
分析其他选项:
B错误:j=j+1是顺序存储结构的遍历方式,只是简单地将下标加1。在链式存储中,相邻结点在数组中的位置不一定相邻,所以这种方式不适用于静态链表。
C错误:j->next是动态链表中使用指针的访问方式。在静态链表中使用数组实现,不存在指针运算,所以这种写法是错误的。
D错误:r[j]->next的写法混用了数组访问和指针运算,在静态链表中是语法错误。静态链表使用数组实现,应该用数组下标访问方式r[j].next。
解释:静态链表是用数组模拟链表,结点间的链接关系是通过数组元素的next域来维护的,而不是通过实际的指针。所以遍历时要通过数组下标访问方式获取下一个结点的位置。知识点:数组、链表
题友讨论(76)
单选题
链表
队列
29.
某带链的队列初始状态为 front=rear=NULL 。经过一系列正常的入队与退队操作后, front=rear=10 。该队列中的元素个数为( )
A
1
B
0
C
1或0
D
不确定
正确答案:A
你的答案:B
官方解析:
往队列的队尾插入一个元素为入队,从队列的排头删除一个元素称为退队。初始时 front=rear=0 , front 总是指向队头元素的前一位置,入队一次 rear+1 ,退队一次 front+1 。队列队头队尾指针相同时队列为空。而带链的队列,由于每个元素都包含一个指针域指向下一个元素,当带链队列为空时 front=rear=Null ,插入第 1 个元素时, rear+1 指向该元素, front+1 也指向该元素,插入第 2 个元素时 rear+1 , front 不变,删除 1 个元素时 front+1 。即 front=rear 不为空时带链的队列中只有一个元素。故本题答案为 A 选项。
知识点:链表、队列
题友讨论(55)
多选题
链表
数组
30.
以下操作中,数组比链表速度更快的是()
A
返回中间节点
B
返回头部节点
C
选择随机节点
D
头部插入
正确答案:AC
你的答案:C
官方解析:
注解:LRU每次使用以后排在最前面
知识点:数组、链表、测试、后端开发、客户端开发、前端开发、人工智能/算法、数据、运维/技术支持