数组与链表
数组
数组是一种线性数据结构,其将相同类型的元素存储在连续的内存空间中。
数组常用操作
- 初始化数组
在数组中访问元素非常高效,我们可以在$O(1)$时间内随机访问数组中的任意一个元素1
2
3
4
5
6int randomAccess(int *nums,int size)
{
int randomIndex=rand()%size;
int randomNum=nums[randomIndex];
return randomNum;
} - 增删元素
数组元素在内存中是紧挨着的,它们之间没有空间再存放任何数据。如果想在数组中间插入一个元素,则需要将该元素之后的所有元素都向后移动一位,之后再把元素赋值给该索引。同理,删除某索引处的元素时,则需要把该索引之后的元素都向前移动一位。1
2
3
4
5
6
7
8void insert(int*nums,int size,int num,int index)
{
for(int i=size-1;i>index;i--)
{
nums[i]=nums[i-1];
}
nums[index]=num;
}1
2
3
4
5
6
7void delete(int *nums,int size,int index)
{
for(int i=index;i<size-1,i++)
{
nums[i]=nums[i+1];
}
} - 遍历数组
既可以通过索引遍历数组,也可以直接遍历获取数组中的每个元素1
2
3
4
5
6
7
8void traverse(int*nums,int size)
{
int count=0;
for(int i=0;i<size;i++)
{
count+=nums[i];
}
} - 查找元素
数组是线性数据结构,所以对其进行查找的操作被称为“线性查找”。1
2
3
4
5
6
7
8
9int find(int*nums,int size,int target)
{
for(int i=0;i<size;i++)
{
if(nums[i]==target)
return i;
}
return -1;
} - 扩容数组
在大多数编程语言中,数组的长度是不可变的。如果我们希望扩容数组,则需重新建立一个更大的数组,然后把原数组元素依次复制到新数组。1
2
3
4
5
6
7
8
9
10
11
12
13int*extend(int*nums,int size,int enlarge)
{
int*res=(int*)malloc(sizeof(int)*(size+enlarge));
for(int i=0;i<size;i++)
{
res[i]=nums[i];
}
for(int i=size;i<size+enlarge;i++)
{
res[i]=0;
}
return res;
}
链表
链表是一种线性数据结构,其中每一个元素都是一个节点对象,各个节点通过“引用”相连接,引用记录了下一个节点的内存地址,通过它可以从当前节点访问到下一个节点。链表的设计使得各个节点可以分散存储在内存各处,它们的内存地址无须连续。
定义链表结构
1 | /* 链表节点结构体 */ |
链表常用操作
- 初始化链表
分为两步:1.初始化各个节点对象 2.构建节点之间的引用关系1
2
3
4
5
6
7
8
9
10
11
12/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个节点
ListNode* n0=newListNode(1);
ListNode* n1 = newListNode(3);
ListNode* n2 = newListNode(2);
ListNode* n3 = newListNode(5);
ListNode* n4 = newListNode(4);
// 构建节点之间的引用
n0->next = n1;
n1->next = n2;
n2->next = n3;
n3->next = n4; - 增删节点
插入节点只需改变两个节点引用(指针)即可,时间复杂度为$O(1)$删除节点只需改变一个节点的引用即可1
2
3
4
5
6void insert(ListNode *old1,ListNode *new)
{
ListNode *old2 = old1->next;
new->next = old2;
old1->next = new;
}1
2
3
4
5
6
7
8
9
10
11/* 删除链表的节点 n0 之后的首个节点 */
// 注意:stdio.h 占用了 remove 关键词
void removeItem(ListNode *n0)
{
if(!n0->next)
return;
ListNode *P=n0->next;// 获取n0后第一个
ListNode *n1=P->next;// 获取n0后第二个
n0->next=n1;//链接n0和其后第二个
free(P);
} - 访问节点
在链表中访问节点的效率较低。程序需要从头节点出发,逐个向后遍历,直至找到目标节点。1
2
3
4
5
6
7
8
9
10/* 访问链表中索引为 index 的节点 */
ListNode *access(ListNode *head,int index)
{
for(int i=0;i<index;i++)
{
if(head==NULL)
return NULL;
head=head->next;
}
} - 查找节点
遍历链表,查找其中值为目标值的节点,输出该节点在链表中的索引。此过程也属于线性查找。1
2
3
4
5
6
7
8
9
10
11
12int find(ListNode *head,int target)
{
int index=0;
while(head)
{
if(head->val==target)
return index;
head=head->next;
index++;
}
return -1;
}
常见链表类型
- 单向链表:即上文所介绍的普通链表
- 环形链表:由单向链表的尾结点指向头节点得到,环形链表中任意节点都能视作头节点。
- 双向链表:其记录了两个方向的引用(后继节点和前驱节点),与单向链表相比其更具灵活性,可从两个方向遍历。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/* 双向链表节点结构体 */
typedef struct ListNode
{
int val;
struct ListNode *next;
struct ListNode *prev;
}ListNode;
ListNode *newListNode(int val)
{
ListNode*node;
node=(ListNode*)malloc(sizeof(ListNode));
node->val;
node->next=NULL;
node->prev=NULL;
return node;
}
列表
链表可以看作一个列表,其支持元素增删查改操作,并且可以灵活动态扩容。数组也支持元素增删查改,但由于其长度不可变,因此只能看作一个具有长度限制的列表,故可以使用动态数组来实现列表。
1 | /* 初始化列表 */ |
1 | /* 列表类 */ |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 hanafuda_store's Blog!
评论
