1、本栏用来记录社招找工作过程中的内容,包括基础知识学习以及面试问题的记录等,以便于后续个人回顾学习; 暂时只有2023年3月份,第一次社招找工作的过程;
2、个人经历: 研究生期间课题是SLAM在无人机上的应用,有接触SLAM、Linux、ROS、C/C++、DJI OSDK等;
3、参加工作后(2021-2023年)岗位是嵌入式软件开发,主要是服务器开发,Linux、C/C++、网络编程、docker容器、CMake、makefile、Shell脚本、JSON等。4、求职岗位是嵌入式软件开发、C/C++开发、自动驾驶岗位开发等。
参考:
http://c.biancheng.net/view/3336.html
过程记录
一个完整的链表需要由以下几部分构成:
1、头指针
:一个普通的指针,它的特点是永远指向链表第一个节点的位置。很明显,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据;
2、节点
:链表中的节点又细分为头节点
、首元节点
和其他节点
:
头节点
:其实就是一个不存任何数据的空节点,通常作为链表的第一个节点。对于链表来说,头节点不是必须的,它的作用只是为了方便解决某些实际问题;
首元节点
:由于头节点(也就是空节点)的缘故,链表中称第一个存有数据的节点为首元节点。首元节点只是对链表中第一个存有数据节点的一个称谓,没有实际意义;
其他节点
:链表中其他的节点;
因此,一个存储 {1,2,3}
的完整链表结构如下图 所示:
注意
:链表中有头节点时,头指针指向头节点;反之,若链表中没有头节点,则头指针指向首元节点。
无头节点的链表创建成功后的最终结果如下图所示:
创建一个存储 {1,2,3,4}
且无头节点的链表,C 语言实现代码如下:
#include
#include
typedef struct Link
{
int data;//数据域,存放数据信息
struct Link * Next;//指针域,用于指向后继元素
}link;//link为节点名,每一个节点都是一个link结构体
link * initlink();//初始化链表
void print(link * InputLink);//遍历链表并打印里面的data数据
int main()
{
link * p = initlink();//建立一个链表p,并调用initLink进行初始化
print(p);//遍历并打印该链表的data
return 0;
}
/*创建一个存储 {1,2,3,4} 且无头节点的链表,并初始化*/
link * initlink()
{
link * p = NULL;//先创建一个空的头指针
/*创建首元节点*/
link *tmp = NULL;
tmp = (link *)malloc(sizeof(link));
tmp->data = 1;//首元节点的数据赋值为1
tmp->Next = NULL;
p = tmp;//头指针指向首元节点
/*循环创建3个数据节点*/
int i;
for(i = 2; i<5; i++)
{
link * a = NULL;
a = (link*)malloc(sizeof(link));
a->data = i;//节点中的数据设置为i值,即2,3,4
a->Next = NULL;
tmp->Next = a;//将temp节点与新建立的a节点建立逻辑关系,即前一个的next指向后面的节点
//第一次循环,将tmp节点由指向首元节点改为指向第一个a节点;剩下的循环,将tmp由指向旧的a改为指向新的a
//即指针temp每次都指向新链表的最后一个节点,其实就是 a节点,这里写temp=a也对
tmp = tmp->Next;
}
return p;
}
void print(link * InputLink)
{
link *tmp = InputLink;//建立一个临时的指针,用来遍历链表,刚开始先指向链表的头指针
while(NULL!=tmp)//如果tmp不为空,说明有该节点,即可取数据。
{
printf("%d ",tmp->data);//打印该节点中的数据
tmp = tmp->Next;//tmp指向该节点中的Next指针,当遍历到最后一个节点时,该最后节点的Next指针为空,此时tmp也为空,则下一个循环就不会进入了
}
}
创建一个存储 {1,2,3,4}
且有头节点的链表,C 语言实现代码如下:
#include
#include
typedef struct Link
{
int data;//数据域,存放数据信息
struct Link * Next;//指针域,用于指向后继元素
}link;//link为节点名,每一个节点都是一个link结构体
link * initlink();//初始化链表
void print(link * InputLink);//遍历链表并打印里面的data数据
int main()
{
link * p = initlink();//建立一个链表p,并调用initLink进行初始化
print(p);
return 0;
}
/*创建一个存储 {1,2,3,4} 且有头节点的链表,并初始化*/
link * initlink()
{
link * p = (link *)malloc(sizeof(link));//创建头结点
link *tmp = p;
int i ;
for(i = 1;i<5;i++)
{
link * a = (link *)malloc(sizeof(link));
a->data = i;
a->Next = NULL;
tmp ->Next = a;
tmp = tmp->Next;
}
return p;
}
/*使用带有头节点创建链表的方式,则print函数总打印链表中的数据时,要跳过头结点,因为头结点中是没有数据的,所以判断条件为“当下一个节点中的Next不为NULL时,即说明它不是最后一个节点,就打印此” */
void print(link * InputLink)
{
link *tmp = InputLink;//建立一个临时的指针,用来遍历链表,刚开始先指向链表的头指针
while(NULL!=tmp->Next)//判断tmp->Next是否为空,不为空说明后面有节点,即不是最后一个节点;为什么不判断tmp?因为tmp第一次代表头结点,头结点的data没数据。
{
printf("%d ",tmp->Next->data);//打印tmp节点下一个中的数据,即跳过了第一个节点也就是头结点
tmp = tmp->Next;//指向下一个节点
}
}
在写增删改查函数时,一定要时刻参照单链表的图,如下:
增删改查总代码:
#include
#include
typedef struct Link
{
int data;//数据域,存放数据信息
struct Link * Next;//指针域,用于指向后继元素
}link;//link为节点名,每一个节点都是一个link结构体
link * initlink();//初始化链表
void print(link * InputLink);//遍历链表并打印里面的data数据
link * Insert(link * TargetLink,int data_new,int Add);//向链表中插入元素
link * Delate(link *TargetLink,int pos_del);//删除链表中的指定节点
link* deleteNode(link* head, int val);//删除指定元素
link * update(link *TargetLink ,int date_new,int pos_update);//修改链表中的元素
int select(link *TargetLink,int date_select);//在链表中查找元素
int main()
{
link * p = initlink();//建立一个链表p,并调用initLink进行初始化
printf("原链表:");
print(p);
printf("\n");
link * Add = Insert(p,5,2);//调用插入函数,向原链表中第二个节点插入一个数字5
printf("插入元素后的链表:");
print(p);
printf("\n");
link * Del = Delate(p,2);//调用删除函数,删除第二个节点
printf("删除第2个节点后的链表:");
print(p);
printf("\n");
link * Del_ = deleteNode(p,2);//调用删除函数,删除数据为2的节点
printf("删除数字为2的节点后的链表:");
print(p);
printf("\n");
link * up = update(p,5,2);//调用修改函数,将原链表第二个位置的数字修改为5
printf("修改元素后的链表:");
print(p);
printf("\n");
int pos_sec = select(p,5);//调用查找函数,在链表中查找数字5对应的节点位置
printf("数字5是第%d个节点\n",pos_sec);
return 0;
}
#if 1
/*创建一个存储 {1,2,3,4} 且有头节点的链表,并初始化*/
link * initlink()
{
link * p = (link *)malloc(sizeof(link));//创建头结点
link *tmp = p;
int i ;
for(i = 1;i<5;i++)
{
link * a = (link *)malloc(sizeof(link));
a->data = i;
a->Next = NULL;
tmp ->Next = a;
tmp = tmp->Next;
}
return p;
}
/*使用带有头节点创建链表的方式,则print函数总打印链表中的数据时,要跳过头结点,因为头结点中是没有数据的,所以判断条件为“当下一个节点中的Next不为NULL时,即说明它不是最后一个节点,就打印此” */
void print(link * InputLink)
{
link *tmp = InputLink;//建立一个临时的指针,用来遍历链表,刚开始先指向链表的头指针
while(NULL!=tmp->Next)//判断tmp->Next是否为空,不为空说明后面有节点,即不是最后一个节点;为什么不判断tmp?因为tmp第一次代表头结点,头结点的data没数据。
{
printf("%d ",tmp->Next->data);//打印tmp节点下一个中的数据,即跳过了第一个节点也就是头结点
tmp = tmp->Next;//指向下一个节点
}
}
/*
功能:向链表中插入元素
入参:
link *TargetLink 要插入数据的目标链表(带有头结点)
int data_new 要插入的元素数据
int Add 要插入的位置,比如Add为2,则将新元素插在节点2的位置上
*/
link * Insert(link * TargetLink,int data_new,int Add)
{
link *tmp = TargetLink;
int i ;
for(i = 1;i<Add;i++)//因为有头结点的存在,所以不必单独判断是否是插入在首元节点之前还是之后,整个步骤都是一样的,因为都是插入在头结点之后的。不用单独判断Add是否为1
{
if(NULL == tmp->Next)//说明没找到符合Add位置的节点,即超出了原链表的范围
{
printf("error:Add Not in Link\n");
return TargetLink;
}
tmp = tmp->Next;
}
//创建新节点
link * New = (link *)malloc(sizeof(link));
New ->data = data_new;
//向链表中插入节点
New->Next = tmp->Next;
tmp->Next = New;
return TargetLink;
}
/*
功能:向链表中删除元素
入参:
link *TargetLink 要删除数据的目标链表(带有头结点)
int pos_del 要删除的节点位置
*/
link * Delate(link *TargetLink,int pos_del)
{
link *tmp = TargetLink;
int i ;
for(i = 1;i<pos_del;i++)
{
if(NULL == tmp->Next)//说明没找到符合删除位置的节点,即超出了原链表的范围
{
printf("error:没有该节点\n");
return TargetLink;
}
tmp = tmp->Next;//此时的tmp已经指向了要删除的节点的前面的节点
}
//单独设置一个指针,指向要删除的节点,用于释放该节点
link * del = tmp->Next;//因为此时tmp指向的是要删除节点的前面的节点,因此被删除节点时tmp->Next
tmp->Next = tmp->Next->Next;//tmp里的Next指针指向下一个节点里的Next指针
free(del);//手动释放该节点,防止内存泄漏
return TargetLink;
}
/*
功能:向链表中删除元素
入参:
link *head 要删除数据的目标链表(不带头结点)
int val 要删除的数据
*/
link* deleteNode(link* head, int val) {
int pos = 1;//用于要删除的是第几个节点
link *tmp1 = head;//用于指向被删除的节点
link *tmp2 = head;//用于指向被删除节点的前一个节点
while(NULL != tmp1 )
{
if(val == tmp1 -> data)//如果是要删除的数据
{
if(1 == pos)//如果是第一个节点
{
head = tmp1 -> Next;//需要将头指针指向后一个节点,因为第一个要被删除
}
else//如果被删除的不是头结点
{
tmp2 -> Next = tmp1 -> Next;//被删除节点的前一个节点的Next指向被删除节点的后一个节点
tmp1 -> Next = NULL;//被删除的节点的Next指向空
free(tmp1);//释放掉要被删除的节点
}
return head;
}
else//如果不是要删除的数据,则记录继续往下遍历
{
tmp2 = tmp1;//此处:tmp2记录下tmp1的前一个节点
tmp1 = tmp1 -> Next;//tmp1继续往下
pos ++;//记录被删除的是第几个节点
}
}
return head;
}
/*
功能:链表中修改元素
入参:
link *TargetLink 要修改数据的目标链表(带有头结点)
int date_new 要修改成的数据
int pos_update 要修改的节点位置
*/
link * update(link *TargetLink ,int date_new,int pos_update)
{
link *tmp = TargetLink;
int i ;
for(i = 0;i<pos_update;i++)
{
if(NULL == tmp->Next)//说明没找到符合修改位置的节点,即超出了原链表的范围
{
printf("error:没有该节点\n");
return TargetLink;
}
tmp = tmp->Next;//此时的tmp已经指向了要修改的节点
}
tmp->data = date_new;
return TargetLink;
}
/*
功能:在链表中查找目标元素,返回找到的位置,未找到返回-1
入参:
link *TargetLink 要查找数据的目标链表(带有头结点)
int date_select 要查找到数据
*/
int select(link *TargetLink,int date_select)
{
link *tmp = TargetLink;
int i = 1;//从首元节点开始,第一次循环即首元节点的位置
while(NULL != tmp->Next)//因为有头结点,头结点没数据,所以需要跳过头结点,直接找首元节点的数据;所以头指针的Next为首元节点,即跳过头结点
{
if(date_select == tmp->Next->data)
{
return i;//找到了,返回i
}
tmp = tmp->Next;
i++;
}
return -1;
}
#endif
从名字上理解双向链表,即链表是 “双向” 的,如图 1 所示:
双向,指的是各节点之间的逻辑关系是双向的,但通常头指针只设置一个,除非实际情况需要
。
从图 1 中可以看到,双向链表中各节点包含以下 3 部分信息(如图 2 所示):
指针域:用于指向当前节点的直接前驱节点;
数据域:用于存储数据元素。
指针域:用于指向当前节点的直接后继节点;
因此,双链表的节点结构用 C 语言实现为:
typedef struct line{
struct line * prior; //指向直接前趋
int data;
struct line * next; //指向直接后继
}line;
同单链表相比,双链表仅是各节点多了一个用于指向直接前驱的指针域。因此,我们可以在单链表的基础轻松实现对双链表的创建。
需要注意的是,与单链表不同,双链表创建过程中,每创建一个新节点,都要与其前驱节点建立两次联系,分别是:
具体代码实现:
#include
#include
typedef struct Line
{
struct Line * prior;//指向直接前驱
int data;
struct Line * next;//指向直接后继
}line;
line * lineInit();//双链表初始化函数
int print(line * TargetLine);//遍历打印双链表里的数据
line * lineInit()
{
line *head = NULL;//定义一个双链表的头指针
line * tmp = (line *)malloc(sizeof(line));//创建第一个节点(首元节点)并分配内存
tmp->prior = NULL;
tmp->data = 1;
tmp->next = NULL;
head = tmp;//头指针指向第一个节点
int i;
for(i = 2;i<4;i++)
{
line * body = (line *)malloc(sizeof(line));//创建后续的节点
body ->prior=tmp;//前驱指针指向前面的节点
body ->data = i;
body ->next = NULL;//后继指针指向空
tmp ->next = body;//前面节点的后继指针指向当前节点
tmp = tmp->next;//tmp永远指向当前最后一个节点
}
return head;
}
int print(line * TargetLine)
{
line * tmp = TargetLine;//新建tmp指针指向双链表头指针,用来遍历双链表
while(NULL != tmp )
{
if(NULL == tmp -> next)
printf("%d",tmp->data);
else
printf("%d <-> ",tmp->data);
tmp = tmp ->next;//tmp指向后面的节点
}
return 0;
}
int main()
{
line * source = lineInit();
printf("初始化的链表为:");
print(source);
printf("\n");
return 0;
}
#include
#include
typedef struct Line
{
struct Line * prior;//指向直接前驱
int data;
struct Line * next;//指向直接后继
}line;
line * lineInit();//双链表初始化函数
int print(line * TargetLine);//遍历打印双链表里的数据
line * Insert(line *Targetline, int date_new, int Add);//向链表中插入元素
line * Delate(line *TargetLine,int pos_del);//向链表中删除元素
line * update(line *TargetLine ,int date_new,int pos_update);//修改链表中的元素
int select_double(line *TargetLine,int date_select);//在链表中查找元素
line * lineInit()
{
line *head = NULL;//定义一个双链表的头指针
line * tmp = (line *)malloc(sizeof(line));//创建第一个节点(首元节点)并分配内存
tmp->prior = NULL;
tmp->data = 1;
tmp->next = NULL;
head = tmp;//头指针指向第一个节点
int i;
for(i = 2;i<4;i++)
{
line * body = (line *)malloc(sizeof(line));//创建后续的节点
body ->prior=tmp;//前驱指针指向前面的节点
body ->data = i;
body ->next = NULL;//后继指针指向空
tmp ->next = body;//前面节点的后继指针指向当前节点
tmp = tmp->next;//tmp永远指向当前最后一个节点
}
return head;
}
int print(line * TargetLine)
{
line * tmp = TargetLine;//新建tmp指针指向双链表头指针,用来遍历双链表
while(NULL != tmp )
{
if(NULL == tmp -> next)
printf("%d",tmp->data);
else
printf("%d <-> ",tmp->data);
tmp = tmp ->next;//tmp指向后面的节点
}
return 0;
}
/*
功能:向链表中插入元素
入参:
line *Targetline 要插入数据的目标链表(带有头结点)
int data_new 要插入的元素数据
int Add 要插入的位置,比如Add为2,则将新元素插在节点2的位置上
*/
line * Insert(line *Targetline, int date_new, int Add)
{
//printf("Insert\n");
line * tmp = Targetline;//定义临时指针,刚开始指向双链表头指针,后面用来遍历整个链表
/*先判断Add是否在双链表的位置中*/
int i;
for(i = 1;i<Add-1;i++)//为什么是Add-1呢?因为目的是找到要插入位置的前一个结点
{
if(NULL == tmp)
{
printf("ERROR:Add Not exist");
return Targetline;
}
tmp = tmp->next;
//printf("i is %d\n",i);
}
//经过for循环出来后,此时tmp指向的节点就是咱们要插入的位置的前一个节点
if(1 == Add)//如果是插在第一个节点的位置,需要单独处理,因为要改变头指针,头指针改为指向新建的节点,此时没进上面的for循环,tmp还是指向头节点的
{
line * New_head = (line *)malloc(sizeof(line));
New_head ->prior = NULL;//新的头结点前驱指针指向NULL
New_head->data = date_new;
New_head->next =tmp;//新的头结点的后继指针指向旧的头结点
tmp->prior= New_head;//旧的头结点的前驱指针指向新的头结点
Targetline = New_head;//头指针指向新的头结点
}
else//如果不是插在第一个节点的位置
{
if(NULL == tmp->next)//说明tmp是最后一个节点了,即需要查到最后一个节点后面
{
line * body_final = (line *)malloc(sizeof(line));
body_final ->prior = tmp;//新的结点前驱指针指向插入位置的前一个节点
body_final->data = date_new;
body_final->next = NULL;//新的结点的后继指针指向空
tmp->next = body_final;//原来的最后一个节点的后继指针指向新建立的节点,此时原来的最后一个节点变成了倒数第二个节点,新建立的节点变成了最后一个节点
}
else//此时说明是插入到中间位置
{
line * body_new = (line *)malloc(sizeof(line));
body_new ->prior = tmp;//新的结点前驱指针指向原节点的前驱节点
body_new->data = date_new;
body_new->next =tmp->next;//新的结点的后继指针指向原结点
tmp ->next ->prior = body_new;//原节点的前驱指针指向新节点
tmp ->next = body_new;//旧节点的前一个节点的后继指针指向新节点
}
}
return Targetline;
}
/*
功能:向双链表中删除元素
入参:
line *TargetLine 要删除数据的目标链表
int pos_del 要删除的节点位置
*/
line * Delate(line *TargetLine,int pos_del)
{
line *tmp = TargetLine;
int i;
for(i = 1;i<pos_del;i++)
{
if(NULL == tmp ->next)
{
printf("ERROR:pos_del Not exit");
return TargetLine;
}
tmp = tmp ->next;//此时tmp指向了要删除的节点
}
if(1 == pos_del)//如果要删除的是头结点
{
tmp ->next->prior = NULL;//原头结点的下一个节点的前驱指针指向空
TargetLine = tmp->next;//头指针指向原头结点的下一个节点
tmp ->next = NULL;//原头结点的后驱指针指向空
line * Del = tmp;//新建一个指针指向要删除的节点,用于释放内存
free(Del);
}
else if(NULL == tmp ->next)//tmp->next为空说明要删除的是尾节点
{
tmp ->prior->next = NULL;//要删除节点的前一个节点的后驱指针指向空
tmp ->prior = NULL;
free(tmp);
}
else//要删除的既不是头结点,也不是尾节点
{
tmp ->prior->next = tmp ->next;//要删除节点的前一个节点的后驱指针指向要删除节点的下一个节点
tmp ->next->prior = tmp ->prior;//要删除节点的后一个节点的前驱指针指向要删除节点的前一个节点
tmp ->prior =NULL;
tmp ->next = NULL;
free(tmp);
}
return TargetLine;
}
/*
功能:链表中修改元素
入参:
line *TargetLine 要修改数据的目标链表(带有头结点)
int date_new 要修改成的数据
int pos_update 要修改的节点位置
*/
line * update(line *TargetLine ,int date_new,int pos_update)
{
line *tmp = TargetLine;
int i ;
for(i = 1;i<pos_update;i++)
{
if(NULL == tmp)//说明没找到符合修改位置的节点,即超出了原链表的范围
{
printf("error:没有该节点\n");
return TargetLine;
}
tmp = tmp->next;//此时的tmp已经指向了要修改的节点
}
tmp->data = date_new;
return TargetLine;
}
/*
功能:在链表中查找目标元素,返回找到的位置,未找到返回-1
入参:
line *TargetLine 要查找数据的目标链表(带有头结点)
int date_select 要查找到数据
*/
int select_double(line *TargetLine,int date_select)
{
line *tmp = TargetLine;
int i = 1;//从首元节点开始,第一次循环即首元节点的位置
while(NULL != tmp)
{
if(date_select == tmp->data)
{
return i;//找到了,返回i
}
tmp = tmp->next;
i++;
}
return -1;
}
int main()
{
line * source = lineInit();
printf("初始化的链表为:");
print(source);
printf("\n");
source = Insert(source,4,4);//将数字4插入到第三个节点
printf("插入节点后的链表为:");
print(source);
printf("\n");
source = Delate(source,4);//删除第4个节点
printf("删除节点后的链表为:");
print(source);
printf("\n");
source = update(source,6,3);//修改第3个节点为6
printf("修改节点后的链表为:");
print(source);
printf("\n");
int pos_sec = select_double(source,6);//调用查找函数,在链表中查找数字5对应的节点位置
printf("数字6是第%d个节点\n",pos_sec);
return 0;
}